A successful send() means K2 accepted all records atomically. The producer binding does not return offsets or a consumer acknowledgement. Our batch ID is assigned by this Worker, not by K2.
The log sits
between now
and next.
Producers and consumers should not have to arrive at the same time. K2 accepts ordered batches of bytes from the edge, retains them durably, and lets independent subscriptions advance on their own clocks.
One write. Two timelines.
The arrows below are the architecture; the receipt in the live lab is real. Subscription lanes only become live when a scoped K2 Consume credential is installed—no fake read-back.
Your browser
Chooses a fixed synthetic event lifecycle. No customer data or arbitrary messages.
POST /api/produceEdge Worker
Assigns stable event IDs, encodes JSON as bytes, and calls the K2 producer binding.
env.EVENTS.send(records)K2 durable log
Accepts or rejects the whole batch. K2 stores the ordered log on R2 internally.
bdayweek2026_k2_labThe Worker producer is ready. Checking whether real subscription readers are configured…
A real subscription requests a batch from K2's authenticated HTTP API, gets a five-minute lease, processes every record, and acknowledges the entire batch. A second subscription still has its own unread copy.
Put bytes on the stream.
Pick an example, inspect its exact three-record template, then produce it through a real Worker binding. Nothing here purchases, publishes, or triggers a security rule in another app.
Choose a lifecycle.
Loading K2 examples…
Waiting for the example catalog…
The Worker adds fresh event UUIDs, an application batch ID, and a producer timestamp when you send. Only the three predefined records can be written.
An atomic commit to K2.
One click creates one batch of three records. Either K2 accepts them all, or this page shows an error. A non-retryable error may have an unknown outcome, so the Worker does not retry on your behalf.
Inspect the actual accepted JSON records
No accepted batch yet.
Same events. Separate cursors.
Each lane uses its own real subscription—not a mirrored array from the producer response. If one reader acknowledges a batch, the other still has its own position. Public visitors share each subscription's cursor.
Operations reader
Leases up to 10 K2 records, validates their lab schema, and acknowledges the whole batch after processing.
Audit reader
Reads the same stream on an independent cursor. Duplicate event IDs must be handled idempotently.
Checking whether scoped K2 Consume credentials are configured. Nothing is simulated.
What the receipt does not tell you.
Durable acceptance, consumption, and processing are three different states. This lab labels them separately.
All or nothing.
K2 accepts every record in a batch together. It stores bytes; your application decides what they mean. A producer success is not proof any reader has consumed an event.
Duplicates can happen.
If a lease expires or a batch is released, K2 can deliver it again. Producer retries with uncertain outcomes can also create duplicates. Use the event ID as an idempotency key.
Read without deleting.
Each subscription receives the log independently. Acknowledging a batch advances only that subscription; the bytes stay in the stream until the one-day retention window ends.
No public stream endpoint.
HTTP production is disabled. Only the Worker binding can append, and this API permits small preset batches behind a rate limit. A K2 Consume token, when configured, stays in a Worker secret.
Readable primitives, real boundaries.
const records = events.map(event => ({
content: new TextEncoder().encode(JSON.stringify(event)),
headers: { "event-type": event.type }
}));
const result = await env.EVENTS.send(records);
if (!result.success) return failedBatch();The binding authenticates on behalf of the Worker; it receives bytes, not strings or browser tokens.
POST /subscriptions/:id/consume
→ batch_id + records + lease expiry
validate every record → process the batch
POST /subscriptions/:id/batches/:batch_id/ackThere is no read binding in this launch. Subscription HTTP requests use a server-side K2 Consume credential, never JavaScript delivered to your browser.