experimental-a2 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +1 -1
- package/dist/{actor-C7b-1arn.d.ts → actor-CqibLgjQ.d.ts} +15 -7
- package/dist/actor-CqibLgjQ.d.ts.map +1 -0
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-client.js +2 -2
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +5 -5
- package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
- package/dist/actor-shared-DFET5q16.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +54 -43
- package/dist/actor.js.map +1 -1
- package/dist/{ai-Cz3hqVhk.d.ts → ai-DAu77CDr.d.ts} +8 -8
- package/dist/{ai-Cz3hqVhk.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
- package/dist/{ai-mMiWul1T.js → ai-EEfKyc-z.js} +9 -9
- package/dist/ai-EEfKyc-z.js.map +1 -0
- package/dist/ai-client.d.ts +2 -2
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.d.ts +4 -2
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +214 -67
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
- package/dist/{ai-harness-CV6rXbB3.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
- package/dist/{ai-harness-Clbg5ei7.js → ai-harness-dQeJaR1t.js} +7 -3
- package/dist/ai-harness-dQeJaR1t.js.map +1 -0
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.js +3 -3
- package/dist/ai-pi.js.map +1 -1
- package/dist/ai-react.d.ts +5 -5
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +14 -17
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +86 -73
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BhHLoNKF.js → client-BQS4i_V9.js} +29 -6
- package/dist/client-BQS4i_V9.js.map +1 -0
- package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
- package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
- package/dist/contract-CWukt01d.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
- package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
- package/dist/{internal-CscSY7K_.js → internal-CvsrprnU.js} +40 -16
- package/dist/internal-CvsrprnU.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/otel.js +1 -1
- package/dist/otel.js.map +1 -1
- package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
- package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
- package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
- package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
- package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
- package/dist/runtime-consumer-CIeL33wj.js +12 -0
- package/dist/runtime-consumer-CIeL33wj.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +3 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +3 -3
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-qyb5aJJG.js → server-DtFrWxi3.js} +412 -116
- package/dist/server-DtFrWxi3.js.map +1 -0
- package/dist/{server-DYAATFuT.d.ts → server-qgez0Xny.d.ts} +108 -41
- package/dist/server-qgez0Xny.d.ts.map +1 -0
- package/dist/server.browser.js +1 -1
- package/dist/server.browser.js.map +1 -1
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
- package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
- package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
- package/dist/session-clear-wHd5GCpZ.js.map +1 -0
- package/dist/store-D0MhBvg6.d.ts +532 -0
- package/dist/store-D0MhBvg6.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-codec-DwrWlgQx.js +65 -0
- package/dist/store-codec-DwrWlgQx.js.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +406 -225
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-7WJV0YWP.js → store-polling-BZt2Rf3D.js} +2 -2
- package/dist/{store-polling-7WJV0YWP.js.map → store-polling-BZt2Rf3D.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +1598 -587
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-presence-polling-JuAy2Ck2.js +111 -0
- package/dist/store-presence-polling-JuAy2Ck2.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +4 -4
- package/dist/store-redis-notify-CsJw_hfD.js +3060 -0
- package/dist/store-redis-notify-CsJw_hfD.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +4 -5
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +527 -299
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-DrTyiIuo.js +57 -0
- package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
- package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
- package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
- package/dist/{wire-BKxH1vbP.js → wire-CV-Z7wz-.js} +6 -5
- package/dist/wire-CV-Z7wz-.js.map +1 -0
- package/docs/01-quickstart.mdx +21 -15
- package/docs/02-examples.mdx +1 -1
- package/docs/actors/01-introduction.mdx +13 -2
- package/docs/actors/02-concurrency.mdx +1 -1
- package/docs/agents/01-introduction.mdx +1 -1
- package/docs/agents/02-tools.mdx +12 -11
- package/docs/agents/03-conversations.mdx +36 -25
- package/docs/agents/04-harnesses.mdx +14 -7
- package/docs/client/03-presence.mdx +18 -14
- package/docs/contract/01-events.mdx +2 -2
- package/docs/contract/02-state.mdx +18 -14
- package/docs/contract/03-sessions.mdx +32 -25
- package/docs/index.mdx +29 -24
- package/docs/reference/01-core.mdx +7 -6
- package/docs/reference/02-server.mdx +250 -191
- package/docs/reference/04-ai.mdx +33 -23
- package/docs/reference/05-telemetry.mdx +27 -13
- package/docs/reference/06-errors.mdx +25 -19
- package/docs/reference/meta.ts +1 -1
- package/docs/schedulers/01-overview.mdx +8 -8
- package/docs/schedulers/02-vercel-queues.mdx +2 -2
- package/docs/schedulers/03-qstash.mdx +4 -4
- package/docs/server/01-consumers.mdx +418 -0
- package/docs/server/02-timers.mdx +31 -21
- package/docs/server/03-cancellation.mdx +52 -44
- package/docs/server/04-routes.mdx +4 -1
- package/docs/server/05-sql-projections.mdx +391 -0
- package/docs/server/06-guarantees.mdx +61 -51
- package/docs/stores/01-overview.mdx +3 -3
- package/docs/stores/02-redis.mdx +24 -10
- package/docs/stores/03-redis-http.mdx +8 -6
- package/docs/stores/04-postgres.mdx +65 -14
- package/docs/stores/05-sqlite.mdx +5 -3
- package/docs/upgrades/01-overview.mdx +31 -0
- package/docs/upgrades/78-0-22.mdx +115 -0
- package/docs/upgrades/79-0-21.mdx +165 -0
- package/docs/upgrades/80-0-20.mdx +43 -0
- package/docs/upgrades/81-0-19.mdx +33 -0
- package/docs/upgrades/82-0-18.mdx +33 -0
- package/docs/upgrades/83-0-17.mdx +69 -0
- package/docs/upgrades/meta.ts +3 -0
- package/examples/playground/DEPLOY.md +39 -7
- package/examples/playground/app/a0/chat.test.tsx +2 -2
- package/examples/playground/app/a0/sandbox-log.ts +17 -15
- package/examples/playground/app/a0/sandbox.ts +58 -13
- package/examples/playground/app/a0/server.ts +4 -4
- package/examples/playground/app/a2slack/a2slack.css +1067 -0
- package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
- package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
- package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
- package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
- package/examples/playground/app/a2slack/consumers/search.ts +37 -0
- package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
- package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
- package/examples/playground/app/a2slack/db/a2.ts +209 -0
- package/examples/playground/app/a2slack/db/index.ts +73 -0
- package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
- package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
- package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
- package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
- package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
- package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +41 -0
- package/examples/playground/app/a2slack/db/queries.ts +263 -0
- package/examples/playground/app/a2slack/db/schema.ts +145 -0
- package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
- package/examples/playground/app/a2slack/drills/harness.ts +244 -0
- package/examples/playground/app/a2slack/events/route.ts +36 -0
- package/examples/playground/app/a2slack/identity.ts +44 -0
- package/examples/playground/app/a2slack/inbox/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/page.tsx +101 -0
- package/examples/playground/app/a2slack/lag.ts +73 -0
- package/examples/playground/app/a2slack/layout.tsx +43 -0
- package/examples/playground/app/a2slack/messages/route.ts +17 -0
- package/examples/playground/app/a2slack/model.test.ts +208 -0
- package/examples/playground/app/a2slack/model.ts +369 -0
- package/examples/playground/app/a2slack/models.test.ts +72 -0
- package/examples/playground/app/a2slack/page.tsx +10 -0
- package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
- package/examples/playground/app/a2slack/search/route.ts +40 -0
- package/examples/playground/app/a2slack/search-index.ts +108 -0
- package/examples/playground/app/a2slack/server.test.ts +315 -0
- package/examples/playground/app/a2slack/server.ts +55 -0
- package/examples/playground/app/a2slack/sessions.ts +50 -0
- package/examples/playground/app/a2slack/thread/route.ts +8 -0
- package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
- package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
- package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
- package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
- package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
- package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
- package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
- package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
- package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
- package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
- package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
- package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
- package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
- package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
- package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
- package/examples/playground/app/agent/server.ts +3 -3
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
- package/examples/playground/app/chat/server.ts +45 -35
- package/examples/playground/app/components/demo-icons.tsx +7 -0
- package/examples/playground/app/components/demos.tsx +7 -0
- package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
- package/examples/playground/app/orders/page.tsx +1 -1
- package/examples/playground/app/orders/server.ts +49 -30
- package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
- package/examples/playground/app/recovery/page.tsx +4 -4
- package/examples/playground/app/recovery/server.ts +23 -32
- package/examples/playground/drizzle.config.ts +14 -0
- package/examples/playground/lib/store.ts +1 -1
- package/examples/playground/next.config.ts +6 -1
- package/examples/playground/package.json +12 -1
- package/examples/playground/scripts/migrate.mjs +31 -0
- package/examples/playground/vercel.json +9 -0
- package/examples/playground/vitest.drills.config.ts +32 -0
- package/package.json +5 -2
- package/sql/postgres/drizzle-schema.ts +209 -0
- package/sql/postgres/schema.sql +227 -67
- package/src/actor-shared.ts +10 -2
- package/src/actor.ts +101 -71
- package/src/ai-agent.ts +162 -111
- package/src/ai-control-server.ts +8 -8
- package/src/ai-fx.ts +324 -82
- package/src/ai-harness.ts +17 -2
- package/src/ai-pi.ts +2 -2
- package/src/ai-server.ts +2 -2
- package/src/ai.ts +7 -7
- package/src/client.ts +19 -2
- package/src/contract.ts +1 -1
- package/src/deterministic-id.ts +27 -4
- package/src/errors.ts +8 -5
- package/src/idempotent-replay.ts +0 -1
- package/src/index.ts +1 -1
- package/src/internal.ts +57 -16
- package/src/otel.ts +1 -1
- package/src/postgres-schema.ts +98 -57
- package/src/presence-recovery.ts +18 -11
- package/src/presence-tombstones.ts +52 -0
- package/src/push-envelope.ts +20 -0
- package/src/runtime-consumer.ts +10 -0
- package/src/scheduler-task.ts +2 -2
- package/src/scheduler-vercel.ts +1 -1
- package/src/server.browser.ts +1 -1
- package/src/server.ts +819 -218
- package/src/session-socket.ts +1 -1
- package/src/store-codec.ts +66 -0
- package/src/store-memory.ts +604 -286
- package/src/store-postgres.ts +2006 -857
- package/src/store-presence-polling.ts +59 -34
- package/src/store-redis-core.ts +1961 -1333
- package/src/store-redis-notify.ts +76 -26
- package/src/store-redis.ts +2 -3
- package/src/store-sqlite.ts +915 -516
- package/src/store.ts +241 -102
- package/src/telemetry.ts +2 -1
- package/src/wire.ts +1 -0
- package/dist/actor-C7b-1arn.d.ts.map +0 -1
- package/dist/actor-shared-t7w8xIKP.js.map +0 -1
- package/dist/ai-harness-Clbg5ei7.js.map +0 -1
- package/dist/ai-mMiWul1T.js.map +0 -1
- package/dist/client-BhHLoNKF.js.map +0 -1
- package/dist/contract-BYKASlCV.js.map +0 -1
- package/dist/idempotent-replay-JdgMQRRm.js +0 -21
- package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
- package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
- package/dist/internal-CscSY7K_.js.map +0 -1
- package/dist/server-DYAATFuT.d.ts.map +0 -1
- package/dist/server-qyb5aJJG.js.map +0 -1
- package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
- package/dist/session-clear-DGI4jTQ5.js.map +0 -1
- package/dist/store-CyLUy86v.d.ts +0 -404
- package/dist/store-CyLUy86v.d.ts.map +0 -1
- package/dist/store-codec-DTG0Ftek.js +0 -10
- package/dist/store-codec-DTG0Ftek.js.map +0 -1
- package/dist/store-presence-polling-B1ixeqIS.js +0 -94
- package/dist/store-presence-polling-B1ixeqIS.js.map +0 -1
- package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
- package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
- package/dist/stream-buffer-Cx7AAPGq.js +0 -23
- package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
- package/dist/wire-BKxH1vbP.js.map +0 -1
- package/docs/server/01-handlers.mdx +0 -227
package/dist/store-memory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-memory.js","names":[],"sources":["../src/store-memory.ts"],"sourcesContent":["/**\n * experimental-a2/store-memory — in-memory store backend (the test default).\n *\n * Implements the A2Store interface with zero dependencies. See\n * specs/a2-implementation.md §3 for the contract; the conformance suite\n * in test/conformance is the executable version of it.\n */\n\nimport type { PresencePatch } from './contract.ts'\nimport { A2Error, supersededAttemptError } from './errors.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport { nullProtoRecord } from './internal.ts'\nimport { CLEAR_EVENT_TYPE, sessionClearedError } from './session-clear.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type AppendEvent,\n type Clock,\n type Event,\n type EventCause,\n type IdSource,\n type PresenceRow,\n type StoredEvent,\n type StoreStateRead,\n} from './store.ts'\n\nexport type MemoryStoreOptions = {\n /** Injectable clock — every stored timestamp comes from here. */\n clock?: Clock\n /** Injectable id source for generated event ids. */\n ids?: IdSource\n}\n\ntype Row = {\n id: string\n type: string\n payload: unknown\n index: number\n sessionId: string\n createdAt: Date\n cause: EventCause | null\n lane: string | null\n processedAt: Date | null\n processedByAttempt: number | null\n returnedEventIds: string[] | null\n firstClaimedAt: Date | null\n lastClaimedAt: Date | null\n attemptCount: number\n claimHolder: string | null\n claimExpiresAt: Date | null\n failureCount: number\n lastFailedAt: Date | null\n lastFailedAttempt: number | null\n lastError: string | null\n failedAt: Date | null\n}\n\ntype PresenceEntry = {\n value: unknown\n seen: number\n at: Date\n expiresAt: Date\n}\n\ntype LaneQueue = { rows: Row[]; head: number }\ntype SessionDispatch = {\n ready: Map<number, Row>\n lanes: Map<string, LaneQueue>\n}\n\n/** Contiguous rows; `reclaim` removes from the front below `clearIndex`. */\ntype SessionLog = {\n rows: Row[]\n /** Index of the event before `rows[0]`. */\n offset: number\n /** Latest clear marker, or 0. */\n clearIndex: number\n}\n\nconst RECLAIM_BATCH = 256\n\nconst headOf = (log: SessionLog | undefined): number =>\n log ? log.offset + log.rows.length : 0\n\nconst toStored = (row: Row): StoredEvent => ({\n id: row.id,\n type: row.type,\n payload: structuredClone(row.payload),\n index: row.index,\n sessionId: row.sessionId,\n createdAt: new Date(row.createdAt),\n cause: row.cause ? { ...row.cause } : null,\n lane: row.lane,\n processedAt: row.processedAt ? new Date(row.processedAt) : null,\n processedByAttempt: row.processedByAttempt,\n returnedEventIds: row.returnedEventIds ? [...row.returnedEventIds] : null,\n firstClaimedAt: row.firstClaimedAt ? new Date(row.firstClaimedAt) : null,\n lastClaimedAt: row.lastClaimedAt ? new Date(row.lastClaimedAt) : null,\n attemptCount: row.attemptCount,\n claimHolder: row.claimHolder,\n claimExpiresAt: row.claimExpiresAt ? new Date(row.claimExpiresAt) : null,\n failureCount: row.failureCount,\n lastFailedAt: row.lastFailedAt ? new Date(row.lastFailedAt) : null,\n lastFailedAttempt: row.lastFailedAttempt,\n lastError: row.lastError,\n failedAt: row.failedAt ? new Date(row.failedAt) : null,\n})\n\nconst toEvent = (row: Row): Event => ({\n id: row.id,\n type: row.type,\n payload: structuredClone(row.payload),\n index: row.index,\n sessionId: row.sessionId,\n createdAt: new Date(row.createdAt),\n})\n\nconst snapshotKey = (sessionId: string, reducerName: string): string =>\n `${sessionId}\\u0000${reducerName}`\n\nconst eventPinKey = (sessionId: string, index: number): string =>\n `${sessionId}\\u0000${index}`\n\nexport function memory(options: MemoryStoreOptions = {}): A2Store {\n const clock = options.clock ?? SYSTEM_CLOCK\n const generateId = options.ids ?? RANDOM_IDS\n\n const sessions = new Map<string, SessionLog>()\n const dispatch = new Map<string, SessionDispatch>()\n /** Global unique index on event ids, like the SQL schema's. */\n const byEventId = new Map<string, Row>()\n /** Keyed by `${sessionId}\\u0000${reducerName}` — a pure cache. */\n const snapshots = new Map<\n string,\n { index: number; state: unknown; updatedAt: Date }\n >()\n const historicalSnapshots = new Map<\n string,\n { index: number; state: unknown; updatedAt: Date }\n >()\n const snapshotPins = new Map<string, Set<string>>()\n const snapshotPinCounts = new Map<string, number>()\n const streamSubscribers = new Map<string, Set<(row: Row) => void>>()\n /** ns → participant → field → latest surviving write. */\n const presenceRows = new Map<\n string,\n Map<string, Map<string, PresenceEntry>>\n >()\n const presenceSubscribers = new Map<\n string,\n Set<(patch: PresencePatch) => void>\n >()\n\n const notifyStreams = (sessionId: string, rows: Row[]): void => {\n const subs = streamSubscribers.get(sessionId)\n if (!subs) return\n for (const listener of subs) {\n for (const row of rows) listener(row)\n }\n }\n\n const logOf = (sessionId: string): SessionLog => {\n let log = sessions.get(sessionId)\n if (!log) {\n log = { rows: [], offset: 0, clearIndex: 0 }\n sessions.set(sessionId, log)\n }\n return log\n }\n\n const rowAt = (sessionId: string, index: number): Row | undefined => {\n const log = sessions.get(sessionId)\n return log && index > log.offset\n ? log.rows[index - log.offset - 1]\n : undefined\n }\n\n const clearIndexOf = (sessionId: string): number =>\n sessions.get(sessionId)?.clearIndex ?? 0\n\n /** The row owning `id`, ignoring this session's rows below its clear. */\n const ownerOf = (sessionId: string, id: string): Row | undefined => {\n const row = byEventId.get(id)\n return row?.sessionId === sessionId && row.index < clearIndexOf(sessionId)\n ? undefined\n : row\n }\n\n const checkpointKey = (\n sessionId: string,\n reducerName: string,\n index: number,\n ): string => `${snapshotKey(sessionId, reducerName)}\\u0000${index}`\n\n const releaseSnapshotPins = (sessionId: string, eventIndex: number): void => {\n const pinKey = eventPinKey(sessionId, eventIndex)\n const checkpoints = snapshotPins.get(pinKey)\n if (!checkpoints) return\n for (const key of checkpoints) {\n const next = (snapshotPinCounts.get(key) ?? 1) - 1\n if (next === 0) {\n snapshotPinCounts.delete(key)\n historicalSnapshots.delete(key)\n } else {\n snapshotPinCounts.set(key, next)\n }\n }\n snapshotPins.delete(pinKey)\n }\n\n const dispatchOf = (sessionId: string): SessionDispatch => {\n let state = dispatch.get(sessionId)\n if (!state) {\n state = { ready: new Map(), lanes: new Map() }\n dispatch.set(sessionId, state)\n }\n return state\n }\n\n const enqueue = (sessionId: string, row: Row): void => {\n if (row.processedAt !== null) return\n const state = dispatchOf(sessionId)\n if (row.lane === null) {\n state.ready.set(row.index, row)\n return\n }\n let queue = state.lanes.get(row.lane)\n if (!queue) {\n queue = { rows: [], head: 0 }\n state.lanes.set(row.lane, queue)\n }\n queue.rows.push(row)\n if (queue.rows.length === 1) state.ready.set(row.index, row)\n }\n\n const settle = (sessionId: string, row: Row): void => {\n const state = dispatchOf(sessionId)\n state.ready.delete(row.index)\n if (row.lane === null) return\n const queue = state.lanes.get(row.lane)!\n if (queue.rows[queue.head] !== row) return\n queue.head += 1\n if (queue.head === queue.rows.length) {\n state.lanes.delete(row.lane)\n return\n }\n const next = queue.rows[queue.head]!\n if (next.failedAt === null) state.ready.set(next.index, next)\n }\n\n const find = (sessionId: string, index: number): Row => {\n const row = rowAt(sessionId, index)\n if (!row) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n return row\n }\n\n const hasPending = (sessionId: string): boolean => {\n const log = sessions.get(sessionId)\n return (log?.rows ?? []).some(\n (row) => row.index > log!.clearIndex && row.processedAt === null,\n )\n }\n\n const appendResult = (sessionId: string, events: StoredEvent[]) => ({\n events,\n hasPending: hasPending(sessionId),\n })\n\n /** The attempt-currency fence shared by handler appends and clears. */\n const assertCurrentCause = (sessionId: string, cause: EventCause): void => {\n if (cause.index <= clearIndexOf(sessionId)) {\n throw supersededAttemptError(sessionId, cause)\n }\n const parent = find(sessionId, cause.index)\n if (parent.attemptCount !== cause.attempt || parent.failedAt !== null) {\n throw supersededAttemptError(sessionId, cause)\n }\n }\n\n const claim = ({\n sessionId,\n row,\n holder,\n now,\n expiresAt,\n maxFailures,\n }: {\n sessionId: string\n row: Row\n holder: string\n now: Date\n expiresAt: Date\n maxFailures: number | undefined\n }): StoredEvent => {\n // A claim still held here lapsed: its attempt never finished.\n if (row.claimHolder !== null) {\n row.failureCount += 1\n if (maxFailures !== undefined && row.failureCount >= maxFailures) {\n // Advancing the attempt fences the lapsed one; nothing runs.\n row.attemptCount += 1\n row.failedAt = new Date(now)\n row.claimHolder = null\n row.claimExpiresAt = null\n dispatchOf(sessionId).ready.delete(row.index)\n releaseSnapshotPins(sessionId, row.index)\n return toStored(row)\n }\n }\n row.attemptCount += 1\n row.firstClaimedAt ??= new Date(now)\n row.lastClaimedAt = new Date(now)\n row.claimHolder = holder\n row.claimExpiresAt = new Date(expiresAt)\n return toStored(row)\n }\n\n const checkBatchIds = (\n sessionId: string,\n events: readonly (AppendEvent & { id?: string })[],\n ): Row[] => {\n const supplied = events.filter(\n (candidate): candidate is AppendEvent & { id: string } =>\n candidate.id !== undefined,\n )\n const suppliedIds = new Set(supplied.map((candidate) => candidate.id))\n if (suppliedIds.size !== supplied.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'batch contains the same event id more than once',\n )\n }\n const existing = supplied\n .map((candidate) => ownerOf(sessionId, candidate.id))\n .filter((row): row is Row => row !== undefined)\n const foreign = existing.find((row) => row.sessionId !== sessionId)\n if (foreign) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `event id '${foreign.id}' already exists in another session`,\n )\n }\n return existing\n }\n\n const insert = (\n sessionId: string,\n events: readonly AppendEvent[],\n now: Date,\n cause?: EventCause,\n ): Row[] => {\n const log = logOf(sessionId)\n const base = headOf(log)\n const inserted = events.map((candidate, offset): Row => ({\n id: candidate.id ?? generateId(),\n type: candidate.type,\n payload: structuredClone(candidate.payload),\n index: base + 1 + offset,\n sessionId,\n createdAt: new Date(now),\n cause: cause\n ? { ...cause }\n : candidate.cause\n ? { ...candidate.cause }\n : null,\n lane: candidate.lane ?? null,\n processedAt: candidate.settled ? new Date(now) : null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n claimHolder: null,\n claimExpiresAt: null,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n }))\n for (const row of inserted) {\n if (ownerOf(sessionId, row.id)) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `event id '${row.id}' already exists`,\n )\n }\n }\n for (const row of inserted) {\n log.rows.push(row)\n byEventId.set(row.id, row)\n enqueue(sessionId, row)\n }\n return inserted\n }\n\n /** Every cached fold of this session below its clear, head or historical. */\n const dropCheckpointsBelow = (\n sessionId: string,\n clearIndex: number,\n ): void => {\n const prefix = `${sessionId}\\u0000`\n for (const [key, snapshot] of snapshots) {\n if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue\n snapshots.delete(key)\n snapshotPinCounts.delete(`${key}\\u0000${snapshot.index}`)\n }\n for (const [key, snapshot] of historicalSnapshots) {\n if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue\n historicalSnapshots.delete(key)\n snapshotPinCounts.delete(key)\n }\n }\n\n const readStateOf = (\n sessionId: string,\n reducerName: string,\n throughIndex?: number,\n snapshotThroughIndex?: number,\n ): StoreStateRead => {\n const key = snapshotKey(sessionId, reducerName)\n const clearIndex = clearIndexOf(sessionId)\n const snapshotFrontier = snapshotThroughIndex ?? throughIndex\n const stored = snapshots.get(key)\n const head = stored && stored.index >= clearIndex ? stored : undefined\n let candidate =\n head && (snapshotFrontier === undefined || head.index <= snapshotFrontier)\n ? head\n : null\n if (snapshotFrontier !== undefined) {\n const prefix = `${key}\\u0000`\n for (const [historicalKey, snapshot] of historicalSnapshots) {\n if (\n historicalKey.startsWith(prefix) &&\n snapshot.index >= clearIndex &&\n snapshot.index <= snapshotFrontier &&\n (!candidate || snapshot.index > candidate.index)\n ) {\n candidate = snapshot\n }\n }\n }\n const snapshot = candidate\n ? { index: candidate.index, state: structuredClone(candidate.state) }\n : null\n const afterIndex = Math.max(snapshot?.index ?? 0, clearIndex - 1)\n return {\n headIndex: head?.index ?? null,\n snapshot,\n events: (sessions.get(sessionId)?.rows ?? [])\n .filter(\n (row) =>\n row.index > afterIndex &&\n (throughIndex === undefined || row.index <= throughIndex),\n )\n .map(toEvent),\n ...(clearIndex > 0 ? { clearIndex } : {}),\n }\n }\n\n return {\n async append(sessionId, events, appendOptions) {\n if (events.length === 0) return appendResult(sessionId, [])\n\n // Batch-scoped idempotency (a2-implementation.md §3): all ids\n // already present → lost-ack retry, return the original rows;\n // some present → the caller mixed sent and fresh events.\n const existing = checkBatchIds(sessionId, events)\n if (existing.length > 0) {\n if (existing.length === events.length) {\n return appendResult(\n sessionId,\n idempotentReplay(events, existing.map(toStored)),\n )\n }\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `batch mixes ${existing.length} already-appended and ${events.length - existing.length} fresh events`,\n )\n }\n\n const originIndex = appendOptions?.originIndex\n if (originIndex !== undefined && originIndex <= clearIndexOf(sessionId)) {\n throw sessionClearedError(\n `scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,\n )\n }\n // Attempt-currency fence: a fresh handler append commits only while\n // its causal attempt is still the parent's latest (store.ts `append`).\n for (const event of events) {\n if (event.cause) assertCurrentCause(sessionId, event.cause)\n }\n\n const now = clock.now()\n const inserted = insert(sessionId, events, now)\n notifyStreams(sessionId, inserted)\n return appendResult(sessionId, inserted.map(toStored))\n },\n\n async clear({ sessionId, ifLastIndex, cause }) {\n if (cause) assertCurrentCause(sessionId, cause)\n const log = sessions.get(sessionId)\n const head = headOf(log)\n const clearIndex = log?.clearIndex ?? 0\n if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {\n return { cleared: true, index: head, clearIndex }\n }\n if (ifLastIndex !== undefined && ifLastIndex !== head) {\n return { cleared: false, index: head, clearIndex }\n }\n if (head === 0 || clearIndex === head) {\n return { cleared: true, index: head, clearIndex }\n }\n const [marker] = insert(\n sessionId,\n [{ type: CLEAR_EVENT_TYPE, payload: null, settled: true }],\n clock.now(),\n )\n logOf(sessionId).clearIndex = marker!.index\n // Every row is at or below the marker, so no dispatch state survives.\n dispatch.delete(sessionId)\n notifyStreams(sessionId, [marker!])\n return { cleared: true, index: marker!.index, clearIndex: marker!.index }\n },\n\n async reclaim(sessionId) {\n const log = sessions.get(sessionId)\n if (!log || log.offset >= log.clearIndex - 1) {\n return { deleted: 0, done: true }\n }\n const removed = log.rows.splice(\n 0,\n Math.min(RECLAIM_BATCH, log.clearIndex - 1 - log.offset),\n )\n log.offset += removed.length\n for (const row of removed) {\n // A reused id belongs to its new row now.\n if (byEventId.get(row.id) === row) byEventId.delete(row.id)\n releaseSnapshotPins(sessionId, row.index)\n }\n const done = log.offset === log.clearIndex - 1\n // Writes below the marker are dropped, so one sweep at the end suffices.\n if (done) dropCheckpointsBelow(sessionId, log.clearIndex)\n return { deleted: removed.length, done }\n },\n\n async read(sessionId, opts) {\n const log = sessions.get(sessionId)\n if (!log) return []\n const head = headOf(log)\n const after = Math.max(\n opts?.afterIndex ?? 0,\n log.clearIndex - 1,\n log.offset,\n )\n const through = Math.min(opts?.throughIndex ?? head, head)\n if (through <= after) return []\n return log.rows\n .slice(after - log.offset, through - log.offset)\n .map(toStored)\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n excludeIndexes,\n maxFailures,\n }) {\n const claimedAt = clock.now()\n const now = claimedAt.getTime()\n const expiresAt = new Date(expiresAtMs ?? now + ttlMs)\n const excluded = new Set(excludeIndexes ?? [])\n const eligible: Row[] = []\n let retryAt: Date | null = null\n\n for (const row of dispatchOf(sessionId).ready.values()) {\n if (row.failedAt !== null || excluded.has(row.index)) continue\n if (row.claimExpiresAt && row.claimExpiresAt.getTime() > now) {\n if (!retryAt || row.claimExpiresAt < retryAt) {\n retryAt = new Date(row.claimExpiresAt)\n }\n continue\n }\n eligible.push(row)\n }\n\n if (eligible.length === 0) {\n return retryAt ? { outcome: 'busy', retryAt } : { outcome: 'settled' }\n }\n eligible.sort((a, b) => a.index - b.index)\n const clearIndex = clearIndexOf(sessionId)\n return {\n outcome: 'claimed',\n events: eligible.map((row) => {\n const claimed = claim({\n sessionId,\n row,\n holder,\n now: claimedAt,\n expiresAt,\n maxFailures,\n })\n if (clearIndex > 0) claimed.clearIndex = clearIndex\n return claimed\n }),\n }\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n const now = clock.now()\n const expiresAt = new Date(expiresAtMs ?? now.getTime() + ttlMs)\n const clearIndex = clearIndexOf(sessionId)\n const renewed: number[] = []\n const superseded: number[] = []\n for (const { index, attempt } of claims.toSorted(\n (a, b) => a.index - b.index,\n )) {\n if (index <= clearIndex) {\n superseded.push(index)\n continue\n }\n const row = rowAt(sessionId, index)\n if (!row) continue\n if (row.attemptCount > attempt) {\n superseded.push(row.index)\n continue\n }\n if (\n row.processedAt === null &&\n row.failedAt === null &&\n row.claimHolder === holder &&\n row.claimExpiresAt !== null &&\n row.claimExpiresAt > now\n ) {\n row.claimExpiresAt = new Date(expiresAt)\n renewed.push(row.index)\n }\n }\n return { renewed, superseded }\n },\n\n async completeAttempt({ sessionId, index, attempt, events }) {\n if (index <= clearIndexOf(sessionId)) return { outcome: 'superseded' }\n const parent = find(sessionId, index)\n const cause = { index, attempt }\n if (parent.processedAt !== null) {\n if (parent.processedByAttempt !== attempt) {\n return { outcome: 'superseded' }\n }\n const requestedIds = events.map((event) => event.id)\n if (\n parent.returnedEventIds === null ||\n parent.returnedEventIds.length !== requestedIds.length ||\n parent.returnedEventIds.some(\n (id, offset) => id !== requestedIds[offset],\n )\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'completed attempt does not match the returned event batch',\n )\n }\n const existing = checkBatchIds(sessionId, events)\n if (\n existing.length !== events.length ||\n existing.some(\n (row) =>\n row.cause?.index !== index || row.cause.attempt !== attempt,\n )\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'completed attempt does not match the returned event batch',\n )\n }\n return {\n outcome: 'completed',\n events: idempotentReplay(events, existing.map(toStored)),\n }\n }\n if (\n parent.attemptCount !== attempt ||\n parent.failedAt !== null ||\n parent.claimHolder === null\n ) {\n return { outcome: 'superseded' }\n }\n\n const existing = checkBatchIds(sessionId, events)\n if (existing.length > 0) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains already-appended ids',\n )\n }\n const now = clock.now()\n const inserted = insert(sessionId, events, now, cause)\n parent.processedAt = new Date(now)\n parent.processedByAttempt = attempt\n parent.returnedEventIds = events.map((event) => event.id)\n parent.claimHolder = null\n parent.claimExpiresAt = null\n settle(sessionId, parent)\n releaseSnapshotPins(sessionId, index)\n notifyStreams(sessionId, inserted)\n return { outcome: 'completed', events: inserted.map(toStored) }\n },\n\n async failAttempt({\n sessionId,\n index,\n attempt,\n error,\n maxFailures,\n retryAfterMs,\n }) {\n if (index <= clearIndexOf(sessionId)) {\n return { outcome: 'superseded', failureCount: 0 }\n }\n const row = find(sessionId, index)\n if (\n row.processedAt ||\n row.attemptCount !== attempt ||\n (row.claimHolder === null && row.lastFailedAttempt !== attempt)\n ) {\n return { outcome: 'superseded', failureCount: row.failureCount }\n }\n if (row.lastFailedAttempt === attempt && row.claimHolder === null) {\n return {\n outcome: row.failedAt ? 'dead_lettered' : 'failed',\n failureCount: row.failureCount,\n }\n }\n if (row.failedAt) {\n return { outcome: 'dead_lettered', failureCount: row.failureCount }\n }\n row.failureCount += 1\n row.lastError = error\n const failedAt = clock.now()\n row.lastFailedAt = new Date(failedAt)\n row.lastFailedAttempt = attempt\n row.claimHolder = null\n row.claimExpiresAt =\n retryAfterMs === undefined || row.failureCount >= maxFailures\n ? null\n : new Date(failedAt.getTime() + retryAfterMs)\n if (row.failureCount >= maxFailures) {\n row.failedAt = new Date(failedAt)\n dispatchOf(sessionId).ready.delete(row.index)\n releaseSnapshotPins(sessionId, index)\n return {\n outcome: 'dead_lettered',\n failureCount: row.failureCount,\n }\n }\n return { outcome: 'failed', failureCount: row.failureCount }\n },\n\n async deadLetters(sessionId) {\n const log = sessions.get(sessionId)\n return (log?.rows ?? [])\n .filter(\n (row) =>\n row.index > log!.clearIndex &&\n row.processedAt === null &&\n row.failedAt !== null,\n )\n .map(toStored)\n },\n\n async redrive({ sessionId, index, action }) {\n const row =\n index > clearIndexOf(sessionId) ? rowAt(sessionId, index) : undefined\n if (!row || row.processedAt !== null || row.failedAt === null) {\n return { redriven: false, hasPending: hasPending(sessionId) }\n }\n if (action === 'skip') {\n row.processedAt = clock.now()\n settle(sessionId, row)\n } else {\n row.failedAt = null\n row.failureCount = 0\n const state = dispatchOf(sessionId)\n const queue = row.lane === null ? undefined : state.lanes.get(row.lane)\n if (row.lane === null || queue?.rows[queue.head] === row) {\n state.ready.set(row.index, row)\n }\n }\n return { redriven: true, hasPending: hasPending(sessionId) }\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return readStateOf(\n sessionId,\n reducerName,\n stateOptions?.throughIndex,\n stateOptions?.snapshotThroughIndex,\n )\n },\n\n async readStates(requests, reducerName) {\n return requests.map((request) =>\n readStateOf(\n request.sessionId,\n reducerName,\n request.throughIndex,\n request.snapshotThroughIndex,\n ),\n )\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n const key = snapshotKey(sessionId, reducerName)\n const clearIndex = clearIndexOf(sessionId)\n for (const write of writes.toSorted((a, b) => a.index - b.index)) {\n if (write.index < clearIndex) continue\n const checkpoint = checkpointKey(sessionId, reducerName, write.index)\n for (const eventIndex of write.pinEventIndexes ?? []) {\n const event = rowAt(sessionId, eventIndex)\n if (\n !event ||\n event.index <= clearIndex ||\n event.processedAt !== null ||\n event.failedAt !== null\n ) {\n continue\n }\n const pinKey = eventPinKey(sessionId, eventIndex)\n let pinned = snapshotPins.get(pinKey)\n if (!pinned) {\n pinned = new Set()\n snapshotPins.set(pinKey, pinned)\n }\n if (!pinned.has(checkpoint)) {\n pinned.add(checkpoint)\n snapshotPinCounts.set(\n checkpoint,\n (snapshotPinCounts.get(checkpoint) ?? 0) + 1,\n )\n }\n }\n\n const current = snapshots.get(key)\n if (!current || write.index >= current.index) {\n if (\n current &&\n write.index > current.index &&\n current.index >= clearIndex\n ) {\n const currentCheckpoint = checkpointKey(\n sessionId,\n reducerName,\n current.index,\n )\n if ((snapshotPinCounts.get(currentCheckpoint) ?? 0) > 0) {\n historicalSnapshots.set(currentCheckpoint, {\n index: current.index,\n state: structuredClone(current.state),\n updatedAt: new Date(current.updatedAt),\n })\n }\n }\n snapshots.set(key, {\n index: write.index,\n state: structuredClone(write.state),\n updatedAt: clock.now(),\n })\n historicalSnapshots.delete(checkpoint)\n } else if (\n write.index < current.index &&\n (snapshotPinCounts.get(checkpoint) ?? 0) > 0\n ) {\n historicalSnapshots.set(checkpoint, {\n index: write.index,\n state: structuredClone(write.state),\n updatedAt: clock.now(),\n })\n }\n }\n },\n\n presence: {\n async set(ns, participant, values, meta) {\n let participants = presenceRows.get(ns)\n if (!participants) {\n participants = new Map()\n presenceRows.set(ns, participants)\n }\n let fields = participants.get(participant)\n if (!fields) {\n fields = new Map()\n participants.set(participant, fields)\n }\n\n // Expiry anchors on the storage's own clock — the sender's\n // `at` orders writes but never extends or shortens a lifetime.\n const expiresAtMs = clock.now().getTime() + meta.ttlMs\n // Field-keyed and caller-named — null-prototype, like every\n // presence map (see `nullProtoRecord`).\n const applied: Record<string, unknown> = nullProtoRecord()\n let appliedCount = 0\n for (const [field, value] of Object.entries(values)) {\n // Field-wise LWW by `at`; ties go to the incoming write, so\n // same-stamp sets keep set-then-read intuition.\n const existing = fields.get(field)\n if (existing && existing.at > meta.at) continue\n if (value === null) {\n fields.delete(field)\n } else {\n fields.set(field, {\n value: structuredClone(value),\n seen: meta.seen,\n at: new Date(meta.at),\n expiresAt: new Date(expiresAtMs),\n })\n }\n applied[field] = structuredClone(value)\n appliedCount += 1\n }\n if (fields.size === 0) participants.delete(participant)\n if (participants.size === 0) presenceRows.delete(ns)\n\n // Only applied fields broadcast — a losing write repaints nothing,\n // so subscribers stay consistent with what read() returns.\n if (appliedCount === 0) return\n const subs = presenceSubscribers.get(ns)\n if (!subs) return\n const patch: PresencePatch = {\n participant,\n values: applied,\n seen: meta.seen,\n at: new Date(meta.at),\n }\n for (const listener of subs) listener(patch)\n },\n\n async read(ns) {\n const participants = presenceRows.get(ns)\n if (!participants) return []\n const now = clock.now()\n const rows: PresenceRow[] = []\n for (const [participant, fields] of participants) {\n for (const [field, entry] of fields) {\n // Expiry is enforced lazily on read — no timers; a silent\n // participant's rows vanish the next time anyone looks.\n if (entry.expiresAt <= now) {\n fields.delete(field)\n continue\n }\n rows.push({\n participant,\n field,\n value: structuredClone(entry.value),\n seen: entry.seen,\n at: new Date(entry.at),\n expiresAt: new Date(entry.expiresAt),\n })\n }\n if (fields.size === 0) participants.delete(participant)\n }\n if (participants.size === 0) presenceRows.delete(ns)\n return rows\n },\n\n subscribe(ns, onPatch) {\n let subs = presenceSubscribers.get(ns)\n if (!subs) {\n subs = new Set()\n presenceSubscribers.set(ns, subs)\n }\n subs.add(onPatch)\n return () => {\n subs.delete(onPatch)\n if (subs.size === 0) presenceSubscribers.delete(ns)\n }\n },\n },\n\n stream(sessionId, opts) {\n const startAfter = opts?.startAfter ?? 0\n return {\n [Symbol.asyncIterator](): AsyncIterator<Event> {\n let last = Math.max(startAfter, clearIndexOf(sessionId) - 1)\n let wake: (() => void) | null = null\n let closed = false\n\n const onRow = (): void => {\n wake?.()\n }\n let subs = streamSubscribers.get(sessionId)\n if (!subs) {\n subs = new Set()\n streamSubscribers.set(sessionId, subs)\n }\n subs.add(onRow)\n const stop = (): void => {\n if (closed) return\n closed = true\n subs.delete(onRow)\n if (subs.size === 0) streamSubscribers.delete(sessionId)\n wake?.()\n }\n\n return {\n async next(): Promise<IteratorResult<Event>> {\n try {\n for (;;) {\n if (closed) return { value: undefined, done: true }\n const log = sessions.get(sessionId)\n // Rows reclaimed behind a running stream are skipped.\n const row = log?.rows[Math.max(0, last - log.offset)]\n if (row) {\n const value = toStored(row)\n last = row.index\n return { value, done: false }\n }\n // oxlint-disable-next-line no-await-in-loop -- wait-for-wake\n await new Promise<void>((resolve) => {\n wake = resolve\n })\n wake = null\n }\n } catch (error) {\n stop()\n throw error\n }\n },\n async return(): Promise<IteratorResult<Event>> {\n stop()\n return { value: undefined, done: true }\n },\n }\n },\n }\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"store-memory.js","names":[],"sources":["../src/store-memory.ts"],"sourcesContent":["/**\n * experimental-a2/store-memory — in-memory store backend (the test default).\n *\n * Implements the A2Store interface with zero dependencies. See\n * specs/a2-implementation.md §3 for the contract; the conformance suite\n * in test/conformance is the executable version of it.\n */\n\nimport type { PresencePatch } from './contract.ts'\nimport { A2Error, supersededAttemptError } from './errors.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport { nullProtoRecord } from './internal.ts'\nimport { CLEAR_EVENT_TYPE, sessionClearedError } from './session-clear.ts'\nimport { idleLogCursor } from './store-codec.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type AppendEvent,\n type Clock,\n type Event,\n type EventCause,\n type HeadDispatch,\n type HeadEvent,\n type HeadRef,\n type IdSource,\n type PresenceRow,\n type StoreConsumer,\n type StoredCursor,\n type StoredEvent,\n type StoreStateRead,\n} from './store.ts'\n\nexport type MemoryStoreOptions = {\n /** Injectable clock — every stored timestamp comes from here. */\n clock?: Clock\n /** Injectable id source for generated event ids. */\n ids?: IdSource\n}\n\ntype Row = {\n id: string\n type: string\n payload: unknown\n index: number\n sessionId: string\n createdAt: Date\n cause: EventCause | null\n}\n\ntype Dispatch = Omit<HeadDispatch, 'consumer' | 'lane'>\n\ntype Completion = { attempt: number; returnedEventIds: string[] }\n\n/**\n * One (consumer, lane) record. A log cursor reads the whole log by type, a\n * lane cursor queues its lane's events, and a concurrent cursor (lane null)\n * covers one event.\n */\ntype Cursor = {\n consumer: string\n lane: string | null\n kind: 'log' | 'lane' | 'event'\n position: number\n head: number | null\n dispatch: Dispatch\n /** Lane cursors: the lane's event indexes in log order. */\n queue: number[]\n /** Lane cursors: where the head sits in `queue`. */\n queued: number\n /** Completions by head index; a log cursor keeps only its latest. */\n completions: Map<number, Completion>\n /** Log cursors: the types it takes, null for every type. */\n types: readonly string[] | null\n}\n\n/** A null `value` is a tombstone: it keeps a delete's `at` until it expires. */\ntype PresenceEntry = {\n value: unknown\n seen: number\n at: Date\n expiresAt: Date\n}\n\n/** Contiguous rows; `reclaim` removes from the front below `clearIndex`. */\ntype SessionLog = {\n rows: Row[]\n /** Index of the event before `rows[0]`. */\n offset: number\n /** Latest clear marker, or 0. */\n clearIndex: number\n /** The session's unique index on event ids, like the SQL schemas'. */\n byEventId: Map<string, Row>\n}\n\nconst RECLAIM_BATCH = 256\n\nconst headOf = (log: SessionLog | undefined): number =>\n log ? log.offset + log.rows.length : 0\n\nconst freshDispatch = (): Dispatch => ({\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n claimHolder: null,\n claimExpiresAt: null,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n})\n\nconst date = (value: Date | null): Date | null =>\n value ? new Date(value) : null\n\nconst toStored = (row: Row): StoredEvent => ({\n id: row.id,\n type: row.type,\n payload: structuredClone(row.payload),\n index: row.index,\n sessionId: row.sessionId,\n createdAt: new Date(row.createdAt),\n cause: row.cause ? { ...row.cause } : null,\n})\n\nconst toEvent = (row: Row): Event => ({\n id: row.id,\n type: row.type,\n payload: structuredClone(row.payload),\n index: row.index,\n sessionId: row.sessionId,\n createdAt: new Date(row.createdAt),\n})\n\nconst toHead = (cursor: Cursor, row: Row): HeadEvent => ({\n ...toStored(row),\n consumer: cursor.consumer,\n lane: cursor.lane,\n firstClaimedAt: date(cursor.dispatch.firstClaimedAt),\n lastClaimedAt: date(cursor.dispatch.lastClaimedAt),\n attemptCount: cursor.dispatch.attemptCount,\n claimHolder: cursor.dispatch.claimHolder,\n claimExpiresAt: date(cursor.dispatch.claimExpiresAt),\n failureCount: cursor.dispatch.failureCount,\n lastFailedAt: date(cursor.dispatch.lastFailedAt),\n lastFailedAttempt: cursor.dispatch.lastFailedAttempt,\n lastError: cursor.dispatch.lastError,\n failedAt: date(cursor.dispatch.failedAt),\n})\n\nconst toCursor = (cursor: Cursor): StoredCursor => ({\n consumer: cursor.consumer,\n lane: cursor.lane,\n position: cursor.position,\n head: cursor.head,\n firstClaimedAt: date(cursor.dispatch.firstClaimedAt),\n lastClaimedAt: date(cursor.dispatch.lastClaimedAt),\n attemptCount: cursor.dispatch.attemptCount,\n claimHolder: cursor.dispatch.claimHolder,\n claimExpiresAt: date(cursor.dispatch.claimExpiresAt),\n failureCount: cursor.dispatch.failureCount,\n lastFailedAt: date(cursor.dispatch.lastFailedAt),\n lastFailedAttempt: cursor.dispatch.lastFailedAttempt,\n lastError: cursor.dispatch.lastError,\n failedAt: date(cursor.dispatch.failedAt),\n})\n\nconst compareText = (a: string, b: string): number =>\n a < b ? -1 : a > b ? 1 : 0\n\n/** Consumer, then lane, with concurrent cursors last by head. */\nconst byCursor = (a: StoredCursor, b: StoredCursor): number =>\n compareText(a.consumer, b.consumer) ||\n (a.lane === null\n ? b.lane === null\n ? (a.head ?? 0) - (b.head ?? 0)\n : 1\n : b.lane === null\n ? -1\n : compareText(a.lane, b.lane))\n\nconst cursorKey = (\n consumer: string,\n lane: string | null,\n index: number,\n): string =>\n lane === null ? `${consumer}\\u0000#${index}` : `${consumer}\\u0000L${lane}`\n\nconst takes = (types: readonly string[] | null, type: string): boolean =>\n type !== CLEAR_EVENT_TYPE && (types === null || types.includes(type))\n\nconst byHead = (a: HeadEvent, b: HeadEvent): number =>\n a.index - b.index || compareText(a.consumer, b.consumer)\n\nconst snapshotKey = (sessionId: string, reducerName: string): string =>\n `${sessionId}\\u0000${reducerName}`\n\nconst headPinKey = (sessionId: string, ref: HeadRef): string =>\n `${sessionId}\\u0000${cursorKey(ref.consumer, ref.lane, ref.index)}\\u0000${ref.index}`\n\n/** Whether a cursor belongs to a consumer the caller serves; omitted, every one. */\nconst servedBy = (\n consumers: readonly StoreConsumer[] | undefined,\n): ((cursor: Cursor) => boolean) => {\n if (!consumers) return () => true\n const names = new Set(consumers.map((consumer) => consumer.name))\n return (cursor) => names.has(cursor.consumer)\n}\n\n/** A cursor's current head. */\nconst refOf = (cursor: Cursor): HeadRef => ({\n consumer: cursor.consumer,\n lane: cursor.lane,\n index: cursor.head!,\n})\n\nexport function memory(options: MemoryStoreOptions = {}): A2Store {\n const clock = options.clock ?? SYSTEM_CLOCK\n const generateId = options.ids ?? RANDOM_IDS\n\n const sessions = new Map<string, SessionLog>()\n /** Session id → cursor key → cursor. */\n const cursors = new Map<string, Map<string, Cursor>>()\n /** Keyed by `${sessionId}\\u0000${reducerName}` — a pure cache. */\n const snapshots = new Map<\n string,\n { index: number; state: unknown; updatedAt: Date }\n >()\n const historicalSnapshots = new Map<\n string,\n { index: number; state: unknown; updatedAt: Date }\n >()\n const snapshotPins = new Map<string, Set<string>>()\n const snapshotPinCounts = new Map<string, number>()\n const streamSubscribers = new Map<string, Set<(row: Row) => void>>()\n /** ns → participant → field → latest surviving write. */\n const presenceRows = new Map<\n string,\n Map<string, Map<string, PresenceEntry>>\n >()\n const presenceSubscribers = new Map<\n string,\n Set<(patch: PresencePatch) => void>\n >()\n\n const notifyStreams = (sessionId: string, rows: Row[]): void => {\n const subs = streamSubscribers.get(sessionId)\n if (!subs) return\n for (const listener of subs) {\n for (const row of rows) listener(row)\n }\n }\n\n const logOf = (sessionId: string): SessionLog => {\n let log = sessions.get(sessionId)\n if (!log) {\n log = { rows: [], offset: 0, clearIndex: 0, byEventId: new Map() }\n sessions.set(sessionId, log)\n }\n return log\n }\n\n const rowAt = (sessionId: string, index: number): Row | undefined => {\n const log = sessions.get(sessionId)\n return log && index > log.offset\n ? log.rows[index - log.offset - 1]\n : undefined\n }\n\n const clearIndexOf = (sessionId: string): number =>\n sessions.get(sessionId)?.clearIndex ?? 0\n\n /** The session's row owning `id`, ignoring rows below its clear. */\n const ownerOf = (sessionId: string, id: string): Row | undefined => {\n const log = sessions.get(sessionId)\n const row = log?.byEventId.get(id)\n return log && row && row.index >= log.clearIndex ? row : undefined\n }\n\n const cursorsOf = (sessionId: string): Map<string, Cursor> => {\n let map = cursors.get(sessionId)\n if (!map) {\n map = new Map()\n cursors.set(sessionId, map)\n }\n return map\n }\n\n const cursorAt = (sessionId: string, ref: HeadRef): Cursor | undefined =>\n cursors.get(sessionId)?.get(cursorKey(ref.consumer, ref.lane, ref.index))\n\n const checkpointKey = (\n sessionId: string,\n reducerName: string,\n index: number,\n ): string => `${snapshotKey(sessionId, reducerName)}\\u0000${index}`\n\n const releaseSnapshotPins = (sessionId: string, ref: HeadRef): void => {\n const pinKey = headPinKey(sessionId, ref)\n const checkpoints = snapshotPins.get(pinKey)\n if (!checkpoints) return\n for (const key of checkpoints) {\n const next = (snapshotPinCounts.get(key) ?? 1) - 1\n if (next === 0) {\n snapshotPinCounts.delete(key)\n historicalSnapshots.delete(key)\n } else {\n snapshotPinCounts.set(key, next)\n }\n }\n snapshotPins.delete(pinKey)\n }\n\n /** The next event after `after` that a log cursor takes, or null. */\n const nextLogHead = (\n sessionId: string,\n after: number,\n types: readonly string[] | null,\n ): number | null => {\n const log = sessions.get(sessionId)\n if (!log) return null\n for (\n let offset = Math.max(0, after - log.offset);\n offset < log.rows.length;\n offset += 1\n ) {\n const row = log.rows[offset]!\n if (takes(types, row.type)) return row.index\n }\n return null\n }\n\n /** Moves a cursor past its head: the next head starts with fresh bookkeeping. */\n const advance = (sessionId: string, cursor: Cursor): void => {\n const done = cursor.head!\n releaseSnapshotPins(sessionId, refOf(cursor))\n cursor.position = done\n cursor.dispatch = freshDispatch()\n if (cursor.kind === 'event') {\n cursor.head = null\n } else if (cursor.kind === 'lane') {\n cursor.queued += 1\n cursor.head = cursor.queue[cursor.queued] ?? null\n } else {\n cursor.head = nextLogHead(sessionId, done, cursor.types)\n }\n }\n\n const hasPending = (\n sessionId: string,\n consumers: readonly StoreConsumer[] | undefined,\n ): boolean => {\n const served = servedBy(consumers)\n for (const cursor of cursors.get(sessionId)?.values() ?? []) {\n if (\n cursor.head !== null &&\n cursor.dispatch.failedAt === null &&\n served(cursor)\n ) {\n return true\n }\n }\n return false\n }\n\n /** The attempt-currency fence shared by consumer appends and clears. */\n const assertCurrentCause = (sessionId: string, cause: EventCause): void => {\n if (cause.index <= clearIndexOf(sessionId)) {\n throw supersededAttemptError(sessionId, cause)\n }\n const cursor = cursorAt(sessionId, cause)\n if (\n !cursor ||\n cursor.head !== cause.index ||\n cursor.dispatch.attemptCount !== cause.attempt ||\n cursor.dispatch.failedAt !== null\n ) {\n throw supersededAttemptError(sessionId, cause)\n }\n }\n\n const deadLetter = (sessionId: string, cursor: Cursor, now: Date): void => {\n cursor.dispatch.failedAt = new Date(now)\n releaseSnapshotPins(sessionId, refOf(cursor))\n }\n\n const claim = ({\n sessionId,\n cursor,\n holder,\n now,\n expiresAt,\n maxFailures,\n }: {\n sessionId: string\n cursor: Cursor\n holder: string\n now: Date\n expiresAt: Date\n maxFailures: number | undefined\n }): HeadEvent => {\n const dispatch = cursor.dispatch\n const row = rowAt(sessionId, cursor.head!)!\n // A claim still held here lapsed: its attempt never finished.\n if (dispatch.claimHolder !== null) {\n dispatch.failureCount += 1\n if (maxFailures !== undefined && dispatch.failureCount >= maxFailures) {\n // Advancing the attempt fences the lapsed one; nothing runs.\n dispatch.attemptCount += 1\n dispatch.claimHolder = null\n dispatch.claimExpiresAt = null\n deadLetter(sessionId, cursor, now)\n return toHead(cursor, row)\n }\n }\n dispatch.attemptCount += 1\n dispatch.firstClaimedAt ??= new Date(now)\n dispatch.lastClaimedAt = new Date(now)\n dispatch.claimHolder = holder\n dispatch.claimExpiresAt = new Date(expiresAt)\n return toHead(cursor, row)\n }\n\n const checkBatchIds = (\n sessionId: string,\n events: readonly (AppendEvent & { id?: string })[],\n ): Row[] => {\n const supplied = events.filter(\n (candidate): candidate is AppendEvent & { id: string } =>\n candidate.id !== undefined,\n )\n const suppliedIds = new Set(supplied.map((candidate) => candidate.id))\n if (suppliedIds.size !== supplied.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'batch contains the same event id more than once',\n )\n }\n return supplied\n .map((candidate) => ownerOf(sessionId, candidate.id))\n .filter((row): row is Row => row !== undefined)\n }\n\n /** Records the types a declared log consumer takes on its cursor. */\n const refreshLogTypes = (\n sessionId: string,\n consumers: readonly StoreConsumer[] | undefined,\n ): void => {\n const map = cursors.get(sessionId)\n if (!map) return\n for (const consumer of consumers ?? []) {\n if (!consumer.log) continue\n const cursor = map.get(cursorKey(consumer.name, consumer.log.lane, 0))\n if (cursor?.kind === 'log') cursor.types = consumer.log.types\n }\n }\n\n /** Starts the cursor work an inserted batch carries. */\n const enqueue = (\n sessionId: string,\n rows: readonly Row[],\n events: readonly AppendEvent[],\n base: number,\n consumers: readonly StoreConsumer[] | undefined,\n ): void => {\n const map = cursorsOf(sessionId)\n rows.forEach((row, offset) => {\n for (const target of events[offset]!.targets ?? []) {\n const key = cursorKey(target.consumer, target.lane, row.index)\n if (target.lane === null) {\n map.set(key, {\n consumer: target.consumer,\n lane: null,\n kind: 'event',\n position: row.index - 1,\n head: row.index,\n dispatch: freshDispatch(),\n queue: [],\n queued: 0,\n completions: new Map(),\n types: null,\n })\n continue\n }\n let cursor = map.get(key)\n if (!cursor) {\n cursor = {\n consumer: target.consumer,\n lane: target.lane,\n kind: 'lane',\n position: clearIndexOf(sessionId),\n head: null,\n dispatch: freshDispatch(),\n queue: [],\n queued: 0,\n completions: new Map(),\n types: null,\n }\n map.set(key, cursor)\n }\n cursor.queue.push(row.index)\n cursor.head ??= row.index\n }\n })\n const clearIndex = clearIndexOf(sessionId)\n for (const consumer of consumers ?? []) {\n const log = consumer.log\n if (!log) continue\n const first = rows.find((row) => takes(log.types, row.type))\n if (!first) continue\n const key = cursorKey(consumer.name, log.lane, 0)\n const cursor = map.get(key)\n if (!cursor) {\n const position = log.backfill ? clearIndex : base\n map.set(key, {\n consumer: consumer.name,\n lane: log.lane,\n kind: 'log',\n position,\n head: log.backfill\n ? nextLogHead(sessionId, position, log.types)\n : first.index,\n dispatch: freshDispatch(),\n queue: [],\n queued: 0,\n completions: new Map(),\n types: log.types,\n })\n } else {\n cursor.types = log.types\n // Not `first`: a deployment that doesn't serve this consumer may\n // have appended events it takes since it went idle.\n cursor.head ??= nextLogHead(sessionId, cursor.position, log.types)\n }\n }\n }\n\n const insert = (\n sessionId: string,\n events: readonly AppendEvent[],\n now: Date,\n consumers: readonly StoreConsumer[] | undefined,\n cause?: EventCause,\n ): Row[] => {\n const log = logOf(sessionId)\n const base = headOf(log)\n const inserted = events.map((candidate, offset): Row => ({\n id: candidate.id ?? generateId(),\n type: candidate.type,\n payload: structuredClone(candidate.payload),\n index: base + 1 + offset,\n sessionId,\n createdAt: new Date(now),\n cause: cause\n ? { ...cause }\n : candidate.cause\n ? { ...candidate.cause }\n : null,\n }))\n for (const row of inserted) {\n if (ownerOf(sessionId, row.id)) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `event id '${row.id}' already exists`,\n )\n }\n }\n for (const row of inserted) {\n log.rows.push(row)\n log.byEventId.set(row.id, row)\n }\n enqueue(sessionId, inserted, events, base, consumers)\n return inserted\n }\n\n /** Every cached fold of this session below its clear, head or historical. */\n const dropCheckpointsBelow = (\n sessionId: string,\n clearIndex: number,\n ): void => {\n const prefix = `${sessionId}\\u0000`\n for (const [key, snapshot] of snapshots) {\n if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue\n snapshots.delete(key)\n snapshotPinCounts.delete(`${key}\\u0000${snapshot.index}`)\n }\n for (const [key, snapshot] of historicalSnapshots) {\n if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue\n historicalSnapshots.delete(key)\n snapshotPinCounts.delete(key)\n }\n }\n\n const readStateOf = (\n sessionId: string,\n reducerName: string,\n throughIndex?: number,\n snapshotThroughIndex?: number,\n ): StoreStateRead => {\n const key = snapshotKey(sessionId, reducerName)\n const clearIndex = clearIndexOf(sessionId)\n const snapshotFrontier = snapshotThroughIndex ?? throughIndex\n const stored = snapshots.get(key)\n const head = stored && stored.index >= clearIndex ? stored : undefined\n let candidate =\n head && (snapshotFrontier === undefined || head.index <= snapshotFrontier)\n ? head\n : null\n if (snapshotFrontier !== undefined) {\n const prefix = `${key}\\u0000`\n for (const [historicalKey, snapshot] of historicalSnapshots) {\n if (\n historicalKey.startsWith(prefix) &&\n snapshot.index >= clearIndex &&\n snapshot.index <= snapshotFrontier &&\n (!candidate || snapshot.index > candidate.index)\n ) {\n candidate = snapshot\n }\n }\n }\n const snapshot = candidate\n ? { index: candidate.index, state: structuredClone(candidate.state) }\n : null\n const afterIndex = Math.max(snapshot?.index ?? 0, clearIndex - 1)\n return {\n headIndex: head?.index ?? null,\n snapshot,\n events: (sessions.get(sessionId)?.rows ?? [])\n .filter(\n (row) =>\n row.index > afterIndex &&\n (throughIndex === undefined || row.index <= throughIndex),\n )\n .map(toEvent),\n ...(clearIndex > 0 ? { clearIndex } : {}),\n }\n }\n\n /** The dead-lettered head a redrive names, or undefined. */\n const deadHeadOf = (\n sessionId: string,\n consumer: string,\n index: number,\n ): Cursor | undefined => {\n if (index <= clearIndexOf(sessionId)) return undefined\n for (const cursor of cursors.get(sessionId)?.values() ?? []) {\n if (\n cursor.consumer === consumer &&\n cursor.head === index &&\n cursor.dispatch.failedAt !== null\n ) {\n return cursor\n }\n }\n return undefined\n }\n\n return {\n async append(sessionId, events, appendOptions) {\n const consumers = appendOptions?.consumers\n if (events.length === 0) {\n return { events: [], hasPending: hasPending(sessionId, consumers) }\n }\n\n // Batch-scoped idempotency (a2-implementation.md §3): all ids\n // already present → lost-ack retry, return the original rows;\n // some present → the caller mixed sent and fresh events.\n const existing = checkBatchIds(sessionId, events)\n if (existing.length > 0) {\n if (existing.length === events.length) {\n return {\n events: idempotentReplay(events, existing.map(toStored)),\n hasPending: hasPending(sessionId, consumers),\n }\n }\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `batch mixes ${existing.length} already-appended and ${events.length - existing.length} fresh events`,\n )\n }\n\n const originIndex = appendOptions?.originIndex\n if (originIndex !== undefined && originIndex <= clearIndexOf(sessionId)) {\n throw sessionClearedError(\n `scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,\n )\n }\n // Attempt-currency fence: a fresh consumer append commits only while\n // its causal attempt still owns the cursor's head (store.ts `append`).\n for (const event of events) {\n if (event.cause) assertCurrentCause(sessionId, event.cause)\n }\n\n const inserted = insert(sessionId, events, clock.now(), consumers)\n notifyStreams(sessionId, inserted)\n return {\n events: inserted.map(toStored),\n hasPending: hasPending(sessionId, consumers),\n }\n },\n\n async clear({ sessionId, ifLastIndex, cause }) {\n if (cause) assertCurrentCause(sessionId, cause)\n const log = sessions.get(sessionId)\n const head = headOf(log)\n const clearIndex = log?.clearIndex ?? 0\n if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {\n return { cleared: true, index: head, clearIndex }\n }\n if (ifLastIndex !== undefined && ifLastIndex !== head) {\n return { cleared: false, index: head, clearIndex }\n }\n if (head === 0 || clearIndex === head) {\n return { cleared: true, index: head, clearIndex }\n }\n const [marker] = insert(\n sessionId,\n [{ type: CLEAR_EVENT_TYPE, payload: null }],\n clock.now(),\n undefined,\n )\n logOf(sessionId).clearIndex = marker!.index\n // Every event is at or below the marker: no head survives, and log\n // cursors count from the marker.\n const map = cursors.get(sessionId)\n for (const [key, cursor] of map ?? []) {\n if (cursor.head !== null) releaseSnapshotPins(sessionId, refOf(cursor))\n if (cursor.kind !== 'log') {\n map!.delete(key)\n continue\n }\n cursor.position = marker!.index\n cursor.head = null\n cursor.dispatch = freshDispatch()\n cursor.completions.clear()\n }\n notifyStreams(sessionId, [marker!])\n return { cleared: true, index: marker!.index, clearIndex: marker!.index }\n },\n\n async reclaim(sessionId) {\n const log = sessions.get(sessionId)\n if (!log || log.offset >= log.clearIndex - 1) {\n return { deleted: 0, done: true }\n }\n const removed = log.rows.splice(\n 0,\n Math.min(RECLAIM_BATCH, log.clearIndex - 1 - log.offset),\n )\n log.offset += removed.length\n for (const row of removed) {\n // A reused id belongs to its new row now.\n if (log.byEventId.get(row.id) === row) log.byEventId.delete(row.id)\n }\n const done = log.offset === log.clearIndex - 1\n // Writes below the marker are dropped, so one sweep at the end suffices.\n if (done) dropCheckpointsBelow(sessionId, log.clearIndex)\n return { deleted: removed.length, done }\n },\n\n async wakeLog({ sessionId, consumer }) {\n const { log } = consumer\n const key = cursorKey(consumer.name, log.lane, 0)\n const head = headOf(sessions.get(sessionId))\n let cursor = cursors.get(sessionId)?.get(key)\n if (!cursor) {\n const marker = clearIndexOf(sessionId)\n if (head === marker) {\n return {\n cursor: idleLogCursor(consumer.name, log.lane, marker),\n head,\n }\n }\n const position = log.backfill ? marker : head\n cursor = {\n consumer: consumer.name,\n lane: log.lane,\n kind: 'log',\n position,\n head: log.backfill\n ? nextLogHead(sessionId, position, log.types)\n : null,\n dispatch: freshDispatch(),\n queue: [],\n queued: 0,\n completions: new Map(),\n types: log.types,\n }\n cursorsOf(sessionId).set(key, cursor)\n } else {\n cursor.types = log.types\n cursor.head ??= nextLogHead(sessionId, cursor.position, log.types)\n }\n return { cursor: toCursor(cursor), head }\n },\n\n async cursors(sessionId) {\n return [...(cursors.get(sessionId)?.values() ?? [])]\n .filter((cursor) => cursor.kind !== 'event' || cursor.head !== null)\n .map(toCursor)\n .toSorted(byCursor)\n },\n\n async read(sessionId, opts) {\n const log = sessions.get(sessionId)\n if (!log) return []\n const head = headOf(log)\n const after = Math.max(\n opts?.afterIndex ?? 0,\n log.clearIndex - 1,\n log.offset,\n )\n const through = Math.min(opts?.throughIndex ?? head, head)\n if (through <= after) return []\n return log.rows\n .slice(after - log.offset, through - log.offset)\n .map(toStored)\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n exclude,\n consumers,\n maxFailures,\n through,\n }) {\n refreshLogTypes(sessionId, consumers)\n const claimedAt = clock.now()\n const now = claimedAt.getTime()\n const expiresAt = new Date(expiresAtMs ?? now + ttlMs)\n const excluded = new Set(\n (exclude ?? []).map((ref) =>\n cursorKey(ref.consumer, ref.lane, ref.index),\n ),\n )\n const served = servedBy(consumers)\n const eligible: Cursor[] = []\n let retryAt: Date | null = null\n\n for (const [key, cursor] of cursors.get(sessionId) ?? []) {\n if (\n cursor.head === null ||\n (through !== undefined && cursor.head > through) ||\n cursor.dispatch.failedAt !== null ||\n !served(cursor) ||\n excluded.has(key)\n ) {\n continue\n }\n const expiry = cursor.dispatch.claimExpiresAt\n if (expiry && expiry.getTime() > now) {\n if (!retryAt || expiry < retryAt) retryAt = new Date(expiry)\n continue\n }\n eligible.push(cursor)\n }\n\n if (eligible.length === 0) {\n return retryAt ? { outcome: 'busy', retryAt } : { outcome: 'settled' }\n }\n const clearIndex = clearIndexOf(sessionId)\n const claimed = eligible\n .map((cursor) => {\n const head = claim({\n sessionId,\n cursor,\n holder,\n now: claimedAt,\n expiresAt,\n maxFailures,\n })\n if (clearIndex > 0) head.clearIndex = clearIndex\n return head\n })\n .toSorted(byHead)\n return {\n outcome: 'claimed',\n events: claimed,\n head: headOf(sessions.get(sessionId)),\n }\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n const now = clock.now()\n const expiresAt = new Date(expiresAtMs ?? now.getTime() + ttlMs)\n const clearIndex = clearIndexOf(sessionId)\n const renewed: HeadRef[] = []\n const superseded: HeadRef[] = []\n const ordered = claims.toSorted(\n (a, b) => a.index - b.index || compareText(a.consumer, b.consumer),\n )\n for (const { attempt, ...ref } of ordered) {\n if (ref.index <= clearIndex) {\n superseded.push(ref)\n continue\n }\n const cursor = cursorAt(sessionId, ref)\n if (!cursor) continue\n const dispatch = cursor.dispatch\n // An event the cursor has not reached yet holds no claim to renew.\n if (cursor.head !== ref.index && cursor.position < ref.index) continue\n if (cursor.head !== ref.index || dispatch.attemptCount > attempt) {\n superseded.push(ref)\n continue\n }\n if (\n dispatch.failedAt === null &&\n dispatch.claimHolder === holder &&\n dispatch.claimExpiresAt !== null &&\n dispatch.claimExpiresAt > now\n ) {\n dispatch.claimExpiresAt = new Date(expiresAt)\n renewed.push(ref)\n }\n }\n return { renewed, superseded }\n },\n\n async completeAttempt({\n sessionId,\n consumer,\n lane,\n index,\n attempt,\n events,\n consumers,\n }) {\n if (index <= clearIndexOf(sessionId)) return { outcome: 'superseded' }\n const ref = { consumer, lane, index }\n const cursor = cursorAt(sessionId, ref)\n if (!cursor) {\n if (!rowAt(sessionId, index)) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n return { outcome: 'superseded' }\n }\n refreshLogTypes(sessionId, consumers)\n\n if (cursor.head !== index) {\n const completion = cursor.completions.get(index)\n if (!completion || completion.attempt !== attempt) {\n return { outcome: 'superseded' }\n }\n const requestedIds = events.map((event) => event.id)\n if (\n completion.returnedEventIds.length !== requestedIds.length ||\n completion.returnedEventIds.some(\n (id, offset) => id !== requestedIds[offset],\n )\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'completed attempt does not match the returned event batch',\n )\n }\n const existing = checkBatchIds(sessionId, events)\n if (\n existing.length !== events.length ||\n existing.some(\n (row) =>\n row.cause?.consumer !== consumer ||\n row.cause.lane !== lane ||\n row.cause.index !== index ||\n row.cause.attempt !== attempt,\n )\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'completed attempt does not match the returned event batch',\n )\n }\n return {\n outcome: 'completed',\n events:\n events.length === 0\n ? []\n : idempotentReplay(events, existing.map(toStored)),\n }\n }\n const dispatch = cursor.dispatch\n if (\n dispatch.attemptCount !== attempt ||\n dispatch.failedAt !== null ||\n dispatch.claimHolder === null\n ) {\n return { outcome: 'superseded' }\n }\n\n const existing = checkBatchIds(sessionId, events)\n if (existing.length > 0) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains already-appended ids',\n )\n }\n const inserted = insert(sessionId, events, clock.now(), consumers, {\n consumer,\n lane,\n index,\n attempt,\n })\n if (cursor.kind === 'log') cursor.completions.clear()\n cursor.completions.set(index, {\n attempt,\n returnedEventIds: events.map((event) => event.id),\n })\n advance(sessionId, cursor)\n notifyStreams(sessionId, inserted)\n return { outcome: 'completed', events: inserted.map(toStored) }\n },\n\n async failAttempt({\n sessionId,\n consumer,\n lane,\n index,\n attempt,\n error,\n maxFailures,\n retryAfterMs,\n }) {\n if (index <= clearIndexOf(sessionId)) {\n return { outcome: 'superseded', failureCount: 0 }\n }\n const cursor = cursorAt(sessionId, { consumer, lane, index })\n if (!cursor) {\n if (!rowAt(sessionId, index)) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n return { outcome: 'superseded', failureCount: 0 }\n }\n const dispatch = cursor.dispatch\n if (\n cursor.head !== index ||\n dispatch.attemptCount !== attempt ||\n (dispatch.claimHolder === null &&\n dispatch.lastFailedAttempt !== attempt)\n ) {\n return {\n outcome: 'superseded',\n failureCount: cursor.head === index ? dispatch.failureCount : 0,\n }\n }\n if (\n dispatch.lastFailedAttempt === attempt &&\n dispatch.claimHolder === null\n ) {\n return {\n outcome: dispatch.failedAt ? 'dead_lettered' : 'failed',\n failureCount: dispatch.failureCount,\n }\n }\n if (dispatch.failedAt) {\n return { outcome: 'dead_lettered', failureCount: dispatch.failureCount }\n }\n dispatch.failureCount += 1\n dispatch.lastError = error\n const failedAt = clock.now()\n dispatch.lastFailedAt = new Date(failedAt)\n dispatch.lastFailedAttempt = attempt\n dispatch.claimHolder = null\n dispatch.claimExpiresAt =\n retryAfterMs === undefined || dispatch.failureCount >= maxFailures\n ? null\n : new Date(failedAt.getTime() + retryAfterMs)\n if (dispatch.failureCount >= maxFailures) {\n deadLetter(sessionId, cursor, failedAt)\n return {\n outcome: 'dead_lettered',\n failureCount: dispatch.failureCount,\n }\n }\n return { outcome: 'failed', failureCount: dispatch.failureCount }\n },\n\n async deadLetters(sessionId) {\n const clearIndex = clearIndexOf(sessionId)\n const dead: HeadEvent[] = []\n for (const cursor of cursors.get(sessionId)?.values() ?? []) {\n if (\n cursor.head === null ||\n cursor.head <= clearIndex ||\n cursor.dispatch.failedAt === null\n ) {\n continue\n }\n dead.push(toHead(cursor, rowAt(sessionId, cursor.head)!))\n }\n return dead.toSorted(byHead)\n },\n\n async redrive({ sessionId, consumer, index, action, consumers }) {\n const cursor = deadHeadOf(sessionId, consumer, index)\n if (!cursor) {\n return { redriven: false, hasPending: hasPending(sessionId, consumers) }\n }\n refreshLogTypes(sessionId, consumers)\n if (action === 'skip') {\n advance(sessionId, cursor)\n } else {\n cursor.dispatch.failedAt = null\n cursor.dispatch.failureCount = 0\n }\n return { redriven: true, hasPending: hasPending(sessionId, consumers) }\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return readStateOf(\n sessionId,\n reducerName,\n stateOptions?.throughIndex,\n stateOptions?.snapshotThroughIndex,\n )\n },\n\n async readStates(requests, reducerName) {\n return requests.map((request) =>\n readStateOf(\n request.sessionId,\n reducerName,\n request.throughIndex,\n request.snapshotThroughIndex,\n ),\n )\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n const key = snapshotKey(sessionId, reducerName)\n const clearIndex = clearIndexOf(sessionId)\n for (const write of writes.toSorted((a, b) => a.index - b.index)) {\n if (write.index < clearIndex) continue\n const checkpoint = checkpointKey(sessionId, reducerName, write.index)\n for (const pin of write.pins ?? []) {\n const cursor = cursorAt(sessionId, pin)\n if (\n !cursor ||\n pin.index <= clearIndex ||\n cursor.head !== pin.index ||\n cursor.dispatch.failedAt !== null\n ) {\n continue\n }\n const pinKey = headPinKey(sessionId, pin)\n let pinned = snapshotPins.get(pinKey)\n if (!pinned) {\n pinned = new Set()\n snapshotPins.set(pinKey, pinned)\n }\n if (!pinned.has(checkpoint)) {\n pinned.add(checkpoint)\n snapshotPinCounts.set(\n checkpoint,\n (snapshotPinCounts.get(checkpoint) ?? 0) + 1,\n )\n }\n }\n\n const current = snapshots.get(key)\n if (!current || write.index >= current.index) {\n if (\n current &&\n write.index > current.index &&\n current.index >= clearIndex\n ) {\n const currentCheckpoint = checkpointKey(\n sessionId,\n reducerName,\n current.index,\n )\n if ((snapshotPinCounts.get(currentCheckpoint) ?? 0) > 0) {\n historicalSnapshots.set(currentCheckpoint, {\n index: current.index,\n state: structuredClone(current.state),\n updatedAt: new Date(current.updatedAt),\n })\n }\n }\n snapshots.set(key, {\n index: write.index,\n state: structuredClone(write.state),\n updatedAt: clock.now(),\n })\n historicalSnapshots.delete(checkpoint)\n } else if (\n write.index < current.index &&\n (snapshotPinCounts.get(checkpoint) ?? 0) > 0\n ) {\n historicalSnapshots.set(checkpoint, {\n index: write.index,\n state: structuredClone(write.state),\n updatedAt: clock.now(),\n })\n }\n }\n },\n\n presence: {\n async set(ns, participant, values, meta) {\n let participants = presenceRows.get(ns)\n if (!participants) {\n participants = new Map()\n presenceRows.set(ns, participants)\n }\n let fields = participants.get(participant)\n if (!fields) {\n fields = new Map()\n participants.set(participant, fields)\n }\n\n // Expiry anchors on the storage's own clock — the sender's\n // `at` orders writes but never extends or shortens a lifetime.\n const now = clock.now()\n const expiresAtMs = now.getTime() + meta.ttlMs\n // Field-keyed and caller-named — null-prototype, like every\n // presence map (see `nullProtoRecord`).\n const applied: Record<string, unknown> = nullProtoRecord()\n let appliedCount = 0\n for (const [field, value] of Object.entries(values)) {\n // Field-wise LWW by `at` against unexpired values and\n // tombstones alike; ties go to the incoming write, so\n // same-stamp sets keep set-then-read intuition.\n const existing = fields.get(field)\n if (existing && existing.expiresAt > now && existing.at > meta.at)\n continue\n fields.set(field, {\n value: structuredClone(value),\n seen: meta.seen,\n at: new Date(meta.at),\n expiresAt: new Date(expiresAtMs),\n })\n applied[field] = structuredClone(value)\n appliedCount += 1\n }\n if (fields.size === 0) participants.delete(participant)\n if (participants.size === 0) presenceRows.delete(ns)\n\n // Only applied fields broadcast — a losing write repaints nothing,\n // so subscribers stay consistent with what read() returns.\n if (appliedCount === 0) return\n const subs = presenceSubscribers.get(ns)\n if (!subs) return\n const patch: PresencePatch = {\n participant,\n values: applied,\n seen: meta.seen,\n at: new Date(meta.at),\n }\n for (const listener of subs) listener(patch)\n },\n\n async read(ns) {\n const participants = presenceRows.get(ns)\n if (!participants) return []\n const now = clock.now()\n const rows: PresenceRow[] = []\n for (const [participant, fields] of participants) {\n for (const [field, entry] of fields) {\n // Expiry is enforced lazily on read — no timers; a silent\n // participant's rows vanish the next time anyone looks.\n if (entry.expiresAt <= now) {\n fields.delete(field)\n continue\n }\n if (entry.value === null) continue\n rows.push({\n participant,\n field,\n value: structuredClone(entry.value),\n seen: entry.seen,\n at: new Date(entry.at),\n expiresAt: new Date(entry.expiresAt),\n })\n }\n if (fields.size === 0) participants.delete(participant)\n }\n if (participants.size === 0) presenceRows.delete(ns)\n return rows\n },\n\n subscribe(ns, onPatch) {\n let subs = presenceSubscribers.get(ns)\n if (!subs) {\n subs = new Set()\n presenceSubscribers.set(ns, subs)\n }\n subs.add(onPatch)\n return () => {\n subs.delete(onPatch)\n if (subs.size === 0) presenceSubscribers.delete(ns)\n }\n },\n },\n\n stream(sessionId, opts) {\n const startAfter = opts?.startAfter ?? 0\n return {\n [Symbol.asyncIterator](): AsyncIterator<Event> {\n let last = Math.max(startAfter, clearIndexOf(sessionId) - 1)\n let wake: (() => void) | null = null\n let closed = false\n\n const onRow = (): void => {\n wake?.()\n }\n let subs = streamSubscribers.get(sessionId)\n if (!subs) {\n subs = new Set()\n streamSubscribers.set(sessionId, subs)\n }\n subs.add(onRow)\n const stop = (): void => {\n if (closed) return\n closed = true\n subs.delete(onRow)\n if (subs.size === 0) streamSubscribers.delete(sessionId)\n wake?.()\n }\n\n return {\n async next(): Promise<IteratorResult<Event>> {\n try {\n for (;;) {\n if (closed) return { value: undefined, done: true }\n const log = sessions.get(sessionId)\n // Rows reclaimed behind a running stream are skipped.\n const row = log?.rows[Math.max(0, last - log.offset)]\n if (row) {\n const value = toStored(row)\n last = row.index\n return { value, done: false }\n }\n // oxlint-disable-next-line no-await-in-loop -- wait-for-wake\n await new Promise<void>((resolve) => {\n wake = resolve\n })\n wake = null\n }\n } catch (error) {\n stop()\n throw error\n }\n },\n async return(): Promise<IteratorResult<Event>> {\n stop()\n return { value: undefined, done: true }\n },\n }\n },\n }\n },\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as POLL_TIMINGS } from "./internal-CvsrprnU.js";
|
|
2
2
|
//#region src/stream-activity.ts
|
|
3
3
|
const streamActivities = /* @__PURE__ */ new WeakMap();
|
|
4
4
|
function createStreamActivity(wake) {
|
|
@@ -149,4 +149,4 @@ function pollingStream(readAfter, options) {
|
|
|
149
149
|
//#endregion
|
|
150
150
|
export { streamActivities as i, pollingStream as n, createStreamActivity as r, defaultSleep as t };
|
|
151
151
|
|
|
152
|
-
//# sourceMappingURL=store-polling-
|
|
152
|
+
//# sourceMappingURL=store-polling-BZt2Rf3D.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-polling-
|
|
1
|
+
{"version":3,"file":"store-polling-BZt2Rf3D.js","names":[],"sources":["../src/stream-activity.ts","../src/store-polling.ts"],"sourcesContent":["export type StreamActivity = {\n readonly idle: boolean\n setIdle(idle: boolean): void\n activate(): void\n subscribe(listener: (idle: boolean) => void): () => void\n}\n\nexport const streamActivities: WeakMap<object, StreamActivity> = new WeakMap()\n\nexport function createStreamActivity(wake: () => void): StreamActivity {\n let idle = false\n const listeners = new Set<(idle: boolean) => void>()\n const setIdle = (next: boolean): void => {\n if (idle === next) return\n idle = next\n for (const listener of listeners) listener(idle)\n }\n return {\n get idle() {\n return idle\n },\n setIdle,\n activate() {\n setIdle(false)\n wake()\n },\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n }\n}\n","/**\n * The shared polling stream — how poll-based backends (sqlite,\n * postgres) implement `A2Store.stream`.\n *\n * The cadence is activity-adaptive: while events are flowing, polls\n * run at a tight floor (25ms) so streams feel pushed — a token\n * generation reads smoothly instead of clumping at the idle interval;\n * every empty poll doubles the gap until the idle ceiling. The cadence\n * is owned (POLL_TIMINGS in internal.ts), not a public option: the\n * floor already keeps active streams smooth, so tuning it is a cost\n * decision a2 makes, not one it delegates. Correctness never depends\n * on the cadence — each poll is a catch-up read (`index > last`, in\n * order), so nothing can be missed, only arrive a beat later.\n */\n\nimport { POLL_TIMINGS } from './internal.ts'\nimport type { Event } from './store.ts'\nimport { createStreamActivity, streamActivities } from './stream-activity.ts'\n\n/** One cancellable sleep — the injection seam for cadence tests. */\nexport type Sleeper = { promise: Promise<void>; cancel: () => void }\n\n/** Unref'd cancellable timer — shared with the redis safety read. */\nexport const defaultSleep = (ms: number): Sleeper => {\n let timer: ReturnType<typeof setTimeout> | undefined\n let wake: (() => void) | undefined\n const promise = new Promise<void>((resolve) => {\n wake = resolve // assigned synchronously by the executor\n timer = setTimeout(resolve, ms)\n // Don't hold the process open for an idle poll.\n ;(timer as { unref?: () => void }).unref?.()\n })\n return {\n promise,\n cancel: () => {\n clearTimeout(timer)\n wake?.()\n },\n }\n}\n\n// Read the seam lazily — tests shrink POLL_TIMINGS in their setup.\nconst ceiling = (): number => POLL_TIMINGS.idleCeilingMs\nconst floor = (): number =>\n Math.min(POLL_TIMINGS.activeFloorMs, POLL_TIMINGS.idleCeilingMs)\n\nexport type PollingStreamOptions = {\n /** Deliver events strictly after this index (exclusive). */\n startAfter?: number\n /** Injectable sleep — tests assert the cadence without wall-clock waits. */\n sleep?: (ms: number) => Sleeper\n}\n\n/**\n * Build the `stream()` iterable from a backend's catch-up read. The\n * iterable ends when the consumer calls `return()` (a disconnecting\n * SSE client), waking any pending sleep immediately.\n */\nexport function pollingStream(\n readAfter: (afterIndex: number) => Event[] | Promise<Event[]>,\n options: PollingStreamOptions,\n): AsyncIterable<Event> {\n const startAfter = options.startAfter ?? 0\n const sleep = options.sleep ?? defaultSleep\n\n return {\n [Symbol.asyncIterator](): AsyncIterator<Event> {\n let last = startAfter\n let buffer: Array<Event | undefined> = []\n let position = 0\n let closed = false\n let pending: Sleeper | null = null\n let interval = floor()\n let activityVersion = 0\n const activity = createStreamActivity(() => {\n if (closed) return\n activityVersion += 1\n interval = floor()\n pending?.cancel()\n })\n\n const iterator: AsyncIterator<Event> = {\n async next(): Promise<IteratorResult<Event>> {\n for (;;) {\n if (closed) return { value: undefined, done: true }\n try {\n if (position === buffer.length) {\n // oxlint-disable-next-line no-await-in-loop -- poll loop\n const rows = await readAfter(last)\n if (closed) return { value: undefined, done: true }\n buffer = rows\n position = 0\n }\n } catch (error) {\n if (closed) return { value: undefined, done: true }\n throw error\n }\n if (closed) return { value: undefined, done: true }\n const row = buffer[position]\n if (row) {\n // Activity: tighten back to the floor.\n activity.activate()\n last = row.index\n buffer[position] = undefined\n position += 1\n if (position === buffer.length) {\n buffer = []\n position = 0\n }\n return { value: row, done: false }\n }\n // Empty poll: wait, then back off toward the idle ceiling.\n activity.setIdle(interval === ceiling())\n const version = activityVersion\n pending = sleep(interval)\n // oxlint-disable-next-line no-await-in-loop -- poll loop\n await pending.promise\n pending = null\n if (version === activityVersion) {\n interval = Math.min(interval * 2, ceiling())\n }\n }\n },\n async return(): Promise<IteratorResult<Event>> {\n closed = true\n buffer = []\n position = 0\n pending?.cancel()\n return { value: undefined, done: true }\n },\n }\n streamActivities.set(iterator, activity)\n return iterator\n },\n }\n}\n"],"mappings":";;AAOA,MAAa,mCAAoD,IAAI,QAAQ;AAE7E,SAAgB,qBAAqB,MAAkC;CACrE,IAAI,OAAO;CACX,MAAM,4BAAY,IAAI,IAA6B;CACnD,MAAM,WAAW,SAAwB;EACvC,IAAI,SAAS,MAAM;EACnB,OAAO;EACP,KAAK,MAAM,YAAY,WAAW,SAAS,IAAI;CACjD;CACA,OAAO;EACL,IAAI,OAAO;GACT,OAAO;EACT;EACA;EACA,WAAW;GACT,QAAQ,KAAK;GACb,KAAK;EACP;EACA,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,aAAa,UAAU,OAAO,QAAQ;EACxC;CACF;AACF;;;;;;;;;;;;;;;;;;ACRA,MAAa,gBAAgB,OAAwB;CACnD,IAAI;CACJ,IAAI;CAOJ,OAAO;EACL,SAAA,IAPkB,SAAe,YAAY;GAC7C,OAAO;GACP,QAAQ,WAAW,SAAS,EAAE;GAE7B,MAAkC,QAAQ;EAC7C,CAEQ;EACN,cAAc;GACZ,aAAa,KAAK;GAClB,OAAO;EACT;CACF;AACF;AAGA,MAAM,gBAAwB,aAAa;AAC3C,MAAM,cACJ,KAAK,IAAI,aAAa,eAAe,aAAa,aAAa;;;;;;AAcjE,SAAgB,cACd,WACA,SACsB;CACtB,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,QAAQ,QAAQ,SAAS;CAE/B,OAAO,EACL,CAAC,OAAO,iBAAuC;EAC7C,IAAI,OAAO;EACX,IAAI,SAAmC,CAAC;EACxC,IAAI,WAAW;EACf,IAAI,SAAS;EACb,IAAI,UAA0B;EAC9B,IAAI,WAAW,MAAM;EACrB,IAAI,kBAAkB;EACtB,MAAM,WAAW,2BAA2B;GAC1C,IAAI,QAAQ;GACZ,mBAAmB;GACnB,WAAW,MAAM;GACjB,SAAS,OAAO;EAClB,CAAC;EAED,MAAM,WAAiC;GACrC,MAAM,OAAuC;IAC3C,SAAS;KACP,IAAI,QAAQ,OAAO;MAAE,OAAO,KAAA;MAAW,MAAM;KAAK;KAClD,IAAI;MACF,IAAI,aAAa,OAAO,QAAQ;OAE9B,MAAM,OAAO,MAAM,UAAU,IAAI;OACjC,IAAI,QAAQ,OAAO;QAAE,OAAO,KAAA;QAAW,MAAM;OAAK;OAClD,SAAS;OACT,WAAW;MACb;KACF,SAAS,OAAO;MACd,IAAI,QAAQ,OAAO;OAAE,OAAO,KAAA;OAAW,MAAM;MAAK;MAClD,MAAM;KACR;KACA,IAAI,QAAQ,OAAO;MAAE,OAAO,KAAA;MAAW,MAAM;KAAK;KAClD,MAAM,MAAM,OAAO;KACnB,IAAI,KAAK;MAEP,SAAS,SAAS;MAClB,OAAO,IAAI;MACX,OAAO,YAAY,KAAA;MACnB,YAAY;MACZ,IAAI,aAAa,OAAO,QAAQ;OAC9B,SAAS,CAAC;OACV,WAAW;MACb;MACA,OAAO;OAAE,OAAO;OAAK,MAAM;MAAM;KACnC;KAEA,SAAS,QAAQ,aAAa,QAAQ,CAAC;KACvC,MAAM,UAAU;KAChB,UAAU,MAAM,QAAQ;KAExB,MAAM,QAAQ;KACd,UAAU;KACV,IAAI,YAAY,iBACd,WAAW,KAAK,IAAI,WAAW,GAAG,QAAQ,CAAC;IAE/C;GACF;GACA,MAAM,SAAyC;IAC7C,SAAS;IACT,SAAS,CAAC;IACV,WAAW;IACX,SAAS,OAAO;IAChB,OAAO;KAAE,OAAO,KAAA;KAAW,MAAM;IAAK;GACxC;EACF;EACA,iBAAiB,IAAI,UAAU,QAAQ;EACvC,OAAO;CACT,EACF;AACF"}
|
package/dist/store-postgres.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as Clock, s as IdSource, t as A2Store } from "./store-
|
|
1
|
+
import { r as Clock, s as IdSource, t as A2Store } from "./store-D0MhBvg6.js";
|
|
2
2
|
//#region src/store-postgres.d.ts
|
|
3
3
|
/** The result shape this backend reads: just rows. */
|
|
4
4
|
type PostgresQueryResult = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-postgres.d.ts","names":[],"sources":["../src/store-postgres.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"store-postgres.d.ts","names":[],"sources":["../src/store-postgres.ts"],"mappings":";;;KAqFY;EAAwB,MAAM;;;KAG9B;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD;;;;;;;;KASU;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD,YAAY,QAAQ;EACpB,QAAQ;;KAGE;;EAEV;;EAEA;;EAEA,SAAS;;EAET,QAAQ;;EAER,MAAM;;KAGI,gBAAgB;;;EAG1B,SAAS;;iBA4tCK,SAAS,UAAS,uBAA4B"}
|