Checkpoints and deletions
Keep these three positions separate:
| Position | Owner | Purpose |
|---|---|---|
| Execution journal | Browserlayer | Resume completed steps within a run |
| Source checkpoint | Your sync | Continue fetching the website's dataset |
| Consumer cursor | Your app | Continue applying published changes |
Incremental publication
Inside a sync with a checkpoint schema such as z.object({ next: z.string() }):
const checkpoint = await ctx.getCheckpoint(); // null on the first run
// Fetch the next page through your connector using checkpoint?.next.
await ctx.publish("page", records, {
checkpoint: { next: nextPageToken },
deleted: deletedIds,
});records, nextPageToken and deletedIds come from the upstream page you fetched. Publication commits records, checkpoint and notification intent atomically.
Full snapshots
await ctx.publish("snapshot", records, {
checkpoint: null,
scan,
completeScan: true,
});Use one unique scan ID for every page in a snapshot. Generate it in a durable step. Only mark the final successful page completeScan: true.
| Case | Result |
|---|---|
| Complete snapshot omits an old record | Publish a deletion |
| Complete snapshot is empty | Delete all old records and publish an update notification |
| Partial scan fails | Already published batches remain; missing records are not deleted |
| Scan continues across runs | Carry its scan ID in the checkpoint |
Model changes need a new sync identity or an explicit development dataset reset. Renaming a sync starts a different stream.