Repository navigation
fix(cloudflare): use timestamp-based ids to handle table recreation - #2190
Conversation
… recreated The idle alarm's deleteAll() and resetSchema's DROP TABLE reset the AUTOINCREMENT sequence, so new events got ids starting at 1 again. Replay uses `WHERE id > ?`, so a subscriber resuming with an id issued before the reset silently skipped every newer event stored since. Seed the sequence with the current time in microseconds whenever the table is created, so ids issued after a reset stay above earlier ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017T4x44m5ATzLkRn6JNgLr2
The previous fix seeded the id sequence with the current time, which only kept new ids above old ones while the old table averaged fewer than 1,000 events per millisecond. Give each events table a generation instead, stored in a meta table and changed whenever the table is recreated, and issue `<generation>-<sequence>` ids. On resume, an id from another generation replays every stored event, however the sequences compare, so no assumption about the event rate is left. Tables created before this change keep issuing plain ids, so ids already held by subscribers keep working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017T4x44m5ATzLkRn6JNgLr2
- Issue `<generation>-<sequence>` ids from every table, including tables created before generations existed (`0-<sequence>`). Plain ids already held by subscribers still parse as generation `0`, so one id format remains. - Declare the meta table `WITHOUT ROWID`, so its primary key needs no hidden index and each write touches one B-tree instead of two. - Tighten test assertions loosened by the earlier time-seed approach and drop redundant test steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017T4x44m5ATzLkRn6JNgLr2
Revert the generation-prefixed ids (e18a7f2, b7a396c) and keep the time-seeded AUTOINCREMENT sequence from 4bd6e5b, so resume ids stay plain integers compared with `WHERE id > ?` and no extra storage is needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017T4x44m5ATzLkRn6JNgLr2
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 94c99db | Commit Preview URL Branch Preview URL |
Oct 06 2026, 08:18 AM |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
Comparing Footnotes
|
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — reviewed the full PR head (all four commits, final state), including a local test run of both publisher suites and type:check.
- Time-seeded
AUTOINCREMENTsequence — when theeventstable is actually created (rowsWritten > 0),ensureSchemaAndCleanupnow seedssqlite_sequencewithDate.now() * 1000microseconds, so ids issued after recreation are always greater than ids issued before it. Plain integers are preserved, so the existingWHERE id > ?numeric comparison and the TEXT-cast read path are untouched. - Regression test —
resumes events stored after idle cleanup for an id issued before itfails without the seeding (replays 0instead of2) and passes with it, so it genuinely pins the reported bug. - Test adaptations — hardcoded ids (
'1','2','3') becameexpect.any(String)or computed relative to the first id, and the digit-boundary test now forcesUPDATE sqlite_sequence SET seq = 0to exercise numericORDER BYwith 9→10.
Verified locally: pnpm --filter @orpc/cloudflare exec vitest run src/publisher-object.test.ts src/publisher.test.ts → 34 passed; type:check passes; no stale references to the reverted generation/prefix:meta approach remain.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Summary
Fixes an issue where resuming from a message id after idle cleanup or schema reset could skip newer events. The problem occurs because SQLite's AUTOINCREMENT sequence restarts when a table is recreated, causing new ids to be lower than ids issued before the cleanup.
Changes
sqlite_sequencetoDate.now() * 1000(microseconds) when creating the events table, ensuring new ids always exceed previously issued ids'1','2') to useexpect.any(String)or compute relative to the first received idresumes events stored after idle cleanup for an id issued before itto verify the fix works correctlyresumes messages in numeric id ordertest to reset the sequence and test the digit boundary scenarioImplementation Details
The fix ensures that when a durable object's storage is cleaned up and the events table is recreated, the AUTOINCREMENT sequence starts at a microsecond timestamp rather than 1. This guarantees that any new events published after cleanup will have ids greater than ids issued before cleanup, making resume queries (
WHERE id > ?) work correctly.https://claude.ai/code/session_017T4x44m5ATzLkRn6JNgLr2