Experimental · pre-1.0

Time, as a first-class concern

tflo — short for “temporal flow” — is an embeddable temporal engine.

It models experience and business logic as timestamped events and temporal rules: event-time, absence, windows, lateness, replay, and provenance. One pure engine — deterministic, testable, replayable. You decide where it runs.

Where it fits: at the edge, in the browser (WASM), embedded in your Rust service, or alongside the Flink, Esper, or Kafka Streams cluster you already operate. Composition, not substitution.

+------------------------------------------------------------+
|                                                            |
|   Connectors:  tflo-connect-kafka   tflo-connect-mqtt      |
|   Sinks:       tflo-sink-influx     tflo-arrow             |
|   Policy:      tflo-cel   tflo-rhai   tflo-rego             |
||
||
|   Catalogs:   tflo-ops (generic)  tflo-fintech (finance)  |
||
||
|   Engine:     tflo-core                                |
|                  graph + windowing + keyed exec            |
|                  timers + dedup + metrics                  |
|                  typed Absent + async contracts            |
||
||
|   State:      tflo-state-files     tflo-state-s3          |
|   Browser:    tflo-wasm                                   |
|                                                            |
+------------------------------------------------------------+
      

Where tflo fits

Composition with the engines you already operate — not substitution.

At the edge, mature engine in the center

tflo runs on sensor gateways and in browsers, emitting typed Signal events. Those signals flow into Flink / Esper / Kafka Streams for cross-source aggregation.

[sensor gateways] [browser dashboards] [embedded services]
       │                  │                    │
       └── tflo signal emission (typed Signal events) ──┐
                                                       │
                                            [Kafka / NATS / MQTT][Flink / Esper /
                                                 Kafka Streams]

Inside a host engine, per key

The host owns watermarks, exactly-once, scaling, savepoints. tflo runs inside a per-key slot (KeyedProcessFunction, DoFn, Processor) and contributes signal logic and typed Absent.

         [Flink job]
             │
     ┌───────┴────────┐
     │  KeyedProcess  │  ← Flink owns watermarks,
     │  Function      │     exactly-once, scaling
     │   ┌────────┐   │
     │   │ tflo   │   │  ← tflo owns signal logic,
     │   │ graph  │   │     typed Absent
     │   └────────┘   │
     └────────────────┘

Same code for batch and streaming

The same detectors that run in production also run against archived events. Iterator::tflo(...) works identically on Vec<Event> (backfill) and Stream<Event> (live) — deterministic output.

// Live
let live = stream.tflo(|t| { ... });

// Backfill — same closure
let back = vec.tflo(|t| { ... })
    .collect();

Where each lane wins

Composition, not competition — honest about where each tool fits.

Rows where tflo shows “—” are deliberate non-goals; for those, host tflo inside Flink / Beam and the host engine covers them.

Capability tflo Flink / Esper / KStreams Hand-rolled state
Runs in browser / WASM case-by-case
Runs on edge gateways under 1 GB RAM ✗ (JVM) case-by-case
Embeds in your Rust service, no sidecar
Typed missing-value reasons (not NaN)
Identical code for batch and streaming partial
Keyed execution, snapshot-safe state
Event-time timers, per-key heap
Built-in event detectors (cross, glitch, runt, pulse) partial
No JobManager / no separate state backend to operate
Streaming SQL for analyst self-service*
Cross-shard / global watermark, multi-shard joins*
Exactly-once via two-phase-commit sinks*
At-least-once + idempotent sinks + Deduplicator
Broad connector catalog (Debezium CDC, dozens of cloud sources)* Kafka / MQTT / Arrow

* Rows where tflo shows “—” are deliberately non-goals: tflo doesn't try to replace the host engine on these axes. Host tflo inside Flink / Beam / Kafka Streams and the host covers them — see positioning.

Everything you need

A complete toolkit for turning noisy events into clean lifecycle events.

Streaming-First

Built on Rust's Iterator and Stream traits. Every node works on live data without accumulating the full history in memory.
🔑

Keyed Execution

Process every entity — signal, device, host, source IP — independently. Each key gets its own window state, perfect for per-entity tracking.
💾

Checkpoint and Restore

Persist window state to files or S3 via tflo-state-files and tflo-state-s3. Crash recovery and fast restart without recomputing from the beginning of time.
📊

Lifecycle Events

The core pattern: noisy domain events in, clean lifecycle events out — appeared, persisted, changed, dropped out, crossed a threshold.
🎯

Event Detectors

Cross, hysteresis, glitch, runt, pulse, pulse-width, and window detectors. Composable primitives for building custom alerting logic.
🧩

Custom Runtime Nodes

Drop in your own logic with the Operator trait via Comp::custom_node — first-class alongside the built-ins.
🔧

Policy Filters

Express filter and alert rules in CEL, Rhai, or Rego via tflo-cel, tflo-rhai, and tflo-rego. Update a policy without recompiling.
🔄

Async Native

First-class Stream adapters with Tokio. Integrate directly into async pipelines with backpressure support.
📡

Kafka Integration

Connect directly to Kafka topics with tflo-connect-kafka. Ingest events and publish results with built-in serde support.

Irregular Data, No Resampling

Time-based windows work on naturally sparse data — detector hits, sensor readings, or events with irregular intervals. No padding, interpolation, or normalized time series required. Just timestamps and values.
📦

Operator Catalogs

The engine lives in tflo-core; operators live in tflo-ops (generic) and tflo-fintech (finance plugin). Domain catalogs evolve on their own cadence — finance is one among RF, observability, fraud, IoT.
🔀

Stream Composition

Merge multiple streams by timestamp, window-join two streams, batch by time windows, deduplicate by key, rate-limit output, or partition with predicates. Stream-processing framework features in a library.
💡

Signal Conditioning

Preprocess raw signals before analysis: DC removal, drifting baseline correction, range normalization, z-score standardization, and gain/offset calibration. Built for sensor and RF pipelines.

Event-Time Timers

Per-key timer heap with deterministic (fire_ts, seq) ordering; snapshot-safe serialization via postcard. The clean way to express dwell timers, watchdog timeouts, and absence detection.
🔐

Idempotent Sinks

Deduplicator primitive: process-local cache plus a durable per-sink window backed by AsyncStateStore. tflo's answer to exactly-once is at-least-once + idempotent, not 2PC.
📈

Pluggable Metrics

Metrics trait fires on keyed-step latency, timer fires, checkpoint commit and failure, late-record drops. No-op default; wire Prometheus / statsd / OTEL in one place.
🔗

Connector Ecosystem

Kafka (KafkaShardRouter with rebalance fencing), MQTT (BoundedSet for QoS-2 dedup), Influx line-protocol sink, and Arrow / Parquet for batch replay and archive.
🧠

Event-Pattern Matching

tflo-cep composes typed signals into multi-event domain signals — abandoned_cart, engaged_with_product, rage_click. Six-method closure API. Same engine runs in Rust and in the browser via tflo-cep-wasm.
🌐

Browser SDK

@tflo/browser-events: capture DOM events, derive typed signals via WASM, route to GA4 / first-party edge / your own sink. ~25 KB gzip. The engine has zero vendor knowledge.

Start in 5 lines

A declarative computation graph over any iterator or stream of events.

use tflo_core::prelude::*;
use tflo_ops::prelude::*;

// A noisy domain event — e.g. a sensor reading or detection confidence
#[derive(Clone)]
struct Detection { ts: i64, confidence: f64 }

// Smooth confidence and flag threshold crossings
let events = detections.into_iter()
    .tflo(|t| {
        t.timestamp(|d| d.ts);
        let confidence = t.prop(|d| d.confidence);
        let smoothed = confidence.sma(5_u64.secs());
        let threshold = t.constant(0.8);
        smoothed.cross(&threshold)
    })
    .collect::<Vec<_>>();

// Same code on a Vec<Detection> (backfill) or a Stream<Detection> (live)

Ready to model your events?

Add tflo-core and tflo-ops to your Cargo.toml and start building real-time event-processing pipelines today.