Build
Events & Sync
How Arke's event system works for real-time sync and change tracking.
Overview
Arke emits events when entities are created or updated. Events are stored in D1 and provide a rolling-window log for real-time client synchronization. This is a lightweight sync mechanism, not a permanent audit trail -- the tips table is the source of truth.
Events are ephemeral with a 30-day rolling window. For full sync, use snapshots.
Event Structure
Each event contains:
| Field | Type | Description |
|---|---|---|
id | number | Auto-increment event ID (use as cursor) |
pi | string | Entity ID that changed |
cid | string | New manifest CID |
ts | string | ISO timestamp of the event |
Example event:
{
"id": 12346,
"pi": "01KDETYWYWM0MJVKM8DK3AEXPY",
"cid": "bafyreibug443cnd4endcwinwttw3c3dzmcl2ikht64xzn5qg56bix3usfy",
"ts": "2025-01-15T12:00:01Z"
}Consuming Events
GET /eventsNo authentication required -- the events endpoint is public.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
cursor | number | Return events older than this ID (from previous response) |
limit | number | Maximum events to return (default: 100, max: 1000) |
network | string | Network to query: main or test (default: main) |
Response Format
{
"events": [
{ "id": 12346, "pi": "01KDE...", "cid": "bafyrei...", "ts": "2025-01-15T12:00:01Z" },
{ "id": 12345, "pi": "01KAB...", "cid": "bafyrei...", "ts": "2025-01-15T12:00:00Z" }
],
"has_more": true,
"cursor": 12345
}events: List of events, newest firsthas_more: Whether there are more (older) events availablecursor: Pass this as?cursor=to get older events
Sync Flow
The events endpoint uses cursor-based pagination for efficient synchronization:
- Initial fetch:
GET /events-- get the newest events, save the highestidas your high-water mark - Paginate backwards:
GET /events?cursor=X-- get older events untilhas_more=false - Poll for new:
GET /events-- if the newestid> your high-water mark, process new events
Example Sync Implementation
let highWaterMark = 0;
// Initial sync - get all events
async function initialSync() {
let cursor: number | undefined;
do {
const url = cursor ? `/events?cursor=${cursor}` : '/events';
const res = await fetch(url);
const data = await res.json();
// Update high-water mark from first batch
if (!cursor && data.events.length > 0) {
highWaterMark = data.events[0].id;
}
// Process events
for (const event of data.events) {
await processEvent(event);
}
cursor = data.has_more ? data.cursor : undefined;
} while (cursor);
}
// Poll for new events
async function pollForNew() {
const res = await fetch('/events');
const data = await res.json();
const newEvents = data.events.filter(e => e.id > highWaterMark);
if (newEvents.length > 0) {
highWaterMark = newEvents[0].id;
for (const event of newEvents) {
await processEvent(event);
}
}
}Event Pruning
Events are automatically pruned after 30 days via an hourly cron job. This keeps the events table lean while providing a sufficient window for client synchronization.