experimental-a2 0.21.1 → 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.
Files changed (139) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/{actor-HnGmrOZZ.d.ts → actor-CqibLgjQ.d.ts} +13 -5
  3. package/dist/actor-CqibLgjQ.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +1 -1
  5. package/dist/actor-react.d.ts +1 -1
  6. package/dist/actor-react.js +1 -1
  7. package/dist/actor-shared-DFET5q16.js.map +1 -1
  8. package/dist/actor.d.ts +1 -1
  9. package/dist/actor.js +3 -3
  10. package/dist/{ai-CvEnqVfo.d.ts → ai-DAu77CDr.d.ts} +2 -2
  11. package/dist/{ai-CvEnqVfo.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
  12. package/dist/{ai-CzE672Q3.js → ai-EEfKyc-z.js} +2 -2
  13. package/dist/{ai-CzE672Q3.js.map → ai-EEfKyc-z.js.map} +1 -1
  14. package/dist/ai-client.d.ts +1 -1
  15. package/dist/ai-client.js +2 -2
  16. package/dist/ai-fx.d.ts +4 -2
  17. package/dist/ai-fx.d.ts.map +1 -1
  18. package/dist/ai-fx.js +211 -61
  19. package/dist/ai-fx.js.map +1 -1
  20. package/dist/{ai-harness-Bfk-Lycn.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
  21. package/dist/{ai-harness-Bfk-Lycn.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
  22. package/dist/ai-harness-dQeJaR1t.js.map +1 -1
  23. package/dist/ai-pi.d.ts +1 -1
  24. package/dist/ai-pi.js +1 -1
  25. package/dist/ai-react.d.ts +1 -1
  26. package/dist/ai-react.js +2 -2
  27. package/dist/ai-server.d.ts +3 -3
  28. package/dist/ai-server.d.ts.map +1 -1
  29. package/dist/ai-server.js +10 -4
  30. package/dist/ai-server.js.map +1 -1
  31. package/dist/ai.d.ts +1 -1
  32. package/dist/ai.js +1 -1
  33. package/dist/{client-9IeTsiAs.js → client-BQS4i_V9.js} +27 -4
  34. package/dist/client-BQS4i_V9.js.map +1 -0
  35. package/dist/client-CUMbS_38.d.ts.map +1 -1
  36. package/dist/client.js +1 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/{internal-Bc4PvfHk.js → internal-CvsrprnU.js} +32 -15
  39. package/dist/{internal-Bc4PvfHk.js.map → internal-CvsrprnU.js.map} +1 -1
  40. package/dist/react.js +1 -1
  41. package/dist/{runtime-consumer-BWmht60z.js → runtime-consumer-CIeL33wj.js} +2 -2
  42. package/dist/{runtime-consumer-BWmht60z.js.map → runtime-consumer-CIeL33wj.js.map} +1 -1
  43. package/dist/scheduler-qstash.d.ts +2 -2
  44. package/dist/scheduler-qstash.js +2 -2
  45. package/dist/scheduler-vercel.d.ts +2 -2
  46. package/dist/scheduler-vercel.js +2 -2
  47. package/dist/{server-DvIB8kj5.js → server-DtFrWxi3.js} +30 -8
  48. package/dist/server-DtFrWxi3.js.map +1 -0
  49. package/dist/{server-BshFZ0eL.d.ts → server-qgez0Xny.d.ts} +3 -3
  50. package/dist/{server-BshFZ0eL.d.ts.map → server-qgez0Xny.d.ts.map} +1 -1
  51. package/dist/server.d.ts +2 -2
  52. package/dist/server.js +1 -1
  53. package/dist/{session-clear-Bk_29eEw.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
  54. package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
  55. package/dist/{store-Z2qjw50n.d.ts → store-D0MhBvg6.d.ts} +15 -6
  56. package/dist/store-D0MhBvg6.d.ts.map +1 -0
  57. package/dist/store-N8PXxDAS.js.map +1 -1
  58. package/dist/store-memory.d.ts +1 -1
  59. package/dist/store-memory.d.ts.map +1 -1
  60. package/dist/store-memory.js +15 -17
  61. package/dist/store-memory.js.map +1 -1
  62. package/dist/{store-polling-DYNyxCev.js → store-polling-BZt2Rf3D.js} +2 -2
  63. package/dist/{store-polling-DYNyxCev.js.map → store-polling-BZt2Rf3D.js.map} +1 -1
  64. package/dist/store-postgres.d.ts +1 -1
  65. package/dist/store-postgres.d.ts.map +1 -1
  66. package/dist/store-postgres.js +31 -40
  67. package/dist/store-postgres.js.map +1 -1
  68. package/dist/store-presence-polling-JuAy2Ck2.js +111 -0
  69. package/dist/store-presence-polling-JuAy2Ck2.js.map +1 -0
  70. package/dist/store-redis-http.d.ts +1 -1
  71. package/dist/store-redis-http.js +3 -3
  72. package/dist/{store-redis-notify-IiVdlYHc.js → store-redis-notify-CsJw_hfD.js} +538 -397
  73. package/dist/store-redis-notify-CsJw_hfD.js.map +1 -0
  74. package/dist/store-redis.d.ts +1 -1
  75. package/dist/store-redis.d.ts.map +1 -1
  76. package/dist/store-redis.js +3 -4
  77. package/dist/store-redis.js.map +1 -1
  78. package/dist/store-sqlite.d.ts +1 -1
  79. package/dist/store-sqlite.d.ts.map +1 -1
  80. package/dist/store-sqlite.js +16 -18
  81. package/dist/store-sqlite.js.map +1 -1
  82. package/dist/stream-buffer-DrTyiIuo.js +57 -0
  83. package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
  84. package/dist/{wire-CZGqNZRx.js → wire-CV-Z7wz-.js} +3 -3
  85. package/dist/{wire-CZGqNZRx.js.map → wire-CV-Z7wz-.js.map} +1 -1
  86. package/docs/actors/01-introduction.mdx +5 -2
  87. package/docs/agents/03-conversations.mdx +9 -8
  88. package/docs/agents/04-harnesses.mdx +7 -4
  89. package/docs/reference/01-core.mdx +3 -2
  90. package/docs/reference/02-server.mdx +5 -1
  91. package/docs/reference/04-ai.mdx +13 -4
  92. package/docs/reference/06-errors.mdx +1 -1
  93. package/docs/server/01-consumers.mdx +4 -1
  94. package/docs/server/06-guarantees.mdx +7 -4
  95. package/docs/stores/02-redis.mdx +16 -10
  96. package/docs/stores/03-redis-http.mdx +6 -4
  97. package/docs/stores/04-postgres.mdx +2 -2
  98. package/docs/upgrades/01-overview.mdx +1 -0
  99. package/docs/upgrades/78-0-22.mdx +115 -0
  100. package/examples/playground/app/a0/sandbox.ts +58 -13
  101. package/examples/playground/app/a2slack/db/a2.ts +7 -4
  102. package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
  103. package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
  104. package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +8 -1
  105. package/examples/playground/app/chat/server.ts +2 -2
  106. package/examples/playground/lib/store.ts +1 -1
  107. package/examples/playground/package.json +1 -1
  108. package/package.json +1 -1
  109. package/sql/postgres/drizzle-schema.ts +7 -4
  110. package/sql/postgres/schema.sql +24 -3
  111. package/src/actor-shared.ts +10 -2
  112. package/src/ai-agent.ts +13 -0
  113. package/src/ai-fx.ts +321 -77
  114. package/src/ai-harness.ts +7 -0
  115. package/src/client.ts +19 -2
  116. package/src/deterministic-id.ts +1 -1
  117. package/src/internal.ts +42 -11
  118. package/src/presence-recovery.ts +18 -11
  119. package/src/presence-tombstones.ts +52 -0
  120. package/src/push-envelope.ts +2 -2
  121. package/src/server.ts +49 -15
  122. package/src/store-memory.ts +24 -32
  123. package/src/store-postgres.ts +43 -53
  124. package/src/store-presence-polling.ts +59 -34
  125. package/src/store-redis-core.ts +595 -439
  126. package/src/store-redis-notify.ts +74 -26
  127. package/src/store-redis.ts +2 -3
  128. package/src/store-sqlite.ts +34 -40
  129. package/src/store.ts +14 -5
  130. package/dist/actor-HnGmrOZZ.d.ts.map +0 -1
  131. package/dist/client-9IeTsiAs.js.map +0 -1
  132. package/dist/server-DvIB8kj5.js.map +0 -1
  133. package/dist/session-clear-Bk_29eEw.d.ts.map +0 -1
  134. package/dist/store-Z2qjw50n.d.ts.map +0 -1
  135. package/dist/store-presence-polling-CzQ0wdtM.js +0 -94
  136. package/dist/store-presence-polling-CzQ0wdtM.js.map +0 -1
  137. package/dist/store-redis-notify-IiVdlYHc.js.map +0 -1
  138. package/dist/stream-buffer-Cx7AAPGq.js +0 -23
  139. package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
@@ -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 { 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\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}\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 /** 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 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 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 /** 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 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 (byEventId.get(row.id) === row) 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 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":";;;;;AA4FA,MAAM,gBAAgB;AAEtB,MAAM,UAAU,QACd,MAAM,IAAI,SAAS,IAAI,KAAK,SAAS;AAEvC,MAAM,uBAAiC;CACrC,gBAAgB;CAChB,eAAe;CACf,cAAc;CACd,aAAa;CACb,gBAAgB;CAChB,cAAc;CACd,cAAc;CACd,mBAAmB;CACnB,WAAW;CACX,UAAU;AACZ;AAEA,MAAM,QAAQ,UACZ,QAAQ,IAAI,KAAK,KAAK,IAAI;AAE5B,MAAM,YAAY,SAA2B;CAC3C,IAAI,IAAI;CACR,MAAM,IAAI;CACV,SAAS,gBAAgB,IAAI,OAAO;CACpC,OAAO,IAAI;CACX,WAAW,IAAI;CACf,WAAW,IAAI,KAAK,IAAI,SAAS;CACjC,OAAO,IAAI,QAAQ,EAAE,GAAG,IAAI,MAAM,IAAI;AACxC;AAEA,MAAM,WAAW,SAAqB;CACpC,IAAI,IAAI;CACR,MAAM,IAAI;CACV,SAAS,gBAAgB,IAAI,OAAO;CACpC,OAAO,IAAI;CACX,WAAW,IAAI;CACf,WAAW,IAAI,KAAK,IAAI,SAAS;AACnC;AAEA,MAAM,UAAU,QAAgB,SAAyB;CACvD,GAAG,SAAS,GAAG;CACf,UAAU,OAAO;CACjB,MAAM,OAAO;CACb,gBAAgB,KAAK,OAAO,SAAS,cAAc;CACnD,eAAe,KAAK,OAAO,SAAS,aAAa;CACjD,cAAc,OAAO,SAAS;CAC9B,aAAa,OAAO,SAAS;CAC7B,gBAAgB,KAAK,OAAO,SAAS,cAAc;CACnD,cAAc,OAAO,SAAS;CAC9B,cAAc,KAAK,OAAO,SAAS,YAAY;CAC/C,mBAAmB,OAAO,SAAS;CACnC,WAAW,OAAO,SAAS;CAC3B,UAAU,KAAK,OAAO,SAAS,QAAQ;AACzC;AAEA,MAAM,YAAY,YAAkC;CAClD,UAAU,OAAO;CACjB,MAAM,OAAO;CACb,UAAU,OAAO;CACjB,MAAM,OAAO;CACb,gBAAgB,KAAK,OAAO,SAAS,cAAc;CACnD,eAAe,KAAK,OAAO,SAAS,aAAa;CACjD,cAAc,OAAO,SAAS;CAC9B,aAAa,OAAO,SAAS;CAC7B,gBAAgB,KAAK,OAAO,SAAS,cAAc;CACnD,cAAc,OAAO,SAAS;CAC9B,cAAc,KAAK,OAAO,SAAS,YAAY;CAC/C,mBAAmB,OAAO,SAAS;CACnC,WAAW,OAAO,SAAS;CAC3B,UAAU,KAAK,OAAO,SAAS,QAAQ;AACzC;AAEA,MAAM,eAAe,GAAW,MAC9B,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;;AAG3B,MAAM,YAAY,GAAiB,MACjC,YAAY,EAAE,UAAU,EAAE,QAAQ,MACjC,EAAE,SAAS,OACR,EAAE,SAAS,QACR,EAAE,QAAQ,MAAM,EAAE,QAAQ,KAC3B,IACF,EAAE,SAAS,OACT,KACA,YAAY,EAAE,MAAM,EAAE,IAAI;AAElC,MAAM,aACJ,UACA,MACA,UAEA,SAAS,OAAO,GAAG,SAAS,SAAS,UAAU,GAAG,SAAS,SAAS;AAEtE,MAAM,SAAS,OAAiC,SAC9C,SAAA,iBAA8B,UAAU,QAAQ,MAAM,SAAS,IAAI;AAErE,MAAM,UAAU,GAAc,MAC5B,EAAE,QAAQ,EAAE,SAAS,YAAY,EAAE,UAAU,EAAE,QAAQ;AAEzD,MAAM,eAAe,WAAmB,gBACtC,GAAG,UAAU,QAAQ;AAEvB,MAAM,cAAc,WAAmB,QACrC,GAAG,UAAU,QAAQ,UAAU,IAAI,UAAU,IAAI,MAAM,IAAI,KAAK,EAAE,QAAQ,IAAI;;AAGhF,MAAM,YACJ,cACkC;CAClC,IAAI,CAAC,WAAW,aAAa;CAC7B,MAAM,QAAQ,IAAI,IAAI,UAAU,KAAK,aAAa,SAAS,IAAI,CAAC;CAChE,QAAQ,WAAW,MAAM,IAAI,OAAO,QAAQ;AAC9C;;AAGA,MAAM,SAAS,YAA6B;CAC1C,UAAU,OAAO;CACjB,MAAM,OAAO;CACb,OAAO,OAAO;AAChB;AAEA,SAAgB,OAAO,UAA8B,CAAC,GAAY;CAChE,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,aAAa,QAAQ,OAAO;CAElC,MAAM,2BAAW,IAAI,IAAwB;;CAE7C,MAAM,0BAAU,IAAI,IAAiC;;CAErD,MAAM,4BAAY,IAAI,IAAiB;;CAEvC,MAAM,4BAAY,IAAI,IAGpB;CACF,MAAM,sCAAsB,IAAI,IAG9B;CACF,MAAM,+BAAe,IAAI,IAAyB;CAClD,MAAM,oCAAoB,IAAI,IAAoB;CAClD,MAAM,oCAAoB,IAAI,IAAqC;;CAEnE,MAAM,+BAAe,IAAI,IAGvB;CACF,MAAM,sCAAsB,IAAI,IAG9B;CAEF,MAAM,iBAAiB,WAAmB,SAAsB;EAC9D,MAAM,OAAO,kBAAkB,IAAI,SAAS;EAC5C,IAAI,CAAC,MAAM;EACX,KAAK,MAAM,YAAY,MACrB,KAAK,MAAM,OAAO,MAAM,SAAS,GAAG;CAExC;CAEA,MAAM,SAAS,cAAkC;EAC/C,IAAI,MAAM,SAAS,IAAI,SAAS;EAChC,IAAI,CAAC,KAAK;GACR,MAAM;IAAE,MAAM,CAAC;IAAG,QAAQ;IAAG,YAAY;GAAE;GAC3C,SAAS,IAAI,WAAW,GAAG;EAC7B;EACA,OAAO;CACT;CAEA,MAAM,SAAS,WAAmB,UAAmC;EACnE,MAAM,MAAM,SAAS,IAAI,SAAS;EAClC,OAAO,OAAO,QAAQ,IAAI,SACtB,IAAI,KAAK,QAAQ,IAAI,SAAS,KAC9B,KAAA;CACN;CAEA,MAAM,gBAAgB,cACpB,SAAS,IAAI,SAAS,CAAC,EAAE,cAAc;;CAGzC,MAAM,WAAW,WAAmB,OAAgC;EAClE,MAAM,MAAM,UAAU,IAAI,EAAE;EAC5B,OAAO,KAAK,cAAc,aAAa,IAAI,QAAQ,aAAa,SAAS,IACrE,KAAA,IACA;CACN;CAEA,MAAM,aAAa,cAA2C;EAC5D,IAAI,MAAM,QAAQ,IAAI,SAAS;EAC/B,IAAI,CAAC,KAAK;GACR,sBAAM,IAAI,IAAI;GACd,QAAQ,IAAI,WAAW,GAAG;EAC5B;EACA,OAAO;CACT;CAEA,MAAM,YAAY,WAAmB,QACnC,QAAQ,IAAI,SAAS,CAAC,EAAE,IAAI,UAAU,IAAI,UAAU,IAAI,MAAM,IAAI,KAAK,CAAC;CAE1E,MAAM,iBACJ,WACA,aACA,UACW,GAAG,YAAY,WAAW,WAAW,EAAE,QAAQ;CAE5D,MAAM,uBAAuB,WAAmB,QAAuB;EACrE,MAAM,SAAS,WAAW,WAAW,GAAG;EACxC,MAAM,cAAc,aAAa,IAAI,MAAM;EAC3C,IAAI,CAAC,aAAa;EAClB,KAAK,MAAM,OAAO,aAAa;GAC7B,MAAM,QAAQ,kBAAkB,IAAI,GAAG,KAAK,KAAK;GACjD,IAAI,SAAS,GAAG;IACd,kBAAkB,OAAO,GAAG;IAC5B,oBAAoB,OAAO,GAAG;GAChC,OACE,kBAAkB,IAAI,KAAK,IAAI;EAEnC;EACA,aAAa,OAAO,MAAM;CAC5B;;CAGA,MAAM,eACJ,WACA,OACA,UACkB;EAClB,MAAM,MAAM,SAAS,IAAI,SAAS;EAClC,IAAI,CAAC,KAAK,OAAO;EACjB,KACE,IAAI,SAAS,KAAK,IAAI,GAAG,QAAQ,IAAI,MAAM,GAC3C,SAAS,IAAI,KAAK,QAClB,UAAU,GACV;GACA,MAAM,MAAM,IAAI,KAAK;GACrB,IAAI,MAAM,OAAO,IAAI,IAAI,GAAG,OAAO,IAAI;EACzC;EACA,OAAO;CACT;;CAGA,MAAM,WAAW,WAAmB,WAAyB;EAC3D,MAAM,OAAO,OAAO;EACpB,oBAAoB,WAAW,MAAM,MAAM,CAAC;EAC5C,OAAO,WAAW;EAClB,OAAO,WAAW,cAAc;EAChC,IAAI,OAAO,SAAS,SAClB,OAAO,OAAO;OACT,IAAI,OAAO,SAAS,QAAQ;GACjC,OAAO,UAAU;GACjB,OAAO,OAAO,OAAO,MAAM,OAAO,WAAW;EAC/C,OACE,OAAO,OAAO,YAAY,WAAW,MAAM,OAAO,KAAK;CAE3D;CAEA,MAAM,cACJ,WACA,cACY;EACZ,MAAM,SAAS,SAAS,SAAS;EACjC,KAAK,MAAM,UAAU,QAAQ,IAAI,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC,GACxD,IACE,OAAO,SAAS,QAChB,OAAO,SAAS,aAAa,QAC7B,OAAO,MAAM,GAEb,OAAO;EAGX,OAAO;CACT;;CAGA,MAAM,sBAAsB,WAAmB,UAA4B;EACzE,IAAI,MAAM,SAAS,aAAa,SAAS,GACvC,MAAM,uBAAuB,WAAW,KAAK;EAE/C,MAAM,SAAS,SAAS,WAAW,KAAK;EACxC,IACE,CAAC,UACD,OAAO,SAAS,MAAM,SACtB,OAAO,SAAS,iBAAiB,MAAM,WACvC,OAAO,SAAS,aAAa,MAE7B,MAAM,uBAAuB,WAAW,KAAK;CAEjD;CAEA,MAAM,cAAc,WAAmB,QAAgB,QAAoB;EACzE,OAAO,SAAS,WAAW,IAAI,KAAK,GAAG;EACvC,oBAAoB,WAAW,MAAM,MAAM,CAAC;CAC9C;CAEA,MAAM,SAAS,EACb,WACA,QACA,QACA,KACA,WACA,kBAQe;EACf,MAAM,WAAW,OAAO;EACxB,MAAM,MAAM,MAAM,WAAW,OAAO,IAAK;EAEzC,IAAI,SAAS,gBAAgB,MAAM;GACjC,SAAS,gBAAgB;GACzB,IAAI,gBAAgB,KAAA,KAAa,SAAS,gBAAgB,aAAa;IAErE,SAAS,gBAAgB;IACzB,SAAS,cAAc;IACvB,SAAS,iBAAiB;IAC1B,WAAW,WAAW,QAAQ,GAAG;IACjC,OAAO,OAAO,QAAQ,GAAG;GAC3B;EACF;EACA,SAAS,gBAAgB;EACzB,SAAS,mBAAmB,IAAI,KAAK,GAAG;EACxC,SAAS,gBAAgB,IAAI,KAAK,GAAG;EACrC,SAAS,cAAc;EACvB,SAAS,iBAAiB,IAAI,KAAK,SAAS;EAC5C,OAAO,OAAO,QAAQ,GAAG;CAC3B;CAEA,MAAM,iBACJ,WACA,WACU;EACV,MAAM,WAAW,OAAO,QACrB,cACC,UAAU,OAAO,KAAA,CACrB;EAEA,IAAI,IADoB,IAAI,SAAS,KAAK,cAAc,UAAU,EAAE,CACtD,CAAC,CAAC,SAAS,SAAS,QAChC,MAAM,IAAI,QACR,2BACA,iDACF;EAEF,MAAM,WAAW,SACd,KAAK,cAAc,QAAQ,WAAW,UAAU,EAAE,CAAC,CAAC,CACpD,QAAQ,QAAoB,QAAQ,KAAA,CAAS;EAChD,MAAM,UAAU,SAAS,MAAM,QAAQ,IAAI,cAAc,SAAS;EAClE,IAAI,SACF,MAAM,IAAI,QACR,2BACA,aAAa,QAAQ,GAAG,oCAC1B;EAEF,OAAO;CACT;;CAGA,MAAM,mBACJ,WACA,cACS;EACT,MAAM,MAAM,QAAQ,IAAI,SAAS;EACjC,IAAI,CAAC,KAAK;EACV,KAAK,MAAM,YAAY,aAAa,CAAC,GAAG;GACtC,IAAI,CAAC,SAAS,KAAK;GACnB,MAAM,SAAS,IAAI,IAAI,UAAU,SAAS,MAAM,SAAS,IAAI,MAAM,CAAC,CAAC;GACrE,IAAI,QAAQ,SAAS,OAAO,OAAO,QAAQ,SAAS,IAAI;EAC1D;CACF;;CAGA,MAAM,WACJ,WACA,MACA,QACA,MACA,cACS;EACT,MAAM,MAAM,UAAU,SAAS;EAC/B,KAAK,SAAS,KAAK,WAAW;GAC5B,KAAK,MAAM,UAAU,OAAO,OAAO,CAAE,WAAW,CAAC,GAAG;IAClD,MAAM,MAAM,UAAU,OAAO,UAAU,OAAO,MAAM,IAAI,KAAK;IAC7D,IAAI,OAAO,SAAS,MAAM;KACxB,IAAI,IAAI,KAAK;MACX,UAAU,OAAO;MACjB,MAAM;MACN,MAAM;MACN,UAAU,IAAI,QAAQ;MACtB,MAAM,IAAI;MACV,UAAU,cAAc;MACxB,OAAO,CAAC;MACR,QAAQ;MACR,6BAAa,IAAI,IAAI;MACrB,OAAO;KACT,CAAC;KACD;IACF;IACA,IAAI,SAAS,IAAI,IAAI,GAAG;IACxB,IAAI,CAAC,QAAQ;KACX,SAAS;MACP,UAAU,OAAO;MACjB,MAAM,OAAO;MACb,MAAM;MACN,UAAU,aAAa,SAAS;MAChC,MAAM;MACN,UAAU,cAAc;MACxB,OAAO,CAAC;MACR,QAAQ;MACR,6BAAa,IAAI,IAAI;MACrB,OAAO;KACT;KACA,IAAI,IAAI,KAAK,MAAM;IACrB;IACA,OAAO,MAAM,KAAK,IAAI,KAAK;IAC3B,OAAO,SAAS,IAAI;GACtB;EACF,CAAC;EACD,MAAM,aAAa,aAAa,SAAS;EACzC,KAAK,MAAM,YAAY,aAAa,CAAC,GAAG;GACtC,MAAM,MAAM,SAAS;GACrB,IAAI,CAAC,KAAK;GACV,MAAM,QAAQ,KAAK,MAAM,QAAQ,MAAM,IAAI,OAAO,IAAI,IAAI,CAAC;GAC3D,IAAI,CAAC,OAAO;GACZ,MAAM,MAAM,UAAU,SAAS,MAAM,IAAI,MAAM,CAAC;GAChD,MAAM,SAAS,IAAI,IAAI,GAAG;GAC1B,IAAI,CAAC,QAAQ;IACX,MAAM,WAAW,IAAI,WAAW,aAAa;IAC7C,IAAI,IAAI,KAAK;KACX,UAAU,SAAS;KACnB,MAAM,IAAI;KACV,MAAM;KACN;KACA,MAAM,IAAI,WACN,YAAY,WAAW,UAAU,IAAI,KAAK,IAC1C,MAAM;KACV,UAAU,cAAc;KACxB,OAAO,CAAC;KACR,QAAQ;KACR,6BAAa,IAAI,IAAI;KACrB,OAAO,IAAI;IACb,CAAC;GACH,OAAO;IACL,OAAO,QAAQ,IAAI;IAGnB,OAAO,SAAS,YAAY,WAAW,OAAO,UAAU,IAAI,KAAK;GACnE;EACF;CACF;CAEA,MAAM,UACJ,WACA,QACA,KACA,WACA,UACU;EACV,MAAM,MAAM,MAAM,SAAS;EAC3B,MAAM,OAAO,OAAO,GAAG;EACvB,MAAM,WAAW,OAAO,KAAK,WAAW,YAAiB;GACvD,IAAI,UAAU,MAAM,WAAW;GAC/B,MAAM,UAAU;GAChB,SAAS,gBAAgB,UAAU,OAAO;GAC1C,OAAO,OAAO,IAAI;GAClB;GACA,WAAW,IAAI,KAAK,GAAG;GACvB,OAAO,QACH,EAAE,GAAG,MAAM,IACX,UAAU,QACR,EAAE,GAAG,UAAU,MAAM,IACrB;EACR,EAAE;EACF,KAAK,MAAM,OAAO,UAChB,IAAI,QAAQ,WAAW,IAAI,EAAE,GAC3B,MAAM,IAAI,QACR,2BACA,aAAa,IAAI,GAAG,iBACtB;EAGJ,KAAK,MAAM,OAAO,UAAU;GAC1B,IAAI,KAAK,KAAK,GAAG;GACjB,UAAU,IAAI,IAAI,IAAI,GAAG;EAC3B;EACA,QAAQ,WAAW,UAAU,QAAQ,MAAM,SAAS;EACpD,OAAO;CACT;;CAGA,MAAM,wBACJ,WACA,eACS;EACT,MAAM,SAAS,GAAG,UAAU;EAC5B,KAAK,MAAM,CAAC,KAAK,aAAa,WAAW;GACvC,IAAI,CAAC,IAAI,WAAW,MAAM,KAAK,SAAS,SAAS,YAAY;GAC7D,UAAU,OAAO,GAAG;GACpB,kBAAkB,OAAO,GAAG,IAAI,QAAQ,SAAS,OAAO;EAC1D;EACA,KAAK,MAAM,CAAC,KAAK,aAAa,qBAAqB;GACjD,IAAI,CAAC,IAAI,WAAW,MAAM,KAAK,SAAS,SAAS,YAAY;GAC7D,oBAAoB,OAAO,GAAG;GAC9B,kBAAkB,OAAO,GAAG;EAC9B;CACF;CAEA,MAAM,eACJ,WACA,aACA,cACA,yBACmB;EACnB,MAAM,MAAM,YAAY,WAAW,WAAW;EAC9C,MAAM,aAAa,aAAa,SAAS;EACzC,MAAM,mBAAmB,wBAAwB;EACjD,MAAM,SAAS,UAAU,IAAI,GAAG;EAChC,MAAM,OAAO,UAAU,OAAO,SAAS,aAAa,SAAS,KAAA;EAC7D,IAAI,YACF,SAAS,qBAAqB,KAAA,KAAa,KAAK,SAAS,oBACrD,OACA;EACN,IAAI,qBAAqB,KAAA,GAAW;GAClC,MAAM,SAAS,GAAG,IAAI;GACtB,KAAK,MAAM,CAAC,eAAe,aAAa,qBACtC,IACE,cAAc,WAAW,MAAM,KAC/B,SAAS,SAAS,cAClB,SAAS,SAAS,qBACjB,CAAC,aAAa,SAAS,QAAQ,UAAU,QAE1C,YAAY;EAGlB;EACA,MAAM,WAAW,YACb;GAAE,OAAO,UAAU;GAAO,OAAO,gBAAgB,UAAU,KAAK;EAAE,IAClE;EACJ,MAAM,aAAa,KAAK,IAAI,UAAU,SAAS,GAAG,aAAa,CAAC;EAChE,OAAO;GACL,WAAW,MAAM,SAAS;GAC1B;GACA,SAAS,SAAS,IAAI,SAAS,CAAC,EAAE,QAAQ,CAAC,EAAA,CACxC,QACE,QACC,IAAI,QAAQ,eACX,iBAAiB,KAAA,KAAa,IAAI,SAAS,aAChD,CAAC,CACA,IAAI,OAAO;GACd,GAAI,aAAa,IAAI,EAAE,WAAW,IAAI,CAAC;EACzC;CACF;;CAGA,MAAM,cACJ,WACA,UACA,UACuB;EACvB,IAAI,SAAS,aAAa,SAAS,GAAG,OAAO,KAAA;EAC7C,KAAK,MAAM,UAAU,QAAQ,IAAI,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC,GACxD,IACE,OAAO,aAAa,YACpB,OAAO,SAAS,SAChB,OAAO,SAAS,aAAa,MAE7B,OAAO;CAIb;CAEA,OAAO;EACL,MAAM,OAAO,WAAW,QAAQ,eAAe;GAC7C,MAAM,YAAY,eAAe;GACjC,IAAI,OAAO,WAAW,GACpB,OAAO;IAAE,QAAQ,CAAC;IAAG,YAAY,WAAW,WAAW,SAAS;GAAE;GAMpE,MAAM,WAAW,cAAc,WAAW,MAAM;GAChD,IAAI,SAAS,SAAS,GAAG;IACvB,IAAI,SAAS,WAAW,OAAO,QAC7B,OAAO;KACL,QAAQ,iBAAiB,QAAQ,SAAS,IAAI,QAAQ,CAAC;KACvD,YAAY,WAAW,WAAW,SAAS;IAC7C;IAEF,MAAM,IAAI,QACR,2BACA,eAAe,SAAS,OAAO,wBAAwB,OAAO,SAAS,SAAS,OAAO,cACzF;GACF;GAEA,MAAM,cAAc,eAAe;GACnC,IAAI,gBAAgB,KAAA,KAAa,eAAe,aAAa,SAAS,GACpE,MAAM,oBACJ,6BAA6B,YAAY,2BAA2B,UAAU,EAChF;GAIF,KAAK,MAAM,SAAS,QAClB,IAAI,MAAM,OAAO,mBAAmB,WAAW,MAAM,KAAK;GAG5D,MAAM,WAAW,OAAO,WAAW,QAAQ,MAAM,IAAI,GAAG,SAAS;GACjE,cAAc,WAAW,QAAQ;GACjC,OAAO;IACL,QAAQ,SAAS,IAAI,QAAQ;IAC7B,YAAY,WAAW,WAAW,SAAS;GAC7C;EACF;EAEA,MAAM,MAAM,EAAE,WAAW,aAAa,SAAS;GAC7C,IAAI,OAAO,mBAAmB,WAAW,KAAK;GAC9C,MAAM,MAAM,SAAS,IAAI,SAAS;GAClC,MAAM,OAAO,OAAO,GAAG;GACvB,MAAM,aAAa,KAAK,cAAc;GACtC,IAAI,gBAAgB,KAAA,KAAa,eAAe,cAAc,GAC5D,OAAO;IAAE,SAAS;IAAM,OAAO;IAAM;GAAW;GAElD,IAAI,gBAAgB,KAAA,KAAa,gBAAgB,MAC/C,OAAO;IAAE,SAAS;IAAO,OAAO;IAAM;GAAW;GAEnD,IAAI,SAAS,KAAK,eAAe,MAC/B,OAAO;IAAE,SAAS;IAAM,OAAO;IAAM;GAAW;GAElD,MAAM,CAAC,UAAU,OACf,WACA,CAAC;IAAE,MAAM;IAAkB,SAAS;GAAK,CAAC,GAC1C,MAAM,IAAI,GACV,KAAA,CACF;GACA,MAAM,SAAS,CAAC,CAAC,aAAa,OAAQ;GAGtC,MAAM,MAAM,QAAQ,IAAI,SAAS;GACjC,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,CAAC,GAAG;IACrC,IAAI,OAAO,SAAS,MAAM,oBAAoB,WAAW,MAAM,MAAM,CAAC;IACtE,IAAI,OAAO,SAAS,OAAO;KACzB,IAAK,OAAO,GAAG;KACf;IACF;IACA,OAAO,WAAW,OAAQ;IAC1B,OAAO,OAAO;IACd,OAAO,WAAW,cAAc;IAChC,OAAO,YAAY,MAAM;GAC3B;GACA,cAAc,WAAW,CAAC,MAAO,CAAC;GAClC,OAAO;IAAE,SAAS;IAAM,OAAO,OAAQ;IAAO,YAAY,OAAQ;GAAM;EAC1E;EAEA,MAAM,QAAQ,WAAW;GACvB,MAAM,MAAM,SAAS,IAAI,SAAS;GAClC,IAAI,CAAC,OAAO,IAAI,UAAU,IAAI,aAAa,GACzC,OAAO;IAAE,SAAS;IAAG,MAAM;GAAK;GAElC,MAAM,UAAU,IAAI,KAAK,OACvB,GACA,KAAK,IAAI,eAAe,IAAI,aAAa,IAAI,IAAI,MAAM,CACzD;GACA,IAAI,UAAU,QAAQ;GACtB,KAAK,MAAM,OAAO,SAEhB,IAAI,UAAU,IAAI,IAAI,EAAE,MAAM,KAAK,UAAU,OAAO,IAAI,EAAE;GAE5D,MAAM,OAAO,IAAI,WAAW,IAAI,aAAa;GAE7C,IAAI,MAAM,qBAAqB,WAAW,IAAI,UAAU;GACxD,OAAO;IAAE,SAAS,QAAQ;IAAQ;GAAK;EACzC;EAEA,MAAM,QAAQ,EAAE,WAAW,YAAY;GACrC,MAAM,EAAE,QAAQ;GAChB,MAAM,MAAM,UAAU,SAAS,MAAM,IAAI,MAAM,CAAC;GAChD,MAAM,OAAO,OAAO,SAAS,IAAI,SAAS,CAAC;GAC3C,IAAI,SAAS,QAAQ,IAAI,SAAS,CAAC,EAAE,IAAI,GAAG;GAC5C,IAAI,CAAC,QAAQ;IACX,MAAM,SAAS,aAAa,SAAS;IACrC,IAAI,SAAS,QACX,OAAO;KACL,QAAQ,cAAc,SAAS,MAAM,IAAI,MAAM,MAAM;KACrD;IACF;IAEF,MAAM,WAAW,IAAI,WAAW,SAAS;IACzC,SAAS;KACP,UAAU,SAAS;KACnB,MAAM,IAAI;KACV,MAAM;KACN;KACA,MAAM,IAAI,WACN,YAAY,WAAW,UAAU,IAAI,KAAK,IAC1C;KACJ,UAAU,cAAc;KACxB,OAAO,CAAC;KACR,QAAQ;KACR,6BAAa,IAAI,IAAI;KACrB,OAAO,IAAI;IACb;IACA,UAAU,SAAS,CAAC,CAAC,IAAI,KAAK,MAAM;GACtC,OAAO;IACL,OAAO,QAAQ,IAAI;IACnB,OAAO,SAAS,YAAY,WAAW,OAAO,UAAU,IAAI,KAAK;GACnE;GACA,OAAO;IAAE,QAAQ,SAAS,MAAM;IAAG;GAAK;EAC1C;EAEA,MAAM,QAAQ,WAAW;GACvB,OAAO,CAAC,GAAI,QAAQ,IAAI,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC,CAAE,CAAC,CACjD,QAAQ,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,IAAI,CAAC,CACnE,IAAI,QAAQ,CAAC,CACb,SAAS,QAAQ;EACtB;EAEA,MAAM,KAAK,WAAW,MAAM;GAC1B,MAAM,MAAM,SAAS,IAAI,SAAS;GAClC,IAAI,CAAC,KAAK,OAAO,CAAC;GAClB,MAAM,OAAO,OAAO,GAAG;GACvB,MAAM,QAAQ,KAAK,IACjB,MAAM,cAAc,GACpB,IAAI,aAAa,GACjB,IAAI,MACN;GACA,MAAM,UAAU,KAAK,IAAI,MAAM,gBAAgB,MAAM,IAAI;GACzD,IAAI,WAAW,OAAO,OAAO,CAAC;GAC9B,OAAO,IAAI,KACR,MAAM,QAAQ,IAAI,QAAQ,UAAU,IAAI,MAAM,CAAC,CAC/C,IAAI,QAAQ;EACjB;EAEA,MAAM,eAAe,EACnB,WACA,QACA,OACA,aACA,SACA,WACA,aACA,WACC;GACD,gBAAgB,WAAW,SAAS;GACpC,MAAM,YAAY,MAAM,IAAI;GAC5B,MAAM,MAAM,UAAU,QAAQ;GAC9B,MAAM,YAAY,IAAI,KAAK,eAAe,MAAM,KAAK;GACrD,MAAM,WAAW,IAAI,KAClB,WAAW,CAAC,EAAA,CAAG,KAAK,QACnB,UAAU,IAAI,UAAU,IAAI,MAAM,IAAI,KAAK,CAC7C,CACF;GACA,MAAM,SAAS,SAAS,SAAS;GACjC,MAAM,WAAqB,CAAC;GAC5B,IAAI,UAAuB;GAE3B,KAAK,MAAM,CAAC,KAAK,WAAW,QAAQ,IAAI,SAAS,KAAK,CAAC,GAAG;IACxD,IACE,OAAO,SAAS,QACf,YAAY,KAAA,KAAa,OAAO,OAAO,WACxC,OAAO,SAAS,aAAa,QAC7B,CAAC,OAAO,MAAM,KACd,SAAS,IAAI,GAAG,GAEhB;IAEF,MAAM,SAAS,OAAO,SAAS;IAC/B,IAAI,UAAU,OAAO,QAAQ,IAAI,KAAK;KACpC,IAAI,CAAC,WAAW,SAAS,SAAS,UAAU,IAAI,KAAK,MAAM;KAC3D;IACF;IACA,SAAS,KAAK,MAAM;GACtB;GAEA,IAAI,SAAS,WAAW,GACtB,OAAO,UAAU;IAAE,SAAS;IAAQ;GAAQ,IAAI,EAAE,SAAS,UAAU;GAEvE,MAAM,aAAa,aAAa,SAAS;GAezC,OAAO;IACL,SAAS;IACT,QAhBc,SACb,KAAK,WAAW;KACf,MAAM,OAAO,MAAM;MACjB;MACA;MACA;MACA,KAAK;MACL;MACA;KACF,CAAC;KACD,IAAI,aAAa,GAAG,KAAK,aAAa;KACtC,OAAO;IACT,CAAC,CAAC,CACD,SAAS,MAGI;IACd,MAAM,OAAO,SAAS,IAAI,SAAS,CAAC;GACtC;EACF;EAEA,MAAM,YAAY,EAAE,WAAW,QAAQ,QAAQ,OAAO,eAAe;GACnE,MAAM,MAAM,MAAM,IAAI;GACtB,MAAM,YAAY,IAAI,KAAK,eAAe,IAAI,QAAQ,IAAI,KAAK;GAC/D,MAAM,aAAa,aAAa,SAAS;GACzC,MAAM,UAAqB,CAAC;GAC5B,MAAM,aAAwB,CAAC;GAC/B,MAAM,UAAU,OAAO,UACpB,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,YAAY,EAAE,UAAU,EAAE,QAAQ,CACnE;GACA,KAAK,MAAM,EAAE,SAAS,GAAG,SAAS,SAAS;IACzC,IAAI,IAAI,SAAS,YAAY;KAC3B,WAAW,KAAK,GAAG;KACnB;IACF;IACA,MAAM,SAAS,SAAS,WAAW,GAAG;IACtC,IAAI,CAAC,QAAQ;IACb,MAAM,WAAW,OAAO;IAExB,IAAI,OAAO,SAAS,IAAI,SAAS,OAAO,WAAW,IAAI,OAAO;IAC9D,IAAI,OAAO,SAAS,IAAI,SAAS,SAAS,eAAe,SAAS;KAChE,WAAW,KAAK,GAAG;KACnB;IACF;IACA,IACE,SAAS,aAAa,QACtB,SAAS,gBAAgB,UACzB,SAAS,mBAAmB,QAC5B,SAAS,iBAAiB,KAC1B;KACA,SAAS,iBAAiB,IAAI,KAAK,SAAS;KAC5C,QAAQ,KAAK,GAAG;IAClB;GACF;GACA,OAAO;IAAE;IAAS;GAAW;EAC/B;EAEA,MAAM,gBAAgB,EACpB,WACA,UACA,MACA,OACA,SACA,QACA,aACC;GACD,IAAI,SAAS,aAAa,SAAS,GAAG,OAAO,EAAE,SAAS,aAAa;GAErE,MAAM,SAAS,SAAS,WAAW;IADrB;IAAU;IAAM;GACO,CAAC;GACtC,IAAI,CAAC,QAAQ;IACX,IAAI,CAAC,MAAM,WAAW,KAAK,GACzB,MAAM,IAAI,UACR,qBAAqB,MAAM,eAAe,UAAU,EACtD;IAEF,OAAO,EAAE,SAAS,aAAa;GACjC;GACA,gBAAgB,WAAW,SAAS;GAEpC,IAAI,OAAO,SAAS,OAAO;IACzB,MAAM,aAAa,OAAO,YAAY,IAAI,KAAK;IAC/C,IAAI,CAAC,cAAc,WAAW,YAAY,SACxC,OAAO,EAAE,SAAS,aAAa;IAEjC,MAAM,eAAe,OAAO,KAAK,UAAU,MAAM,EAAE;IACnD,IACE,WAAW,iBAAiB,WAAW,aAAa,UACpD,WAAW,iBAAiB,MACzB,IAAI,WAAW,OAAO,aAAa,OACtC,GAEA,MAAM,IAAI,QACR,2BACA,2DACF;IAEF,MAAM,WAAW,cAAc,WAAW,MAAM;IAChD,IACE,SAAS,WAAW,OAAO,UAC3B,SAAS,MACN,QACC,IAAI,OAAO,aAAa,YACxB,IAAI,MAAM,SAAS,QACnB,IAAI,MAAM,UAAU,SACpB,IAAI,MAAM,YAAY,OAC1B,GAEA,MAAM,IAAI,QACR,2BACA,2DACF;IAEF,OAAO;KACL,SAAS;KACT,QACE,OAAO,WAAW,IACd,CAAC,IACD,iBAAiB,QAAQ,SAAS,IAAI,QAAQ,CAAC;IACvD;GACF;GACA,MAAM,WAAW,OAAO;GACxB,IACE,SAAS,iBAAiB,WAC1B,SAAS,aAAa,QACtB,SAAS,gBAAgB,MAEzB,OAAO,EAAE,SAAS,aAAa;GAIjC,IADiB,cAAc,WAAW,MAC/B,CAAC,CAAC,SAAS,GACpB,MAAM,IAAI,QACR,2BACA,oDACF;GAEF,MAAM,WAAW,OAAO,WAAW,QAAQ,MAAM,IAAI,GAAG,WAAW;IACjE;IACA;IACA;IACA;GACF,CAAC;GACD,IAAI,OAAO,SAAS,OAAO,OAAO,YAAY,MAAM;GACpD,OAAO,YAAY,IAAI,OAAO;IAC5B;IACA,kBAAkB,OAAO,KAAK,UAAU,MAAM,EAAE;GAClD,CAAC;GACD,QAAQ,WAAW,MAAM;GACzB,cAAc,WAAW,QAAQ;GACjC,OAAO;IAAE,SAAS;IAAa,QAAQ,SAAS,IAAI,QAAQ;GAAE;EAChE;EAEA,MAAM,YAAY,EAChB,WACA,UACA,MACA,OACA,SACA,OACA,aACA,gBACC;GACD,IAAI,SAAS,aAAa,SAAS,GACjC,OAAO;IAAE,SAAS;IAAc,cAAc;GAAE;GAElD,MAAM,SAAS,SAAS,WAAW;IAAE;IAAU;IAAM;GAAM,CAAC;GAC5D,IAAI,CAAC,QAAQ;IACX,IAAI,CAAC,MAAM,WAAW,KAAK,GACzB,MAAM,IAAI,UACR,qBAAqB,MAAM,eAAe,UAAU,EACtD;IAEF,OAAO;KAAE,SAAS;KAAc,cAAc;IAAE;GAClD;GACA,MAAM,WAAW,OAAO;GACxB,IACE,OAAO,SAAS,SAChB,SAAS,iBAAiB,WACzB,SAAS,gBAAgB,QACxB,SAAS,sBAAsB,SAEjC,OAAO;IACL,SAAS;IACT,cAAc,OAAO,SAAS,QAAQ,SAAS,eAAe;GAChE;GAEF,IACE,SAAS,sBAAsB,WAC/B,SAAS,gBAAgB,MAEzB,OAAO;IACL,SAAS,SAAS,WAAW,kBAAkB;IAC/C,cAAc,SAAS;GACzB;GAEF,IAAI,SAAS,UACX,OAAO;IAAE,SAAS;IAAiB,cAAc,SAAS;GAAa;GAEzE,SAAS,gBAAgB;GACzB,SAAS,YAAY;GACrB,MAAM,WAAW,MAAM,IAAI;GAC3B,SAAS,eAAe,IAAI,KAAK,QAAQ;GACzC,SAAS,oBAAoB;GAC7B,SAAS,cAAc;GACvB,SAAS,iBACP,iBAAiB,KAAA,KAAa,SAAS,gBAAgB,cACnD,OACA,IAAI,KAAK,SAAS,QAAQ,IAAI,YAAY;GAChD,IAAI,SAAS,gBAAgB,aAAa;IACxC,WAAW,WAAW,QAAQ,QAAQ;IACtC,OAAO;KACL,SAAS;KACT,cAAc,SAAS;IACzB;GACF;GACA,OAAO;IAAE,SAAS;IAAU,cAAc,SAAS;GAAa;EAClE;EAEA,MAAM,YAAY,WAAW;GAC3B,MAAM,aAAa,aAAa,SAAS;GACzC,MAAM,OAAoB,CAAC;GAC3B,KAAK,MAAM,UAAU,QAAQ,IAAI,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC,GAAG;IAC3D,IACE,OAAO,SAAS,QAChB,OAAO,QAAQ,cACf,OAAO,SAAS,aAAa,MAE7B;IAEF,KAAK,KAAK,OAAO,QAAQ,MAAM,WAAW,OAAO,IAAI,CAAE,CAAC;GAC1D;GACA,OAAO,KAAK,SAAS,MAAM;EAC7B;EAEA,MAAM,QAAQ,EAAE,WAAW,UAAU,OAAO,QAAQ,aAAa;GAC/D,MAAM,SAAS,WAAW,WAAW,UAAU,KAAK;GACpD,IAAI,CAAC,QACH,OAAO;IAAE,UAAU;IAAO,YAAY,WAAW,WAAW,SAAS;GAAE;GAEzE,gBAAgB,WAAW,SAAS;GACpC,IAAI,WAAW,QACb,QAAQ,WAAW,MAAM;QACpB;IACL,OAAO,SAAS,WAAW;IAC3B,OAAO,SAAS,eAAe;GACjC;GACA,OAAO;IAAE,UAAU;IAAM,YAAY,WAAW,WAAW,SAAS;GAAE;EACxE;EAEA,MAAM,UAAU,WAAW,aAAa,cAAc;GACpD,OAAO,YACL,WACA,aACA,cAAc,cACd,cAAc,oBAChB;EACF;EAEA,MAAM,WAAW,UAAU,aAAa;GACtC,OAAO,SAAS,KAAK,YACnB,YACE,QAAQ,WACR,aACA,QAAQ,cACR,QAAQ,oBACV,CACF;EACF;EAEA,MAAM,aAAa,WAAW,aAAa,QAAQ;GACjD,MAAM,MAAM,YAAY,WAAW,WAAW;GAC9C,MAAM,aAAa,aAAa,SAAS;GACzC,KAAK,MAAM,SAAS,OAAO,UAAU,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,GAAG;IAChE,IAAI,MAAM,QAAQ,YAAY;IAC9B,MAAM,aAAa,cAAc,WAAW,aAAa,MAAM,KAAK;IACpE,KAAK,MAAM,OAAO,MAAM,QAAQ,CAAC,GAAG;KAClC,MAAM,SAAS,SAAS,WAAW,GAAG;KACtC,IACE,CAAC,UACD,IAAI,SAAS,cACb,OAAO,SAAS,IAAI,SACpB,OAAO,SAAS,aAAa,MAE7B;KAEF,MAAM,SAAS,WAAW,WAAW,GAAG;KACxC,IAAI,SAAS,aAAa,IAAI,MAAM;KACpC,IAAI,CAAC,QAAQ;MACX,yBAAS,IAAI,IAAI;MACjB,aAAa,IAAI,QAAQ,MAAM;KACjC;KACA,IAAI,CAAC,OAAO,IAAI,UAAU,GAAG;MAC3B,OAAO,IAAI,UAAU;MACrB,kBAAkB,IAChB,aACC,kBAAkB,IAAI,UAAU,KAAK,KAAK,CAC7C;KACF;IACF;IAEA,MAAM,UAAU,UAAU,IAAI,GAAG;IACjC,IAAI,CAAC,WAAW,MAAM,SAAS,QAAQ,OAAO;KAC5C,IACE,WACA,MAAM,QAAQ,QAAQ,SACtB,QAAQ,SAAS,YACjB;MACA,MAAM,oBAAoB,cACxB,WACA,aACA,QAAQ,KACV;MACA,KAAK,kBAAkB,IAAI,iBAAiB,KAAK,KAAK,GACpD,oBAAoB,IAAI,mBAAmB;OACzC,OAAO,QAAQ;OACf,OAAO,gBAAgB,QAAQ,KAAK;OACpC,WAAW,IAAI,KAAK,QAAQ,SAAS;MACvC,CAAC;KAEL;KACA,UAAU,IAAI,KAAK;MACjB,OAAO,MAAM;MACb,OAAO,gBAAgB,MAAM,KAAK;MAClC,WAAW,MAAM,IAAI;KACvB,CAAC;KACD,oBAAoB,OAAO,UAAU;IACvC,OAAO,IACL,MAAM,QAAQ,QAAQ,UACrB,kBAAkB,IAAI,UAAU,KAAK,KAAK,GAE3C,oBAAoB,IAAI,YAAY;KAClC,OAAO,MAAM;KACb,OAAO,gBAAgB,MAAM,KAAK;KAClC,WAAW,MAAM,IAAI;IACvB,CAAC;GAEL;EACF;EAEA,UAAU;GACR,MAAM,IAAI,IAAI,aAAa,QAAQ,MAAM;IACvC,IAAI,eAAe,aAAa,IAAI,EAAE;IACtC,IAAI,CAAC,cAAc;KACjB,+BAAe,IAAI,IAAI;KACvB,aAAa,IAAI,IAAI,YAAY;IACnC;IACA,IAAI,SAAS,aAAa,IAAI,WAAW;IACzC,IAAI,CAAC,QAAQ;KACX,yBAAS,IAAI,IAAI;KACjB,aAAa,IAAI,aAAa,MAAM;IACtC;IAIA,MAAM,cAAc,MAAM,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK;IAGjD,MAAM,UAAmC,gBAAgB;IACzD,IAAI,eAAe;IACnB,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,MAAM,GAAG;KAGnD,MAAM,WAAW,OAAO,IAAI,KAAK;KACjC,IAAI,YAAY,SAAS,KAAK,KAAK,IAAI;KACvC,IAAI,UAAU,MACZ,OAAO,OAAO,KAAK;UAEnB,OAAO,IAAI,OAAO;MAChB,OAAO,gBAAgB,KAAK;MAC5B,MAAM,KAAK;MACX,IAAI,IAAI,KAAK,KAAK,EAAE;MACpB,WAAW,IAAI,KAAK,WAAW;KACjC,CAAC;KAEH,QAAQ,SAAS,gBAAgB,KAAK;KACtC,gBAAgB;IAClB;IACA,IAAI,OAAO,SAAS,GAAG,aAAa,OAAO,WAAW;IACtD,IAAI,aAAa,SAAS,GAAG,aAAa,OAAO,EAAE;IAInD,IAAI,iBAAiB,GAAG;IACxB,MAAM,OAAO,oBAAoB,IAAI,EAAE;IACvC,IAAI,CAAC,MAAM;IACX,MAAM,QAAuB;KAC3B;KACA,QAAQ;KACR,MAAM,KAAK;KACX,IAAI,IAAI,KAAK,KAAK,EAAE;IACtB;IACA,KAAK,MAAM,YAAY,MAAM,SAAS,KAAK;GAC7C;GAEA,MAAM,KAAK,IAAI;IACb,MAAM,eAAe,aAAa,IAAI,EAAE;IACxC,IAAI,CAAC,cAAc,OAAO,CAAC;IAC3B,MAAM,MAAM,MAAM,IAAI;IACtB,MAAM,OAAsB,CAAC;IAC7B,KAAK,MAAM,CAAC,aAAa,WAAW,cAAc;KAChD,KAAK,MAAM,CAAC,OAAO,UAAU,QAAQ;MAGnC,IAAI,MAAM,aAAa,KAAK;OAC1B,OAAO,OAAO,KAAK;OACnB;MACF;MACA,KAAK,KAAK;OACR;OACA;OACA,OAAO,gBAAgB,MAAM,KAAK;OAClC,MAAM,MAAM;OACZ,IAAI,IAAI,KAAK,MAAM,EAAE;OACrB,WAAW,IAAI,KAAK,MAAM,SAAS;MACrC,CAAC;KACH;KACA,IAAI,OAAO,SAAS,GAAG,aAAa,OAAO,WAAW;IACxD;IACA,IAAI,aAAa,SAAS,GAAG,aAAa,OAAO,EAAE;IACnD,OAAO;GACT;GAEA,UAAU,IAAI,SAAS;IACrB,IAAI,OAAO,oBAAoB,IAAI,EAAE;IACrC,IAAI,CAAC,MAAM;KACT,uBAAO,IAAI,IAAI;KACf,oBAAoB,IAAI,IAAI,IAAI;IAClC;IACA,KAAK,IAAI,OAAO;IAChB,aAAa;KACX,KAAK,OAAO,OAAO;KACnB,IAAI,KAAK,SAAS,GAAG,oBAAoB,OAAO,EAAE;IACpD;GACF;EACF;EAEA,OAAO,WAAW,MAAM;GACtB,MAAM,aAAa,MAAM,cAAc;GACvC,OAAO,EACL,CAAC,OAAO,iBAAuC;IAC7C,IAAI,OAAO,KAAK,IAAI,YAAY,aAAa,SAAS,IAAI,CAAC;IAC3D,IAAI,OAA4B;IAChC,IAAI,SAAS;IAEb,MAAM,cAAoB;KACxB,OAAO;IACT;IACA,IAAI,OAAO,kBAAkB,IAAI,SAAS;IAC1C,IAAI,CAAC,MAAM;KACT,uBAAO,IAAI,IAAI;KACf,kBAAkB,IAAI,WAAW,IAAI;IACvC;IACA,KAAK,IAAI,KAAK;IACd,MAAM,aAAmB;KACvB,IAAI,QAAQ;KACZ,SAAS;KACT,KAAK,OAAO,KAAK;KACjB,IAAI,KAAK,SAAS,GAAG,kBAAkB,OAAO,SAAS;KACvD,OAAO;IACT;IAEA,OAAO;KACL,MAAM,OAAuC;MAC3C,IAAI;OACF,SAAS;QACP,IAAI,QAAQ,OAAO;SAAE,OAAO,KAAA;SAAW,MAAM;QAAK;QAClD,MAAM,MAAM,SAAS,IAAI,SAAS;QAElC,MAAM,MAAM,KAAK,KAAK,KAAK,IAAI,GAAG,OAAO,IAAI,MAAM;QACnD,IAAI,KAAK;SACP,MAAM,QAAQ,SAAS,GAAG;SAC1B,OAAO,IAAI;SACX,OAAO;UAAE;UAAO,MAAM;SAAM;QAC9B;QAEA,MAAM,IAAI,SAAe,YAAY;SACnC,OAAO;QACT,CAAC;QACD,OAAO;OACT;MACF,SAAS,OAAO;OACd,KAAK;OACL,MAAM;MACR;KACF;KACA,MAAM,SAAyC;MAC7C,KAAK;MACL,OAAO;OAAE,OAAO,KAAA;OAAW,MAAM;MAAK;KACxC;IACF;GACF,EACF;EACF;CACF;AACF"}
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 { i as POLL_TIMINGS } from "./internal-Bc4PvfHk.js";
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-DYNyxCev.js.map
152
+ //# sourceMappingURL=store-polling-BZt2Rf3D.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"store-polling-DYNyxCev.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"}
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"}
@@ -1,4 +1,4 @@
1
- import { r as Clock, s as IdSource, t as A2Store } from "./store-Z2qjw50n.js";
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":";;;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;;iBAguCK,SAAS,UAAS,uBAA4B"}
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"}
@@ -1,11 +1,11 @@
1
1
  import { i as A2Error, r as sessionClearedError, s as supersededAttemptError, t as CLEAR_EVENT_TYPE } from "./session-clear-wHd5GCpZ.js";
2
- import { _ as nullProtoRecord, r as NOTIFY_TIMINGS } from "./internal-Bc4PvfHk.js";
3
- import { n as pollingStream, t as defaultSleep } from "./store-polling-DYNyxCev.js";
2
+ import { i as NOTIFY_TIMINGS, y as nullProtoRecord } from "./internal-CvsrprnU.js";
3
+ import { n as pollingStream, t as defaultSleep } from "./store-polling-BZt2Rf3D.js";
4
4
  import { r as platformWaitUntil } from "./platform-B4TnJtWu.js";
5
5
  import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
6
6
  import { a as idempotentReplay, i as idleLogCursor, n as decodeCause, r as decodeReturnedEventIds, t as cursorKeyOf } from "./store-codec-DwrWlgQx.js";
7
7
  import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
8
- import { t as pollPresence } from "./store-presence-polling-CzQ0wdtM.js";
8
+ import { t as pollPresence } from "./store-presence-polling-JuAy2Ck2.js";
9
9
  import { createHash } from "node:crypto";
10
10
  //#region src/postgres-notifications.ts
11
11
  const postgresNotificationChannel = ({ kind, sessionId }) => `a2_${kind}_${createHash("md5").update(sessionId).digest("hex")}`;
@@ -863,9 +863,9 @@ const INSERTED_AFTER = `(
863
863
  from a2_events where session_id = ${SESSION})`;
864
864
  /**
865
865
  * An append's checks, under the session lock: the session's head and pending
866
- * state, and what its supplied `ids` say. When every event's id belongs to
867
- * this session already, the batch is a retry: it stops with the committed
868
- * rows. Some owned, or one in another session, aborts, as does scheduled work
866
+ * state, and what its supplied `ids` say. Ids are unique per session. When
867
+ * every event's id belongs to this session already, the batch is a retry: it
868
+ * stops with the committed rows. Some owned aborts, as does scheduled work
869
869
  * from at or below the clear marker (`origin`). Rows below the marker own no
870
870
  * id.
871
871
  */
@@ -879,15 +879,11 @@ const APPEND_CHECKS = `
879
879
  select event.session_id, event.idx, event.event_type, event.payload,
880
880
  event.event_id, event.created_at, event.cause
881
881
  from a2_events event, boundary
882
- where event.event_id = any(${param.texts("ids")})
883
- and not (event.session_id = ${SESSION}
884
- and event.idx < boundary.clear_index)
882
+ where event.session_id = ${SESSION}
883
+ and event.event_id = any(${param.texts("ids")})
884
+ and event.idx >= boundary.clear_index
885
885
  )
886
886
  select case
887
- when exists (select 1 from owned where session_id <> ${SESSION}) then
888
- jsonb_build_object('abort', 'foreign', 'event_id', (
889
- select event_id from owned where session_id <> ${SESSION}
890
- order by idx limit 1))
891
887
  when (select count(*) from owned) = ${param.bigint("count")} then
892
888
  jsonb_build_object('stop', 'replay',
893
889
  'has_pending', boundary.has_pending,
@@ -1334,9 +1330,9 @@ const RETURN_CHECKS = `
1334
1330
  jsonb_build_object('stop', 'superseded')
1335
1331
  when exists (
1336
1332
  select 1 from a2_events event
1337
- where event.event_id = any(${param.texts("ids")})
1338
- and not (event.session_id = ${SESSION}
1339
- and event.idx < ${clearIndexOf(SESSION)})
1333
+ where event.session_id = ${SESSION}
1334
+ and event.event_id = any(${param.texts("ids")})
1335
+ and event.idx >= ${clearIndexOf(SESSION)}
1340
1336
  ) then jsonb_build_object('abort', 'owned')
1341
1337
  else jsonb_build_object('base', (
1342
1338
  select coalesce(max(idx), 0) from a2_events
@@ -1847,7 +1843,7 @@ function postgres(options = {}) {
1847
1843
  events: []
1848
1844
  };
1849
1845
  const { rows: existing } = await query(`select session_id, idx, event_type, payload, event_id, created_at, cause
1850
- from a2_events where event_id = any($1::text[])`, [ids]);
1846
+ from a2_events where session_id = $1 and event_id = any($2::text[])`, [sessionId, ids]);
1851
1847
  const byId = new Map(existing.map((row) => [row["event_id"], row]));
1852
1848
  const ordered = ids.map((id) => byId.get(id));
1853
1849
  if (ordered.some((row) => !row) && await clearedAt({
@@ -1857,7 +1853,7 @@ function postgres(options = {}) {
1857
1853
  })) return superseded;
1858
1854
  if (ordered.some((row) => {
1859
1855
  const cause = row ? decodeCause(row["cause"]) : null;
1860
- return !row || row["session_id"] !== sessionId || cause?.consumer !== consumer || cause.lane !== lane || cause.index !== index || cause.attempt !== attempt;
1856
+ return !row || cause?.consumer !== consumer || cause.lane !== lane || cause.index !== index || cause.attempt !== attempt;
1861
1857
  })) throw new A2Error("PARTIAL_DUPLICATE_BATCH", "returned event retry does not match the committed batch");
1862
1858
  return {
1863
1859
  outcome: "completed",
@@ -1917,7 +1913,6 @@ function postgres(options = {}) {
1917
1913
  },
1918
1914
  abort: (reason) => {
1919
1915
  switch (reason["abort"]) {
1920
- case "foreign": return new A2Error("PARTIAL_DUPLICATE_BATCH", `event id '${String(reason["event_id"])}' already exists in another session`);
1921
1916
  case "mixed": {
1922
1917
  const owned = asNumber(reason["owned"]);
1923
1918
  return new A2Error("PARTIAL_DUPLICATE_BATCH", `batch mixes ${owned} already-appended and ${events.length - owned} fresh events`);
@@ -2527,33 +2522,29 @@ function postgres(options = {}) {
2527
2522
  if (entries.length === 0) return;
2528
2523
  await wrap(async () => {
2529
2524
  const atMs = meta.at.getTime();
2530
- const expiresAtMs = clock.now().getTime() + meta.ttlMs;
2525
+ const nowMs = clock.now().getTime();
2526
+ const expiresAtMs = nowMs + meta.ttlMs;
2531
2527
  const applied = nullProtoRecord();
2532
2528
  const write = async (query) => {
2533
- for (const [field, value] of entries) if ((await (value === null ? query(`delete from a2_presence
2534
- where ns = $1 and participant = $2 and field = $3
2535
- and at <= $4${notifications ? " returning field" : ""}`, [
2536
- ns,
2537
- participant,
2538
- field,
2539
- atMs
2540
- ]) : query(`insert into a2_presence
2541
- (ns, participant, field, value, seen, at, expires_at)
2542
- values ($1, $2, $3, $4::jsonb, $5, $6, $7)
2543
- on conflict (ns, participant, field) do update set
2544
- value = excluded.value,
2545
- seen = excluded.seen,
2546
- at = excluded.at,
2547
- expires_at = excluded.expires_at
2548
- where a2_presence.at <= excluded.at${notifications ? " returning field" : ""}`, [
2529
+ for (const [field, value] of entries) if ((await query(`insert into a2_presence
2530
+ (ns, participant, field, value, seen, at, expires_at)
2531
+ values ($1, $2, $3, $4::jsonb, $5, $6, $7)
2532
+ on conflict (ns, participant, field) do update set
2533
+ value = excluded.value,
2534
+ seen = excluded.seen,
2535
+ at = excluded.at,
2536
+ expires_at = excluded.expires_at
2537
+ where a2_presence.at <= excluded.at
2538
+ or a2_presence.expires_at <= $8${notifications ? " returning field" : ""}`, [
2549
2539
  ns,
2550
2540
  participant,
2551
2541
  field,
2552
2542
  jsonb(value),
2553
2543
  meta.seen,
2554
2544
  atMs,
2555
- expiresAtMs
2556
- ]))).rows.length) applied[field] = structuredClone(value);
2545
+ expiresAtMs,
2546
+ nowMs
2547
+ ])).rows.length) applied[field] = structuredClone(value);
2557
2548
  };
2558
2549
  if (notifications) await withTx(write);
2559
2550
  else {
@@ -2583,7 +2574,7 @@ function postgres(options = {}) {
2583
2574
  )
2584
2575
  select participant, field, value, seen, at, expires_at
2585
2576
  from a2_presence
2586
- where ns = $1 and expires_at > $2
2577
+ where ns = $1 and expires_at > $2 and value <> 'null'::jsonb
2587
2578
  order by participant, field`, [ns, now]);
2588
2579
  return rows.map((row) => ({
2589
2580
  participant: row["participant"],
@@ -2800,7 +2791,7 @@ function postgres(options = {}) {
2800
2791
  const subscription = presenceSubscriptions.get(ns);
2801
2792
  await subscription?.ready;
2802
2793
  await subscription?.poll.ready;
2803
- return read(ns);
2794
+ return subscription ? subscription.poll.snapshot(() => read(ns)) : read(ns);
2804
2795
  };
2805
2796
  presence.subscribe = (ns, onPatch) => {
2806
2797
  if (closed) throw new A2Error("STORE_UNAVAILABLE", "the store was closed");