experimental-a2 0.9.0 → 0.11.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 (123) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/actor-D_54lz_1.d.ts +310 -0
  3. package/dist/actor-D_54lz_1.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +13 -4
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +63 -7
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -4
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +16 -2
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/actor.d.ts +2 -176
  13. package/dist/actor.js +13 -2
  14. package/dist/actor.js.map +1 -1
  15. package/dist/ai-server.d.ts +1 -1
  16. package/dist/ai-server.js +2 -2
  17. package/dist/ai.d.ts +1 -1
  18. package/dist/ai.js +1 -1
  19. package/dist/client-Bf6uSEAk.js +1342 -0
  20. package/dist/client-Bf6uSEAk.js.map +1 -0
  21. package/dist/client-P_NNNRM-.d.ts +243 -0
  22. package/dist/client-P_NNNRM-.d.ts.map +1 -0
  23. package/dist/client.d.ts +2 -202
  24. package/dist/client.js +2 -1026
  25. package/dist/errors-DCk6ch5n.js.map +1 -1
  26. package/dist/errors-DvhSXnxk.d.ts +28 -0
  27. package/dist/errors-DvhSXnxk.d.ts.map +1 -0
  28. package/dist/index.d.ts +3 -35
  29. package/dist/{internal-DRXJ56EI.js → internal-Dq2qYxou.js} +2 -2
  30. package/dist/{internal-DRXJ56EI.js.map → internal-Dq2qYxou.js.map} +1 -1
  31. package/dist/platform-B4TnJtWu.js +34 -0
  32. package/dist/platform-B4TnJtWu.js.map +1 -0
  33. package/dist/react.d.ts +3 -1
  34. package/dist/react.d.ts.map +1 -1
  35. package/dist/react.js +2 -1
  36. package/dist/react.js.map +1 -1
  37. package/dist/scheduler-qstash.d.ts +2 -2
  38. package/dist/scheduler-qstash.js +3 -2
  39. package/dist/scheduler-qstash.js.map +1 -1
  40. package/dist/scheduler-vercel.d.ts +2 -2
  41. package/dist/scheduler-vercel.js +2 -2
  42. package/dist/{server-CBET-jSz.js → server-Dkz2a84E.js} +295 -133
  43. package/dist/server-Dkz2a84E.js.map +1 -0
  44. package/dist/{server-CKY3_lbw.d.ts → server-DwPrMqHB.d.ts} +4 -2
  45. package/dist/server-DwPrMqHB.d.ts.map +1 -0
  46. package/dist/server.d.ts +2 -2
  47. package/dist/server.js +1 -1
  48. package/dist/{store-DGHeBtIQ.d.ts → store-DtDOWLSn.d.ts} +4 -5
  49. package/dist/{store-DGHeBtIQ.d.ts.map → store-DtDOWLSn.d.ts.map} +1 -1
  50. package/dist/store-N8PXxDAS.js.map +1 -1
  51. package/dist/store-memory.d.ts +1 -1
  52. package/dist/store-memory.js +1 -1
  53. package/dist/{store-polling-6DW7F1DT.js → store-polling-CmxUbV93.js} +56 -7
  54. package/dist/store-polling-CmxUbV93.js.map +1 -0
  55. package/dist/store-postgres.d.ts +6 -4
  56. package/dist/store-postgres.d.ts.map +1 -1
  57. package/dist/store-postgres.js +701 -53
  58. package/dist/store-postgres.js.map +1 -1
  59. package/dist/store-presence-polling-C7-XZyW9.js +94 -0
  60. package/dist/store-presence-polling-C7-XZyW9.js.map +1 -0
  61. package/dist/store-redis-http.d.ts +2 -2
  62. package/dist/store-redis-http.d.ts.map +1 -1
  63. package/dist/store-redis-http.js +184 -38
  64. package/dist/store-redis-http.js.map +1 -1
  65. package/dist/{store-redis-core-z-ykbyMg.js → store-redis-notify-BUCyXOn0.js} +491 -27
  66. package/dist/store-redis-notify-BUCyXOn0.js.map +1 -0
  67. package/dist/store-redis.d.ts +5 -13
  68. package/dist/store-redis.d.ts.map +1 -1
  69. package/dist/store-redis.js +27 -271
  70. package/dist/store-redis.js.map +1 -1
  71. package/dist/store-sqlite.d.ts +1 -1
  72. package/dist/store-sqlite.js +1 -1
  73. package/dist/{wire--yji6mO3.js → wire-BO5wWCb1.js} +18 -16
  74. package/dist/wire-BO5wWCb1.js.map +1 -0
  75. package/docs/actors/04-routes.mdx +40 -8
  76. package/docs/guides/03-react.mdx +52 -10
  77. package/docs/guides/05-production.mdx +54 -5
  78. package/docs/guides/09-presence.mdx +12 -7
  79. package/docs/guides/10-transports.mdx +85 -22
  80. package/docs/reference/01-api.mdx +90 -28
  81. package/docs/reference/02-errors.mdx +45 -11
  82. package/examples/playground/package.json +1 -1
  83. package/package.json +1 -1
  84. package/src/actor-client.ts +114 -14
  85. package/src/actor-react.ts +18 -3
  86. package/src/actor.ts +16 -1
  87. package/src/client-errors.ts +51 -0
  88. package/src/client.ts +577 -175
  89. package/src/errors.ts +4 -9
  90. package/src/index.ts +1 -1
  91. package/src/internal.ts +1 -1
  92. package/src/postgres-notification-scope.ts +134 -0
  93. package/src/postgres-notifications.ts +244 -0
  94. package/src/postgres-pool.ts +65 -0
  95. package/src/postgres-resources.ts +185 -0
  96. package/src/presence-recovery.ts +120 -0
  97. package/src/react.ts +3 -0
  98. package/src/redis-http-subscriptions.ts +199 -0
  99. package/src/server-fetch.ts +51 -23
  100. package/src/server.ts +146 -19
  101. package/src/session-socket.ts +241 -88
  102. package/src/sse.ts +17 -0
  103. package/src/store-polling.ts +32 -12
  104. package/src/store-postgres.ts +292 -75
  105. package/src/store-presence-polling.ts +125 -0
  106. package/src/store-redis-core.ts +37 -30
  107. package/src/store-redis-http.ts +51 -45
  108. package/src/store-redis-notify.ts +464 -0
  109. package/src/store-redis.ts +9 -364
  110. package/src/store.ts +3 -4
  111. package/src/stream-activity.ts +32 -0
  112. package/src/wire.ts +39 -15
  113. package/dist/actor-shared-BACubf4x.d.ts +0 -136
  114. package/dist/actor-shared-BACubf4x.d.ts.map +0 -1
  115. package/dist/actor.d.ts.map +0 -1
  116. package/dist/client.d.ts.map +0 -1
  117. package/dist/client.js.map +0 -1
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/server-CBET-jSz.js.map +0 -1
  120. package/dist/server-CKY3_lbw.d.ts.map +0 -1
  121. package/dist/store-polling-6DW7F1DT.js.map +0 -1
  122. package/dist/store-redis-core-z-ykbyMg.js.map +0 -1
  123. package/dist/wire--yji6mO3.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"store-postgres.js","names":[],"sources":["../src/store-postgres.ts"],"sourcesContent":["/**\n * experimental-a2/store-postgres — postgres store backend (the production path).\n *\n * Implements the A2Store interface over any client that speaks the tiny\n * `PostgresClient` shape below — a `pg` Pool matches it structurally\n * (and is what `connectionString` creates, lazily; `pg` is an optional\n * peer dependency). The conformance suite in test/conformance is the\n * executable contract, run against PGlite (real Postgres, in-process)\n * and against a real server when `TEST_DATABASE_URL` is set.\n *\n * Semantics mirror the sqlite backend: `index` is stored as `idx`,\n * timestamps are epoch milliseconds written from the injected clock —\n * never SQL `now()` — and `stream()` is a poll loop. (A LISTEN/NOTIFY\n * upgrade can land later without any API change; callers never branch\n * on the transport.) Appends serialize per session via\n * `pg_advisory_xact_lock`, so concurrent writers can't race the index\n * assignment.\n */\n\nimport { A2Error } from './errors.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport { decodeReturnedEventIds } from './store-codec.ts'\nimport { pollingStream } from './store-polling.ts'\nimport { retryableLazy } from './retryable-lazy.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type Clock,\n type Event,\n type EventCause,\n type IdSource,\n type StoredEvent,\n type StoreStateReadRequest,\n type StoreStateRead,\n} from './store.ts'\n\n/** The result shape this backend reads: just rows. */\nexport type PostgresQueryResult = { rows: Record<string, unknown>[] }\n\n/** One checked-out connection — what transactions run on. */\nexport type PostgresConnection = {\n query(text: string, values?: unknown[]): Promise<PostgresQueryResult>\n release(): void\n}\n\n/**\n * The minimal client this backend needs. `pg.Pool` matches it\n * structurally; so does PGlite (which has no `connect` — transactions\n * then serialize on an internal mutex, fine for a single-connection\n * client).\n */\nexport type PostgresClient = {\n query(text: string, values?: unknown[]): Promise<PostgresQueryResult>\n connect?(): Promise<PostgresConnection>\n end?(): Promise<void>\n}\n\nexport type PostgresStoreOptions = {\n /** Creates a `pg` Pool lazily (requires the optional peer dep `pg`). */\n connectionString?: string | undefined\n /** Bring your own client — a `pg` Pool, PGlite, a pooler wrapper. */\n client?: PostgresClient\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\nexport type PostgresStore = A2Store & {\n /** End the underlying pool/client (when this backend created it, or\n * the injected client has `end`). */\n close(): Promise<void>\n}\n\nconst SCHEMA = `\ncreate table if not exists a2_events (\n session_id text not null,\n idx bigint not null,\n event_type text not null,\n payload jsonb not null,\n event_id text not null,\n created_at bigint not null,\n cause jsonb,\n lane text,\n lane_ready boolean not null default false,\n processed_at bigint,\n processed_by_attempt integer,\n returned_event_ids jsonb,\n first_claimed_at bigint,\n last_claimed_at bigint,\n attempt_count integer not null default 0,\n failure_count integer not null default 0,\n last_failed_at bigint,\n last_failed_attempt integer,\n last_error text,\n failed_at bigint,\n claim_holder text,\n claim_expires_at bigint,\n primary key (session_id, idx)\n);\n\ncreate unique index if not exists a2_events_event_id on a2_events (event_id);\ncreate index if not exists a2_events_unprocessed\n on a2_events (session_id, idx) where processed_at is null;\ncreate index if not exists a2_events_lane_pending\n on a2_events (session_id, lane, idx) where processed_at is null;\ncreate index if not exists a2_events_dispatch_ready\n on a2_events (session_id, idx)\n where lane_ready and processed_at is null and failed_at is null;\ncreate index if not exists a2_events_claim_expiry\n on a2_events (session_id, claim_expires_at)\n where lane_ready and processed_at is null and failed_at is null;\n\ncreate table if not exists a2_snapshots (\n session_id text not null,\n reducer_name text not null,\n up_to_index bigint not null,\n state jsonb not null,\n updated_at bigint not null,\n primary key (session_id, reducer_name)\n);\n\ncreate table if not exists a2_snapshot_history (\n session_id text not null,\n reducer_name text not null,\n up_to_index bigint not null,\n state jsonb not null,\n updated_at bigint not null,\n primary key (session_id, reducer_name, up_to_index)\n);\n\ncreate table if not exists a2_snapshot_pins (\n session_id text not null,\n event_index bigint not null,\n reducer_name text not null,\n up_to_index bigint not null,\n primary key (session_id, event_index, reducer_name, up_to_index)\n);\n\ncreate index if not exists a2_snapshot_pins_checkpoint\n on a2_snapshot_pins (session_id, reducer_name, up_to_index);\n\ncreate table if not exists a2_presence (\n ns text not null,\n participant text not null,\n field text not null,\n value jsonb not null,\n seen bigint not null,\n at bigint not null,\n expires_at bigint not null,\n primary key (ns, participant, field)\n);\n`\n\nconst asNumber = (value: unknown): number => Number(value) // bigint columns arrive as strings\n\nconst toDate = (ms: unknown): Date | null =>\n ms === null || ms === undefined ? null : new Date(asNumber(ms))\n\nconst toCause = (raw: unknown): EventCause | null => {\n if (raw === null || raw === undefined) return null\n const value = (typeof raw === 'string' ? JSON.parse(raw) : raw) as {\n index?: unknown\n attempt?: unknown\n batchSize?: unknown\n }\n if (\n !Number.isInteger(value.index) ||\n Number(value.index) < 1 ||\n !Number.isInteger(value.attempt) ||\n Number(value.attempt) < 1\n ) {\n throw new TypeError('stored event has an invalid cause')\n }\n if (\n value.batchSize !== undefined &&\n (!Number.isInteger(value.batchSize) || Number(value.batchSize) < 1)\n ) {\n throw new TypeError('stored event has an invalid cause')\n }\n return {\n index: Number(value.index),\n attempt: Number(value.attempt),\n ...(value.batchSize === undefined\n ? {}\n : { batchSize: Number(value.batchSize) }),\n }\n}\n\nconst toStored = (row: Record<string, unknown>): StoredEvent => ({\n id: row['event_id'] as string,\n type: row['event_type'] as string,\n payload: row['payload'],\n index: asNumber(row['idx']),\n sessionId: row['session_id'] as string,\n createdAt: new Date(asNumber(row['created_at'])),\n cause: toCause(row['cause']),\n lane: (row['lane'] as string | null) ?? null,\n processedAt: toDate(row['processed_at']),\n processedByAttempt:\n row['processed_by_attempt'] === null ||\n row['processed_by_attempt'] === undefined\n ? null\n : asNumber(row['processed_by_attempt']),\n returnedEventIds:\n row['returned_event_ids'] === null ||\n row['returned_event_ids'] === undefined\n ? null\n : decodeReturnedEventIds(row['returned_event_ids']),\n firstClaimedAt: toDate(row['first_claimed_at']),\n lastClaimedAt: toDate(row['last_claimed_at']),\n attemptCount: asNumber(row['attempt_count']),\n failureCount: asNumber(row['failure_count']),\n lastFailedAt: toDate(row['last_failed_at']),\n lastFailedAttempt:\n row['last_failed_attempt'] === null ||\n row['last_failed_attempt'] === undefined\n ? null\n : asNumber(row['last_failed_attempt']),\n lastError: (row['last_error'] as string | null) ?? null,\n failedAt: toDate(row['failed_at']),\n claimHolder: (row['claim_holder'] as string | null) ?? null,\n claimExpiresAt: toDate(row['claim_expires_at']),\n})\n\nconst toEvent = (row: Record<string, unknown>): Event => ({\n id: row['event_id'] as string,\n type: row['event_type'] as string,\n payload: row['payload'],\n index: asNumber(row['idx']),\n sessionId: row['session_id'] as string,\n createdAt: new Date(asNumber(row['created_at'])),\n})\n\nconst wrap = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn()\n } catch (err) {\n if (err instanceof A2Error || err instanceof TypeError) throw err\n throw new A2Error('STORE_UNAVAILABLE', 'postgres store operation failed', {\n cause: err,\n })\n }\n}\n\nconst jsonb = (value: unknown): string => JSON.stringify(value) ?? 'null'\n\nconst initializeSchema = async (client: PostgresClient): Promise<void> => {\n if (!client.connect) {\n await client.query(SCHEMA)\n return\n }\n\n const conn = await client.connect()\n try {\n await conn.query('begin')\n // `if not exists` does not serialize concurrent catalog writes. Every\n // serverless isolate takes the same transaction-scoped lock before DDL.\n await conn.query(\n \"select pg_advisory_xact_lock(hashtext('a2:postgres:schema'))\",\n )\n await conn.query(SCHEMA)\n await conn.query('commit')\n } catch (err) {\n await conn.query('rollback').catch(() => {})\n throw err\n } finally {\n conn.release()\n }\n}\n\nexport function postgres(options: PostgresStoreOptions = {}): PostgresStore {\n const clock = options.clock ?? SYSTEM_CLOCK\n const generateId = options.ids ?? RANDOM_IDS\n\n if (!options.client && !options.connectionString) {\n throw new TypeError(\n 'postgres() needs a connectionString or an injected client',\n )\n }\n\n // Lazy init: the first operation creates the client (dynamic import\n // of the optional peer dep `pg`) and the schema. Constructing the\n // backend does no I/O — same contract as every other adapter.\n const connection = retryableLazy(async () => {\n let owned: PostgresClient | null = null\n try {\n let c = options.client\n if (!c) {\n const pg = await import('pg').catch(() => {\n throw new A2Error(\n 'STORE_NOT_CONFIGURED',\n \"experimental-a2/store-postgres with a connectionString needs the 'pg' package (optional peer dependency) — install it, or inject a client\",\n )\n })\n c = new pg.default.Pool({\n connectionString: options.connectionString,\n }) as unknown as PostgresClient\n owned = c\n }\n await initializeSchema(c)\n return c\n } catch (err) {\n if (owned?.end) await owned.end().catch(() => {})\n throw err\n }\n })\n const client = connection.get\n\n const readStateBatch = async (\n requests: readonly StoreStateReadRequest[],\n reducerName: string,\n ): Promise<StoreStateRead[]> => {\n if (requests.length === 0) return []\n const c = await client()\n const input = requests.map((request, position) => ({\n position,\n sessionId: request.sessionId,\n throughIndex: request.throughIndex ?? null,\n snapshotThroughIndex:\n request.snapshotThroughIndex ?? request.throughIndex ?? null,\n }))\n const { rows } = await c.query(\n `with request as materialized (\n select * from jsonb_to_recordset($1::jsonb) as value(\n position integer,\n \"sessionId\" text,\n \"throughIndex\" bigint,\n \"snapshotThroughIndex\" bigint\n )\n ), selected as materialized (\n select request.position,\n request.\"sessionId\" as session_id,\n request.\"throughIndex\" as through_index,\n head.up_to_index as head_index,\n candidate.up_to_index as snapshot_index,\n candidate.state as snapshot_state\n from request\n left join a2_snapshots head\n on head.session_id = request.\"sessionId\"\n and head.reducer_name = $2\n left join lateral (\n select up_to_index, state from (\n select head.up_to_index, head.state\n where request.\"snapshotThroughIndex\" is null\n or head.up_to_index <= request.\"snapshotThroughIndex\"\n union all\n select history.up_to_index, history.state\n from a2_snapshot_history history\n where history.session_id = request.\"sessionId\"\n and history.reducer_name = $2\n and (\n request.\"snapshotThroughIndex\" is null\n or history.up_to_index <= request.\"snapshotThroughIndex\"\n )\n ) candidates\n order by up_to_index desc\n limit 1\n ) candidate on true\n )\n select selected.position, 0 as row_order, selected.session_id,\n selected.head_index, selected.snapshot_index,\n selected.snapshot_state,\n null::bigint as idx, null::text as event_type,\n null::jsonb as payload, null::text as event_id,\n null::bigint as created_at\n from selected\n union all\n select selected.position, 1 as row_order, event.session_id,\n selected.head_index, null::bigint as snapshot_index,\n null::jsonb as snapshot_state,\n event.idx, event.event_type, event.payload,\n event.event_id, event.created_at\n from selected\n join a2_events event on event.session_id = selected.session_id\n where event.idx > coalesce(selected.snapshot_index, 0)\n and (\n selected.through_index is null\n or event.idx <= selected.through_index\n )\n order by position, row_order, idx`,\n [jsonb(input), reducerName],\n )\n const reads = requests.map<StoreStateRead>(() => ({\n headIndex: null,\n snapshot: null,\n events: [],\n }))\n for (const row of rows) {\n const read = reads[asNumber(row['position'])]!\n if (row['row_order'] === 0 || row['row_order'] === '0') {\n read.headIndex =\n row['head_index'] === null ? null : asNumber(row['head_index'])\n if (row['snapshot_index'] !== null) {\n read.snapshot = {\n index: asNumber(row['snapshot_index']),\n state: row['snapshot_state'],\n }\n }\n } else {\n read.events.push(toEvent(row))\n }\n }\n return reads\n }\n\n // Transactions run on one connection. Pools check one out; a\n // single-connection client (PGlite) serializes on this mutex instead.\n let mutex: Promise<unknown> = Promise.resolve()\n const withTx = async <T>(\n fn: (query: PostgresConnection['query']) => Promise<T>,\n ): Promise<T> => {\n const c = await client()\n if (c.connect) {\n const conn = await c.connect()\n try {\n await conn.query('begin')\n const result = await fn((text, values) => conn.query(text, values))\n await conn.query('commit')\n return result\n } catch (err) {\n await conn.query('rollback').catch(() => {})\n throw err\n } finally {\n conn.release()\n }\n }\n const run = mutex.then(async () => {\n await c.query('begin')\n try {\n const result = await fn((text, values) => c.query(text, values))\n await c.query('commit')\n return result\n } catch (err) {\n await c.query('rollback').catch(() => {})\n throw err\n }\n })\n mutex = run.catch(() => {})\n return run\n }\n\n return {\n async append(sessionId, events) {\n if (events.length === 0) {\n return wrap(async () => {\n const c = await client()\n const { rows } = await c.query(\n `select exists(\n select 1 from a2_events\n where session_id = $1 and processed_at is null\n ) as has_pending`,\n [sessionId],\n )\n return {\n events: [],\n hasPending: rows[0]?.['has_pending'] === true,\n }\n })\n }\n return wrap(() =>\n withTx(async (query) => {\n // Serialize appends per session — index assignment must not\n // race across writers. Transaction-scoped, self-releasing.\n await query('select pg_advisory_xact_lock(hashtext($1))', [sessionId])\n\n const supplied = events.filter((e) => e.id !== undefined)\n const suppliedIds = supplied.map((e) => e.id as string)\n if (new Set(suppliedIds).size !== suppliedIds.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'batch contains the same event id more than once',\n )\n }\n if (suppliedIds.length > 0) {\n const { rows: existing } = await query(\n `select stored.*,\n exists(\n select 1 from a2_events pending\n where pending.session_id = $2\n and pending.processed_at is null\n ) as session_has_pending\n from a2_events stored\n where stored.event_id = any($1)\n order by stored.idx`,\n [suppliedIds, sessionId],\n )\n if (existing.length > 0) {\n const foreign = existing.find(\n (row) => row['session_id'] !== sessionId,\n )\n if (foreign) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `event id '${String(foreign['event_id'])}' already exists in another session`,\n )\n }\n if (existing.length === events.length) {\n return {\n events: idempotentReplay(events, existing.map(toStored)),\n hasPending: existing[0]?.['session_has_pending'] === true,\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\n // Attempt-currency fence: a fresh handler append commits only\n // while its causal attempt is still the parent's latest\n // (store.ts `append`). `for update` serializes this read against\n // a concurrent `claimAvailable` — an unlocked read admits write\n // skew.\n const causeIndexes = [\n ...new Set(\n events\n .filter((e) => e.cause !== undefined)\n .map((e) => e.cause!.index),\n ),\n ]\n if (causeIndexes.length > 0) {\n const { rows: parents } = await query(\n `select idx, attempt_count, failed_at from a2_events\n where session_id = $1 and idx = any($2::bigint[])\n for update`,\n [sessionId, causeIndexes],\n )\n const byIndex = new Map(\n parents.map((row) => [asNumber(row['idx']), row]),\n )\n for (const e of events) {\n if (!e.cause) continue\n const parent = byIndex.get(e.cause.index)\n if (!parent) {\n throw new TypeError(\n `no event at index ${e.cause.index} in session '${sessionId}'`,\n )\n }\n if (\n asNumber(parent['attempt_count']) !== e.cause.attempt ||\n parent['failed_at'] !== null\n ) {\n throw new A2Error(\n 'SUPERSEDED_ATTEMPT',\n `attempt ${e.cause.attempt} no longer owns event ${e.cause.index} in session '${sessionId}'`,\n )\n }\n }\n }\n\n const { rows: maxRows } = await query(\n `select coalesce(max(idx), 0) as max,\n exists(\n select 1 from a2_events pending\n where pending.session_id = $1\n and pending.processed_at is null\n ) as session_has_pending\n from a2_events\n where session_id = $1`,\n [sessionId],\n )\n const base = asNumber(maxRows[0]?.['max'])\n const now = clock.now().getTime()\n const inserted: StoredEvent[] = []\n for (let i = 0; i < events.length; i += 1) {\n const e = events[i]!\n const id = e.id ?? generateId()\n const index = base + 1 + i\n // oxlint-disable-next-line no-await-in-loop -- one tx, ordered inserts\n await query(\n `insert into a2_events\n (session_id, idx, event_type, payload, event_id, created_at,\n cause, lane, lane_ready, processed_at)\n values (\n $1, $2, $3, $4::jsonb, $5, $6, $7::jsonb, $8,\n $8::text is null or not exists (\n select 1 from a2_events\n where session_id = $1 and lane = $8 and processed_at is null\n ),\n $9\n )`,\n [\n sessionId,\n index,\n e.type,\n jsonb(e.payload),\n id,\n now,\n e.cause ? jsonb(e.cause) : null,\n e.lane ?? null,\n e.settled ? now : null,\n ],\n )\n inserted.push({\n id,\n type: e.type,\n payload: structuredClone(e.payload),\n index,\n sessionId,\n createdAt: new Date(now),\n cause: e.cause ? { ...e.cause } : null,\n lane: e.lane ?? null,\n processedAt: e.settled ? new Date(now) : null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n claimHolder: null,\n claimExpiresAt: null,\n })\n }\n return {\n events: inserted,\n hasPending:\n maxRows[0]?.['session_has_pending'] === true ||\n events.some((event) => event.settled !== true),\n }\n }),\n )\n },\n\n async read(sessionId, opts) {\n return wrap(async () => {\n const c = await client()\n const conditions = ['session_id = $1']\n const params: unknown[] = [sessionId]\n if (opts?.afterIndex !== undefined) {\n params.push(opts.afterIndex)\n conditions.push(`idx > $${params.length}`)\n }\n if (opts?.throughIndex !== undefined) {\n params.push(opts.throughIndex)\n conditions.push(`idx <= $${params.length}`)\n }\n const { rows } = await c.query(\n `select * from a2_events where ${conditions.join(' and ')} order by idx`,\n params,\n )\n return rows.map(toStored)\n })\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n excludeIndexes = [],\n }) {\n return wrap(() =>\n withTx(async (query) => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const { rows } = await query(\n `with eligible as materialized (\n select event.idx\n from a2_events event\n where event.session_id = $1\n and event.processed_at is null\n and event.failed_at is null\n and event.lane_ready\n and (event.claim_expires_at is null or event.claim_expires_at <= $2)\n and not (event.idx = any($5::bigint[]))\n for update of event\n )\n update a2_events claimed set\n attempt_count = claimed.attempt_count + 1,\n first_claimed_at = coalesce(claimed.first_claimed_at, $2),\n last_claimed_at = $2,\n claim_holder = $3,\n claim_expires_at = $4\n from eligible\n where claimed.session_id = $1 and claimed.idx = eligible.idx\n returning claimed.*`,\n [sessionId, now, holder, expiresAt, [...excludeIndexes]],\n )\n if (rows.length > 0) {\n const events = rows\n .map(toStored)\n .toSorted((a, b) => a.index - b.index)\n return { outcome: 'claimed', events }\n }\n const { rows: activeRows } = await query(\n `select min(claim_expires_at) as retry_at\n from a2_events\n where session_id = $1\n and processed_at is null\n and failed_at is null\n and lane_ready\n and claim_expires_at > $2`,\n [sessionId, now],\n )\n const retryAt = activeRows[0]?.['retry_at']\n return retryAt === null || retryAt === undefined\n ? { outcome: 'settled' }\n : { outcome: 'busy', retryAt: new Date(asNumber(retryAt)) }\n }),\n )\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n if (claims.length === 0) return { renewed: [], superseded: [] }\n return wrap(async () => {\n const c = await client()\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const indexes = claims.map((claim) => claim.index)\n const attempts = new Map(\n claims.map((claim) => [claim.index, claim.attempt]),\n )\n const { rows } = await c.query(\n `with listed as (\n select idx, attempt\n from unnest($3::bigint[], $6::int[]) as listed(idx, attempt)\n ),\n renewed as (\n update a2_events event set claim_expires_at = $1\n from listed\n where event.session_id = $2\n and event.idx = listed.idx\n and event.attempt_count = listed.attempt\n and event.processed_at is null\n and event.failed_at is null\n and event.claim_holder = $4\n and event.claim_expires_at > $5\n returning event.idx\n )\n select event.idx, event.attempt_count,\n exists(select 1 from renewed where renewed.idx = event.idx)\n as renewed\n from a2_events event\n where event.session_id = $2 and event.idx = any($3::bigint[])`,\n [\n expiresAt,\n sessionId,\n indexes,\n holder,\n now,\n claims.map((claim) => claim.attempt),\n ],\n )\n const renewed: number[] = []\n const superseded: number[] = []\n for (const row of rows) {\n const index = asNumber(row['idx'])\n if (row['renewed'] === true) renewed.push(index)\n else if (\n asNumber(row['attempt_count']) > (attempts.get(index) ?? 0)\n ) {\n superseded.push(index)\n }\n }\n return {\n renewed: renewed.toSorted((a, b) => a - b),\n superseded: superseded.toSorted((a, b) => a - b),\n }\n })\n },\n\n async completeAttempt({ sessionId, index, attempt, events }) {\n return wrap(() =>\n withTx(async (query) => {\n const { rows: parentRows } = await query(\n `select * from a2_events\n where session_id = $1 and idx = $2\n for update`,\n [sessionId, index],\n )\n const parent = parentRows[0]\n if (!parent) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n const ids = events.map((event) => event.id)\n if (new Set(ids).size !== ids.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains the same event id more than once',\n )\n }\n if (parent['processed_at'] !== null) {\n if (asNumber(parent['processed_by_attempt']) !== attempt) {\n return { outcome: 'superseded' }\n }\n const returnedIds =\n parent['returned_event_ids'] === null ||\n parent['returned_event_ids'] === undefined\n ? null\n : decodeReturnedEventIds(parent['returned_event_ids'])\n if (\n returnedIds === null ||\n returnedIds.length !== ids.length ||\n returnedIds.some((id, offset) => id !== ids[offset])\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'completed attempt does not match the returned event batch',\n )\n }\n if (events.length === 0) {\n return { outcome: 'completed', events: [] }\n }\n const { rows: existing } = await query(\n `select * from a2_events\n where event_id = any($1::text[])`,\n [ids],\n )\n const byId = new Map(\n existing.map((row) => [row['event_id'] as string, row]),\n )\n const ordered = ids.map((id) => byId.get(id))\n if (\n ordered.some((row) => {\n const cause = row ? toCause(row['cause']) : null\n return (\n !row ||\n row['session_id'] !== sessionId ||\n cause?.index !== index ||\n cause.attempt !== attempt\n )\n })\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event retry does not match the committed batch',\n )\n }\n return {\n outcome: 'completed',\n events: idempotentReplay(\n events,\n (ordered as Record<string, unknown>[]).map(toStored),\n ),\n }\n }\n if (\n asNumber(parent['attempt_count']) !== attempt ||\n parent['claim_holder'] === null ||\n parent['claim_holder'] === undefined ||\n parent['failed_at'] !== null\n ) {\n return { outcome: 'superseded' }\n }\n\n await query('select pg_advisory_xact_lock(hashtext($1))', [sessionId])\n if (ids.length > 0) {\n const { rows: existing } = await query(\n 'select event_id from a2_events where event_id = any($1::text[])',\n [ids],\n )\n if (existing.length > 0) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains an already-appended event id',\n )\n }\n }\n\n const { rows: maxRows } = await query(\n 'select coalesce(max(idx), 0) as max from a2_events where session_id = $1',\n [sessionId],\n )\n const base = asNumber(maxRows[0]?.['max'])\n const now = clock.now().getTime()\n const inserted: StoredEvent[] = []\n for (let offset = 0; offset < events.length; offset += 1) {\n const event = events[offset]!\n const childIndex = base + offset + 1\n // oxlint-disable-next-line no-await-in-loop -- one tx, ordered inserts\n const { rows } = await query(\n `insert into a2_events\n (session_id, idx, event_type, payload, event_id, created_at,\n cause, lane, lane_ready, processed_at)\n values (\n $1, $2, $3, $4::jsonb, $5, $6, $7::jsonb, $8,\n $8::text is null or not exists (\n select 1 from a2_events\n where session_id = $1 and lane = $8 and processed_at is null\n ),\n $9\n )\n returning *`,\n [\n sessionId,\n childIndex,\n event.type,\n jsonb(event.payload),\n event.id,\n now,\n jsonb({ index, attempt }),\n event.lane ?? null,\n event.settled ? now : null,\n ],\n )\n inserted.push(toStored(rows[0]!))\n }\n await query(\n `update a2_events set\n processed_at = $1,\n processed_by_attempt = $2,\n returned_event_ids = $5::jsonb,\n lane_ready = false,\n claim_holder = null,\n claim_expires_at = null\n where session_id = $3 and idx = $4`,\n [now, attempt, sessionId, index, jsonb(ids)],\n )\n await query(\n `delete from a2_snapshot_pins\n where session_id = $1 and event_index = $2`,\n [sessionId, index],\n )\n await query(\n `delete from a2_snapshot_history history\n where history.session_id = $1\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = history.session_id\n and pin.reducer_name = history.reducer_name\n and pin.up_to_index = history.up_to_index\n )`,\n [sessionId],\n )\n const parentLane = (parent['lane'] as string | null) ?? null\n if (parentLane !== null) {\n await query(\n `update a2_events set lane_ready = true\n where session_id = $1\n and idx = (\n select idx from a2_events\n where session_id = $1 and lane = $2 and processed_at is null\n order by idx limit 1\n )`,\n [sessionId, parentLane],\n )\n }\n return { outcome: 'completed', events: inserted }\n }),\n )\n },\n\n async failAttempt({ sessionId, index, attempt, error, maxFailures }) {\n return wrap(() =>\n withTx(async (query) => {\n const { rows } = await query(\n `with current as materialized (\n select e.* from a2_events e\n where e.session_id = $1 and e.idx = $2\n for update of e\n ), updated as (\n update a2_events e set\n failure_count = e.failure_count + 1,\n last_error = $4,\n last_failed_at = $6,\n last_failed_attempt = $3,\n failed_at = case\n when e.failure_count + 1 >= $5 then $6\n else e.failed_at\n end,\n claim_holder = null,\n claim_expires_at = null\n from current\n where e.session_id = $1\n and e.idx = $2\n and current.processed_at is null\n and current.failed_at is null\n and current.attempt_count = $3\n and current.claim_holder is not null\n returning e.failure_count, e.failed_at\n )\n select case\n when not exists (select 1 from current) then 'missing'\n when exists (\n select 1 from updated where failed_at is not null\n ) then 'dead_lettered'\n when exists (select 1 from updated) then 'failed'\n when exists (\n select 1 from current\n where processed_at is not null or attempt_count <> $3\n ) then 'superseded'\n when exists (\n select 1 from current where failed_at is not null\n ) then 'dead_lettered'\n when exists (\n select 1 from current\n where claim_holder is null and last_failed_attempt = $3\n ) then 'failed'\n else 'superseded'\n end as outcome,\n coalesce(\n (select failure_count from updated),\n (select failure_count from current)\n ) as failure_count`,\n [\n sessionId,\n index,\n attempt,\n error,\n maxFailures,\n clock.now().getTime(),\n ],\n )\n const row = rows[0]\n if (!row || row['outcome'] === 'missing') {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n if (row['outcome'] === 'dead_lettered') {\n await query(\n `delete from a2_snapshot_pins\n where session_id = $1 and event_index = $2`,\n [sessionId, index],\n )\n await query(\n `delete from a2_snapshot_history history\n where history.session_id = $1\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = history.session_id\n and pin.reducer_name = history.reducer_name\n and pin.up_to_index = history.up_to_index\n )`,\n [sessionId],\n )\n }\n return {\n outcome: row['outcome'] as\n 'failed' | 'dead_lettered' | 'superseded',\n failureCount: asNumber(row['failure_count']),\n }\n }),\n )\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return wrap(\n async () =>\n (\n await readStateBatch(\n [\n {\n sessionId,\n ...(stateOptions?.throughIndex === undefined\n ? {}\n : { throughIndex: stateOptions.throughIndex }),\n ...(stateOptions?.snapshotThroughIndex === undefined\n ? {}\n : {\n snapshotThroughIndex: stateOptions.snapshotThroughIndex,\n }),\n },\n ],\n reducerName,\n )\n )[0]!,\n )\n },\n\n async readStates(requests, reducerName) {\n return wrap(() => readStateBatch(requests, reducerName))\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n await wrap(() =>\n withTx(async (query) => {\n const normalized = [\n ...new Map(writes.map((write) => [write.index, write])).values(),\n ].toSorted((a, b) => a.index - b.index)\n if (normalized.length === 0) return\n\n await query(\n 'select pg_advisory_xact_lock(hashtext($1), hashtext($2))',\n [sessionId, reducerName],\n )\n\n const requestedPins = normalized.flatMap((write) =>\n (write.pinEventIndexes ?? []).map((eventIndex) => ({\n eventIndex,\n snapshotIndex: write.index,\n })),\n )\n if (requestedPins.length > 0) {\n const eventIndexes = [\n ...new Set(requestedPins.map((pin) => pin.eventIndex)),\n ]\n const { rows: events } = await query(\n `select idx, processed_at, failed_at from a2_events\n where session_id = $1 and idx = any($2::bigint[])\n for update`,\n [sessionId, eventIndexes],\n )\n const unfinished = new Set(\n events\n .filter(\n (row) =>\n row['processed_at'] === null && row['failed_at'] === null,\n )\n .map((row) => asNumber(row['idx'])),\n )\n const accepted = requestedPins.filter((pin) =>\n unfinished.has(pin.eventIndex),\n )\n if (accepted.length > 0) {\n await query(\n `insert into a2_snapshot_pins\n (session_id, event_index, reducer_name, up_to_index)\n select $1, pin.\"eventIndex\", $2, pin.\"snapshotIndex\"\n from jsonb_to_recordset($3::jsonb) as pin(\n \"eventIndex\" bigint,\n \"snapshotIndex\" bigint\n )\n on conflict do nothing`,\n [sessionId, reducerName, jsonb(accepted)],\n )\n }\n }\n\n const { rows: headRows } = await query(\n `select up_to_index, state, updated_at from a2_snapshots\n where session_id = $1 and reducer_name = $2\n for update`,\n [sessionId, reducerName],\n )\n const currentHead = headRows[0]\n const latestWrite = normalized.at(-1)!\n const writesHead =\n !currentHead ||\n latestWrite.index >= asNumber(currentHead['up_to_index'])\n const advancesHead =\n !currentHead ||\n latestWrite.index > asNumber(currentHead['up_to_index'])\n const finalHeadIndex = advancesHead\n ? latestWrite.index\n : asNumber(currentHead['up_to_index'])\n const relevantIndexes = [\n ...new Set([\n ...normalized.map((write) => write.index),\n ...(currentHead ? [asNumber(currentHead['up_to_index'])] : []),\n ]),\n ]\n const { rows: pinRows } = await query(\n `select distinct up_to_index from a2_snapshot_pins\n where session_id = $1 and reducer_name = $2\n and up_to_index = any($3::bigint[])`,\n [sessionId, reducerName, relevantIndexes],\n )\n const pinned = new Set(\n pinRows.map((row) => asNumber(row['up_to_index'])),\n )\n const historical = new Map<\n number,\n { index: number; state: unknown; updatedAt: number }\n >()\n if (\n advancesHead &&\n currentHead &&\n pinned.has(asNumber(currentHead['up_to_index']))\n ) {\n const index = asNumber(currentHead['up_to_index'])\n historical.set(index, {\n index,\n state: currentHead['state'],\n updatedAt: asNumber(currentHead['updated_at']),\n })\n }\n const updatedAt = clock.now().getTime()\n for (const write of normalized) {\n if (write.index < finalHeadIndex && pinned.has(write.index)) {\n historical.set(write.index, {\n index: write.index,\n state: write.state,\n updatedAt,\n })\n }\n }\n if (historical.size > 0) {\n await query(\n `insert into a2_snapshot_history\n (session_id, reducer_name, up_to_index, state, updated_at)\n select $1, $2, snapshot.index, snapshot.state, snapshot.\"updatedAt\"\n from jsonb_to_recordset($3::jsonb) as snapshot(\n index bigint,\n state jsonb,\n \"updatedAt\" bigint\n )\n on conflict (session_id, reducer_name, up_to_index) do update set\n state = excluded.state,\n updated_at = excluded.updated_at`,\n [sessionId, reducerName, jsonb([...historical.values()])],\n )\n }\n if (writesHead) {\n await query(\n `insert into a2_snapshots\n (session_id, reducer_name, up_to_index, state, updated_at)\n values ($1, $2, $3, $4::jsonb, $5)\n on conflict (session_id, reducer_name) do update set\n up_to_index = excluded.up_to_index,\n state = excluded.state,\n updated_at = excluded.updated_at\n where excluded.up_to_index >= a2_snapshots.up_to_index`,\n [\n sessionId,\n reducerName,\n latestWrite.index,\n jsonb(latestWrite.state),\n clock.now().getTime(),\n ],\n )\n await query(\n `delete from a2_snapshot_history\n where session_id = $1 and reducer_name = $2 and up_to_index = $3`,\n [sessionId, reducerName, latestWrite.index],\n )\n }\n }),\n )\n },\n\n presence: {\n async set(ns, participant, values, meta) {\n const entries = Object.entries(values)\n if (entries.length === 0) return\n await wrap(async () => {\n const c = await client()\n const atMs = meta.at.getTime()\n // Expiry anchors on the storage clock — the sender's `at`\n // orders writes but never anchors their lifetime.\n const expiresAtMs = clock.now().getTime() + meta.ttlMs\n for (const [field, value] of entries) {\n // Field-wise LWW: a stored row with a strictly newer `at`\n // survives; ties go to the incoming write (memory parity).\n // oxlint-disable-next-line no-await-in-loop -- independent per-field registers\n await (value === null\n ? c.query(\n `delete from a2_presence\n where ns = $1 and participant = $2 and field = $3\n and at <= $4`,\n [ns, participant, field, atMs],\n )\n : c.query(\n `insert into a2_presence\n (ns, participant, field, value, seen, at, expires_at)\n values ($1, $2, $3, $4::jsonb, $5, $6, $7)\n on conflict (ns, participant, field) do update set\n value = excluded.value,\n seen = excluded.seen,\n at = excluded.at,\n expires_at = excluded.expires_at\n where a2_presence.at <= excluded.at`,\n [\n ns,\n participant,\n field,\n jsonb(value),\n meta.seen,\n atMs,\n expiresAtMs,\n ],\n ))\n }\n })\n },\n\n async read(ns) {\n return wrap(async () => {\n const c = await client()\n const now = clock.now().getTime()\n // Sweep and read share one statement (one snapshot): the\n // delete is opportunistic cleanup; the filter alone keeps\n // expired rows out of this result.\n const { rows } = await c.query(\n `with swept as (\n delete from a2_presence\n where ns = $1 and expires_at <= $2\n )\n select participant, field, value, seen, at, expires_at\n from a2_presence\n where ns = $1 and expires_at > $2\n order by participant, field`,\n [ns, now],\n )\n return rows.map((row) => ({\n participant: row['participant'] as string,\n field: row['field'] as string,\n value: row['value'],\n seen: asNumber(row['seen']),\n at: new Date(asNumber(row['at'])),\n expiresAt: new Date(asNumber(row['expires_at'])),\n }))\n })\n },\n },\n\n stream(sessionId, opts) {\n const readAfter = (afterIndex: number): Promise<StoredEvent[]> =>\n wrap(async () => {\n const c = await client()\n const { rows } = await c.query(\n 'select * from a2_events where session_id = $1 and idx > $2 order by idx',\n [sessionId, afterIndex],\n )\n return rows.map(toStored)\n })\n return pollingStream(\n readAfter,\n opts?.startAfter !== undefined ? { startAfter: opts.startAfter } : {},\n )\n },\n\n async close() {\n const current = connection.peek()\n if (!current) return\n const c = await current\n await c.end?.()\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"store-postgres.js","names":[],"sources":["../src/postgres-notifications.ts","../src/postgres-notification-scope.ts","../src/postgres-pool.ts","../src/postgres-resources.ts","../src/store-postgres.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport { NOTIFY_TIMINGS } from './internal.ts'\nimport { defaultSleep, type Sleeper } from './store-polling.ts'\n\nexport const postgresNotificationChannel = ({\n kind,\n sessionId,\n}: {\n kind: 'events' | 'presence'\n sessionId: string\n}): string => `a2_${kind}_${createHash('md5').update(sessionId).digest('hex')}`\n\nexport type PostgresListenerConnection = {\n connect(): Promise<unknown>\n query(text: string): Promise<unknown>\n end(): Promise<void>\n onNotification(\n listener: (message: {\n channel: string\n payload?: string | undefined\n }) => void,\n ): void\n onError(listener: (error: Error) => void): void\n onEnd(listener: () => void): void\n}\n\ntype Watcher = {\n notify(payload: string): void\n resolve(): void\n reject(error: unknown): void\n}\n\ntype WatchOptions = { channel: string; notify: (payload: string) => void }\n\nexport function postgresNotifications(\n createConnection: () => Promise<PostgresListenerConnection>,\n): {\n watch(options: WatchOptions): { ready: Promise<void>; stop(): void }\n close(): Promise<void>\n} {\n const channels = new Map<\n string,\n { listening: boolean; ready?: Promise<unknown>; watchers: Set<Watcher> }\n >()\n const ending = new Set<Promise<void>>()\n const end = (connection: PostgresListenerConnection | undefined): void => {\n if (!connection) return\n const pending = connection\n .end()\n .catch(() => {})\n .finally(() => {\n ending.delete(pending)\n })\n ending.add(pending)\n }\n let current: PostgresListenerConnection | undefined\n let task: Promise<void> | undefined\n let retry: Sleeper | undefined\n let delay = 100\n let connectedAt = 0\n let closed = false\n const queues = new WeakMap<PostgresListenerConnection, Promise<unknown>>()\n const query = ({\n connection,\n text,\n }: {\n connection: PostgresListenerConnection\n text: string\n }): Promise<unknown> => {\n const result = (queues.get(connection) ?? Promise.resolve()).then(() => {\n if (current !== connection)\n throw new Error('Postgres listener disconnected')\n return connection.query(text)\n })\n queues.set(\n connection,\n result.catch(() => {}),\n )\n return result\n }\n\n const fail = ({\n connection,\n error,\n }: {\n connection: PostgresListenerConnection | undefined\n error: unknown\n }): void => {\n if (connection !== current) return\n current = undefined\n end(connection)\n for (const entry of channels.values()) {\n entry.listening = false\n for (const watcher of entry.watchers) watcher.reject(error)\n }\n if (closed || channels.size === 0 || retry) return\n if (Date.now() - connectedAt >= NOTIFY_TIMINGS.safetyReadMs) delay = 100\n retry = defaultSleep(delay)\n delay = Math.min(delay * 2, NOTIFY_TIMINGS.safetyReadMs)\n void retry.promise.then(() => {\n retry = undefined\n reconcile()\n return undefined\n })\n }\n\n const reconcile = (): void => {\n if (closed || task || retry || channels.size === 0) return\n let attempt: PostgresListenerConnection | undefined\n task = (async () => {\n if (!current) {\n connectedAt = Date.now()\n const connection = await createConnection()\n if (closed || channels.size === 0) {\n await connection.end()\n return\n }\n current = connection\n attempt = connection\n connectedAt = Date.now()\n connection.onNotification(({ channel, payload }) => {\n if (current !== connection) return\n for (const watcher of channels.get(channel)?.watchers ?? [])\n watcher.notify(payload ?? '')\n })\n connection.onError((error) => fail({ connection, error }))\n connection.onEnd(() =>\n fail({\n connection,\n error: new Error('Postgres listener disconnected'),\n }),\n )\n await connection.connect()\n if (current !== connection) return\n }\n const connection = current\n attempt = connection\n for (const [channel, entry] of channels) {\n if (entry.listening) continue\n entry.listening = true\n // oxlint-disable-next-line no-await-in-loop -- one session owns the LISTEN commands\n entry.ready = query({\n connection,\n text: `LISTEN \"${channel.replaceAll('\"', '\"\"')}\"`,\n })\n // oxlint-disable-next-line no-await-in-loop -- wait for subscription registration\n await entry.ready\n if (current !== connection) return\n for (const watcher of entry.watchers) {\n watcher.resolve()\n watcher.notify('')\n }\n }\n })()\n .catch((error: unknown) => {\n if (attempt === current) fail({ connection: attempt, error })\n })\n .finally(() => {\n task = undefined\n if ([...channels.values()].some((entry) => !entry.listening))\n reconcile()\n })\n }\n\n const manager = {\n watch({ channel, notify }: WatchOptions) {\n let resolve!: () => void\n let reject!: (error: unknown) => void\n const ready = new Promise<void>((yes, no) => {\n resolve = yes\n reject = no\n })\n const watcher = { notify, resolve, reject }\n let entry = channels.get(channel)\n if (!entry) {\n entry = { listening: false, watchers: new Set() }\n channels.set(channel, entry)\n }\n entry.watchers.add(watcher)\n if (entry.listening) void entry.ready?.then(resolve, reject)\n reconcile()\n let stopped = false\n return {\n ready,\n stop() {\n if (stopped) return\n stopped = true\n resolve()\n entry.watchers.delete(watcher)\n if (entry.watchers.size > 0) return\n channels.delete(channel)\n if (channels.size === 0) {\n retry?.cancel()\n const connection = current\n current = undefined\n end(connection)\n } else if (entry.listening) {\n const connection = current\n if (connection)\n void query({\n connection,\n text: `UNLISTEN \"${channel.replaceAll('\"', '\"\"')}\"`,\n }).catch((error: unknown) => fail({ connection, error }))\n }\n },\n }\n },\n async close() {\n closed = true\n retry?.cancel()\n for (const entry of channels.values()) {\n for (const watcher of entry.watchers) watcher.resolve()\n }\n channels.clear()\n const connection = current\n current = undefined\n end(connection)\n await Promise.all(ending)\n },\n }\n return manager\n}\n\nexport const POSTGRES_NOTIFICATIONS_SQL = `\ncreate or replace function a2_notify_events() returns trigger language plpgsql as $$\nbegin\n perform pg_notify('a2_events_' || md5(new.session_id), '');\n return null;\nend $$;\ncreate or replace function a2_notify_presence() returns trigger language plpgsql as $$\nbegin\n perform pg_notify('a2_presence_' || md5(case when TG_OP = 'DELETE' then old.ns else new.ns end),\n coalesce(current_setting('a2.presence_source', true), ''));\n return null;\nend $$;\ndo $$ begin\n if not exists (select 1 from pg_trigger where tgname = 'a2_notify_events' and tgrelid = 'a2_events'::regclass) then\n create trigger a2_notify_events after insert on a2_events for each row execute function a2_notify_events();\n end if;\n if not exists (select 1 from pg_trigger where tgname = 'a2_notify_presence' and tgrelid = 'a2_presence'::regclass) then\n create trigger a2_notify_presence after insert or update or delete on a2_presence for each row execute function a2_notify_presence();\n end if;\nend $$;\n`\n","import { A2Error } from './errors.ts'\nimport { retryableLazy } from './retryable-lazy.ts'\nimport { NOTIFY_TIMINGS } from './internal.ts'\nimport { defaultSleep, type Sleeper } from './store-polling.ts'\nimport type { Event } from './store.ts'\nimport type {\n PostgresNotifications,\n ResourceLease,\n} from './postgres-resources.ts'\n\ntype ScopedNotifications = PostgresNotifications & {\n stream(options: {\n channel: string\n read: (after: number) => Promise<Event[]>\n startAfter: number\n }): AsyncIterable<Event>\n}\n\nconst failure = (error: unknown): A2Error =>\n error instanceof A2Error\n ? error\n : new A2Error('STORE_UNAVAILABLE', 'Postgres subscription failed', {\n cause: error,\n })\n\nexport function postgresNotificationScope(\n load: () => Promise<ResourceLease<PostgresNotifications>>,\n): ScopedNotifications {\n const state = {\n shared: retryableLazy(load),\n stops: new Set<() => void>(),\n closed: false,\n }\n\n const scope: ScopedNotifications = {\n watch(options) {\n let stopped = false\n let cancel!: () => void\n const cancelled = new Promise<void>((resolve) => {\n cancel = resolve\n })\n let subscription: ReturnType<PostgresNotifications['watch']> | undefined\n const stop = (): void => {\n stopped = true\n cancel()\n subscription?.stop()\n state.stops.delete(stop)\n }\n state.stops.add(stop)\n const ready = Promise.race([\n cancelled,\n state.shared.get().then(async ({ value }) => {\n if (stopped || state.closed) return\n subscription = value.watch(options)\n await subscription.ready\n return undefined\n }),\n ]).catch((error: unknown) => {\n if (!stopped && !state.closed) throw failure(error)\n })\n return { ready, stop }\n },\n stream({ channel, read, startAfter }) {\n return {\n [Symbol.asyncIterator]() {\n let stopped = false\n let after = startAfter\n let buffer: Event[] = []\n let version = 0\n let waiting: Sleeper | undefined\n let lease: ReturnType<PostgresNotifications['watch']> | undefined\n const stop = (): void => {\n stopped = true\n waiting?.cancel()\n lease?.stop()\n state.stops.delete(stop)\n }\n state.stops.add(stop)\n return {\n async next(): Promise<IteratorResult<Event>> {\n try {\n if (stopped || state.closed)\n return { done: true, value: undefined }\n lease ??= scope.watch({\n channel,\n notify: () => {\n version += 1\n waiting?.cancel()\n },\n })\n await lease.ready\n for (;;) {\n if (stopped || state.closed)\n return { done: true, value: undefined }\n const row = buffer.shift()\n if (row) {\n after = row.index\n return { done: false, value: row }\n }\n const before = version\n // oxlint-disable-next-line no-await-in-loop -- cursor catch-up\n buffer = await read(after)\n if (stopped || state.closed)\n return { done: true, value: undefined }\n if (buffer.length || before !== version) continue\n waiting = defaultSleep(NOTIFY_TIMINGS.safetyReadMs)\n // oxlint-disable-next-line no-await-in-loop -- wait for a hint or the safety read\n await waiting.promise\n }\n } catch (error) {\n if (stopped || state.closed)\n return { done: true, value: undefined }\n stop()\n throw failure(error)\n }\n },\n async return(): Promise<IteratorResult<Event>> {\n stop()\n return { done: true, value: undefined }\n },\n }\n },\n }\n },\n async close() {\n if (state.closed) return\n state.closed = true\n for (const stop of state.stops) stop()\n const lease = await state.shared.peek()?.catch(() => undefined)\n await lease?.release()\n },\n }\n return scope\n}\n","import type { Pool } from 'pg'\nimport { platformWaitUntil } from './platform.ts'\nimport type { PostgresClient } from './store-postgres.ts'\n\nexport function postgresPoolClient(pool: Pool): PostgresClient {\n let timer: ReturnType<typeof setTimeout> | undefined\n let resolve: (() => void) | undefined\n const finish = () => {\n clearTimeout(timer)\n timer = undefined\n resolve?.()\n resolve = undefined\n }\n const keepAlive = () => {\n const idleTimeout = pool.options.idleTimeoutMillis\n if (\n pool.ending ||\n pool.idleCount === 0 ||\n typeof idleTimeout !== 'number' ||\n !Number.isFinite(idleTimeout) ||\n idleTimeout <= 0\n )\n return\n finish()\n const pending = new Promise<void>((done) => {\n resolve = done\n timer = setTimeout(finish, idleTimeout + 100)\n timer.unref()\n })\n platformWaitUntil(pending)\n }\n pool.on('remove', () => {\n if (pool.totalCount === 0) finish()\n })\n return {\n async query(text, values) {\n try {\n return await pool.query<Record<string, unknown>>(text, values)\n } finally {\n keepAlive()\n }\n },\n async connect() {\n const client = await pool.connect()\n return {\n query: (text, values) =>\n client.query<Record<string, unknown>>(text, values),\n release() {\n try {\n client.release()\n } finally {\n keepAlive()\n }\n },\n }\n },\n async end() {\n try {\n await pool.end()\n } finally {\n finish()\n }\n },\n }\n}\n","import { createHash } from 'node:crypto'\nimport type { PostgresClient } from './store-postgres.ts'\nimport type { postgresNotifications } from './postgres-notifications.ts'\n\nexport type PostgresNotifications = ReturnType<typeof postgresNotifications>\nexport type ResourceLease<T> = { value: T; release(): Promise<void> }\ntype Entry<T> = { value: T; refs: number }\ntype Cache<K, T> = {\n get(key: K): Entry<T> | undefined\n set(key: K, entry: Entry<T>): unknown\n delete(key: K): boolean\n}\n\nexport type PostgresQueries = {\n client: PostgresClient\n raw: PostgresClient\n exclusive<T>(fn: () => Promise<T>): Promise<T>\n initialize(options: {\n schema: string\n run: () => Promise<void>\n }): Promise<void>\n close(): Promise<void>\n}\n\ntype Registry = {\n pools: Map<string, Entry<PostgresQueries>>\n injected: WeakMap<PostgresClient, Entry<PostgresQueries>>\n listeners: Map<string, Entry<PostgresNotifications>>\n identities: WeakMap<object, number>\n nextIdentity: number\n}\n\nconst registryKey = Symbol.for('experimental-a2.postgres-resources.v3')\nconst registry = (): Registry => {\n let value = Reflect.get(globalThis, registryKey) as Registry | undefined\n if (!value) {\n value = {\n pools: new Map(),\n injected: new WeakMap(),\n listeners: new Map(),\n identities: new WeakMap(),\n nextIdentity: 0,\n }\n Reflect.set(globalThis, registryKey, value)\n }\n return value\n}\n\nconst acquire = <K, T extends { close(): Promise<void> }>({\n cache,\n key,\n create,\n}: {\n cache: Cache<K, T>\n key: K\n create: () => T\n}): ResourceLease<T> => {\n let entry = cache.get(key)\n if (!entry) {\n entry = { value: create(), refs: 0 }\n cache.set(key, entry)\n }\n entry.refs += 1\n const held = entry\n let released = false\n const lease = {\n value: entry.value,\n async release() {\n if (released) return\n released = true\n held.refs -= 1\n if (held.refs !== 0) return\n if (cache.get(key) === held) cache.delete(key)\n await held.value.close()\n },\n }\n return lease\n}\n\nexport function postgresConnectionKey({\n connectionString,\n defaults,\n}: {\n connectionString: string\n defaults: object\n}): string {\n const shared = registry()\n const visited = new Set<object>()\n const identity = (value: object): number => {\n let id = shared.identities.get(value)\n if (id === undefined) {\n id = ++shared.nextIdentity\n shared.identities.set(value, id)\n }\n return id\n }\n const encode = (value: unknown): unknown => {\n if (typeof value === 'function') return ['reference', identity(value)]\n if (value === null || typeof value !== 'object')\n return [typeof value, String(value)]\n if (Buffer.isBuffer(value)) return ['buffer', value.toString('base64')]\n const prototype: unknown = Object.getPrototypeOf(value)\n if (\n visited.has(value) ||\n (prototype !== Object.prototype &&\n prototype !== null &&\n !Array.isArray(value))\n )\n return ['reference', identity(value)]\n visited.add(value)\n const result = [\n Array.isArray(value) ? 'array' : 'object',\n Object.getOwnPropertyNames(value)\n .toSorted()\n .map((key) => [key, encode(Reflect.get(value, key))]),\n ]\n visited.delete(value)\n return result\n }\n const environment = Object.keys(process.env)\n .filter((key) => key.startsWith('PG'))\n .toSorted()\n .map((key) => [key, process.env[key]])\n return createHash('sha256')\n .update(JSON.stringify([connectionString, environment, encode(defaults)]))\n .digest('hex')\n}\n\nconst queries = (raw: PostgresClient): PostgresQueries => {\n let pending: Promise<unknown> = Promise.resolve()\n const schemas = new Map<string, Promise<void>>()\n const exclusive = <T>(fn: () => Promise<T>): Promise<T> => {\n const result = pending.then(fn)\n pending = result.catch(() => {})\n return result\n }\n return {\n raw,\n client: raw.connect\n ? raw\n : { query: (text, values) => exclusive(() => raw.query(text, values)) },\n exclusive,\n initialize({ schema, run }) {\n const existing = schemas.get(schema)\n if (existing) return existing\n const attempt = run()\n schemas.set(schema, attempt)\n void attempt.catch(() => {\n if (schemas.get(schema) === attempt) schemas.delete(schema)\n })\n return attempt\n },\n async close() {\n await pending\n await raw.end?.()\n },\n }\n}\n\nexport const sharePostgresPool = ({\n key,\n create,\n}: {\n key: string\n create: () => PostgresClient\n}): ResourceLease<PostgresQueries> =>\n acquire({ cache: registry().pools, key, create: () => queries(create()) })\n\nexport const sharePostgresClient = (\n client: PostgresClient,\n): ResourceLease<PostgresQueries> =>\n acquire({\n cache: registry().injected,\n key: client,\n create: () => queries(client),\n })\n\nexport const sharePostgresListener = ({\n key,\n create,\n}: {\n key: string\n create: () => PostgresNotifications\n}): ResourceLease<PostgresNotifications> =>\n acquire({ cache: registry().listeners, key, create })\n","/**\n * experimental-a2/store-postgres — postgres store backend (the production path).\n *\n * Implements the A2Store interface over any client that speaks the tiny\n * `PostgresClient` shape below — a `pg` Pool matches it structurally\n * (and is what `connectionString` creates, lazily; `pg` is an optional\n * peer dependency). The conformance suite in test/conformance is the\n * executable contract, run against PGlite (real Postgres, in-process)\n * and against a real server when `TEST_DATABASE_URL` is set.\n *\n * Semantics mirror the sqlite backend: `index` is stored as `idx`,\n * timestamps are epoch milliseconds written from the injected clock —\n * never SQL `now()`. Streams poll by default; an optional direct\n * connection enables LISTEN/NOTIFY for events and presence. Appends serialize per session via\n * `pg_advisory_xact_lock`, so concurrent writers can't race the index\n * assignment.\n */\n\nimport { A2Error } from './errors.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport { decodeReturnedEventIds } from './store-codec.ts'\nimport { pollingStream, defaultSleep, type Sleeper } from './store-polling.ts'\nimport { NOTIFY_TIMINGS, nullProtoRecord } from './internal.ts'\nimport type { PresencePatch } from './contract.ts'\nimport { pollPresence } from './store-presence-polling.ts'\nimport {\n postgresNotifications,\n postgresNotificationChannel,\n POSTGRES_NOTIFICATIONS_SQL,\n type PostgresListenerConnection,\n} from './postgres-notifications.ts'\nimport { retryableLazy } from './retryable-lazy.ts'\nimport { postgresNotificationScope } from './postgres-notification-scope.ts'\nimport { postgresPoolClient } from './postgres-pool.ts'\nimport {\n postgresConnectionKey,\n sharePostgresClient,\n sharePostgresPool,\n sharePostgresListener,\n} from './postgres-resources.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type Clock,\n type Event,\n type EventCause,\n type IdSource,\n type StoredEvent,\n type StoreStateReadRequest,\n type StoreStateRead,\n} from './store.ts'\n\n/** The result shape this backend reads: just rows. */\nexport type PostgresQueryResult = { rows: Record<string, unknown>[] }\n\n/** One checked-out connection — what transactions run on. */\nexport type PostgresConnection = {\n query(text: string, values?: unknown[]): Promise<PostgresQueryResult>\n release(): void\n}\n\n/**\n * The minimal client this backend needs. `pg.Pool` matches it\n * structurally; so does PGlite (which has no `connect` — transactions\n * then serialize on an internal mutex, fine for a single-connection\n * client).\n */\nexport type PostgresClient = {\n query(text: string, values?: unknown[]): Promise<PostgresQueryResult>\n connect?(): Promise<PostgresConnection>\n end?(): Promise<void>\n}\n\nexport type PostgresStoreOptions = {\n /** Shares a lazy `pg` Pool for matching connection configuration (optional peer `pg`). */\n connectionString?: string | undefined\n /** Direct connection to the same database, shared by live LISTEN subscriptions. */\n listenConnectionString?: string | undefined\n /** Bring your own client — a `pg` Pool, PGlite, a pooler wrapper. */\n client?: PostgresClient\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\nexport type PostgresStore = A2Store & {\n /** Release this store’s subscriptions and resource references. Shared\n * connections remain available to other stores. */\n close(): Promise<void>\n}\n\nconst SCHEMA = `\ncreate table if not exists a2_events (\n session_id text not null,\n idx bigint not null,\n event_type text not null,\n payload jsonb not null,\n event_id text not null,\n created_at bigint not null,\n cause jsonb,\n lane text,\n lane_ready boolean not null default false,\n processed_at bigint,\n processed_by_attempt integer,\n returned_event_ids jsonb,\n first_claimed_at bigint,\n last_claimed_at bigint,\n attempt_count integer not null default 0,\n failure_count integer not null default 0,\n last_failed_at bigint,\n last_failed_attempt integer,\n last_error text,\n failed_at bigint,\n claim_holder text,\n claim_expires_at bigint,\n primary key (session_id, idx)\n);\n\ncreate unique index if not exists a2_events_event_id on a2_events (event_id);\ncreate index if not exists a2_events_unprocessed\n on a2_events (session_id, idx) where processed_at is null;\ncreate index if not exists a2_events_lane_pending\n on a2_events (session_id, lane, idx) where processed_at is null;\ncreate index if not exists a2_events_dispatch_ready\n on a2_events (session_id, idx)\n where lane_ready and processed_at is null and failed_at is null;\ncreate index if not exists a2_events_claim_expiry\n on a2_events (session_id, claim_expires_at)\n where lane_ready and processed_at is null and failed_at is null;\n\ncreate table if not exists a2_snapshots (\n session_id text not null,\n reducer_name text not null,\n up_to_index bigint not null,\n state jsonb not null,\n updated_at bigint not null,\n primary key (session_id, reducer_name)\n);\n\ncreate table if not exists a2_snapshot_history (\n session_id text not null,\n reducer_name text not null,\n up_to_index bigint not null,\n state jsonb not null,\n updated_at bigint not null,\n primary key (session_id, reducer_name, up_to_index)\n);\n\ncreate table if not exists a2_snapshot_pins (\n session_id text not null,\n event_index bigint not null,\n reducer_name text not null,\n up_to_index bigint not null,\n primary key (session_id, event_index, reducer_name, up_to_index)\n);\n\ncreate index if not exists a2_snapshot_pins_checkpoint\n on a2_snapshot_pins (session_id, reducer_name, up_to_index);\n\ncreate table if not exists a2_presence (\n ns text not null,\n participant text not null,\n field text not null,\n value jsonb not null,\n seen bigint not null,\n at bigint not null,\n expires_at bigint not null,\n primary key (ns, participant, field)\n);\n`\n\nconst asNumber = (value: unknown): number => Number(value) // bigint columns arrive as strings\n\nconst toDate = (ms: unknown): Date | null =>\n ms === null || ms === undefined ? null : new Date(asNumber(ms))\n\nconst toCause = (raw: unknown): EventCause | null => {\n if (raw === null || raw === undefined) return null\n const value = (typeof raw === 'string' ? JSON.parse(raw) : raw) as {\n index?: unknown\n attempt?: unknown\n batchSize?: unknown\n }\n if (\n !Number.isInteger(value.index) ||\n Number(value.index) < 1 ||\n !Number.isInteger(value.attempt) ||\n Number(value.attempt) < 1\n ) {\n throw new TypeError('stored event has an invalid cause')\n }\n if (\n value.batchSize !== undefined &&\n (!Number.isInteger(value.batchSize) || Number(value.batchSize) < 1)\n ) {\n throw new TypeError('stored event has an invalid cause')\n }\n return {\n index: Number(value.index),\n attempt: Number(value.attempt),\n ...(value.batchSize === undefined\n ? {}\n : { batchSize: Number(value.batchSize) }),\n }\n}\n\nconst toStored = (row: Record<string, unknown>): StoredEvent => ({\n id: row['event_id'] as string,\n type: row['event_type'] as string,\n payload: row['payload'],\n index: asNumber(row['idx']),\n sessionId: row['session_id'] as string,\n createdAt: new Date(asNumber(row['created_at'])),\n cause: toCause(row['cause']),\n lane: (row['lane'] as string | null) ?? null,\n processedAt: toDate(row['processed_at']),\n processedByAttempt:\n row['processed_by_attempt'] === null ||\n row['processed_by_attempt'] === undefined\n ? null\n : asNumber(row['processed_by_attempt']),\n returnedEventIds:\n row['returned_event_ids'] === null ||\n row['returned_event_ids'] === undefined\n ? null\n : decodeReturnedEventIds(row['returned_event_ids']),\n firstClaimedAt: toDate(row['first_claimed_at']),\n lastClaimedAt: toDate(row['last_claimed_at']),\n attemptCount: asNumber(row['attempt_count']),\n failureCount: asNumber(row['failure_count']),\n lastFailedAt: toDate(row['last_failed_at']),\n lastFailedAttempt:\n row['last_failed_attempt'] === null ||\n row['last_failed_attempt'] === undefined\n ? null\n : asNumber(row['last_failed_attempt']),\n lastError: (row['last_error'] as string | null) ?? null,\n failedAt: toDate(row['failed_at']),\n claimHolder: (row['claim_holder'] as string | null) ?? null,\n claimExpiresAt: toDate(row['claim_expires_at']),\n})\n\nconst toEvent = (row: Record<string, unknown>): Event => ({\n id: row['event_id'] as string,\n type: row['event_type'] as string,\n payload: row['payload'],\n index: asNumber(row['idx']),\n sessionId: row['session_id'] as string,\n createdAt: new Date(asNumber(row['created_at'])),\n})\n\nconst wrap = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn()\n } catch (err) {\n if (err instanceof A2Error || err instanceof TypeError) throw err\n throw new A2Error('STORE_UNAVAILABLE', 'postgres store operation failed', {\n cause: err,\n })\n }\n}\n\nconst jsonb = (value: unknown): string => JSON.stringify(value) ?? 'null'\n\nconst initializeSchema = async ({\n client,\n listen,\n}: {\n client: PostgresClient\n listen: boolean\n}): Promise<void> => {\n const schema = SCHEMA + (listen ? POSTGRES_NOTIFICATIONS_SQL : '')\n if (!client.connect) {\n await client.query(schema)\n return\n }\n\n const conn = await client.connect()\n try {\n await conn.query('begin')\n // `if not exists` does not serialize concurrent catalog writes. Every\n // serverless isolate takes the same transaction-scoped lock before DDL.\n await conn.query(\n \"select pg_advisory_xact_lock(hashtext('a2:postgres:schema'))\",\n )\n await conn.query(schema)\n await conn.query('commit')\n } catch (err) {\n await conn.query('rollback').catch(() => {})\n throw err\n } finally {\n conn.release()\n }\n}\n\nconst loadPg = async () =>\n import('pg').catch(() => {\n throw new A2Error(\n 'STORE_NOT_CONFIGURED',\n \"Postgres connections created by A2 require the optional 'pg' package\",\n )\n })\n\nconst createListener = ({\n pg,\n connectionString,\n}: {\n pg: Awaited<ReturnType<typeof loadPg>>\n connectionString: string\n}): ReturnType<typeof postgresNotifications> =>\n postgresNotifications(async () => {\n const listener = new pg.default.Client({\n connectionString,\n application_name: 'a2-listener',\n connectionTimeoutMillis: 10_000,\n })\n const listenerConnection: PostgresListenerConnection = {\n connect: () => listener.connect(),\n query: (text) => listener.query(text),\n end: () => listener.end(),\n onNotification: (callback) => {\n listener.on('notification', callback)\n },\n onError: (callback) => {\n listener.on('error', callback)\n },\n onEnd: (callback) => {\n listener.on('end', callback)\n },\n }\n return listenerConnection\n })\n\nexport function postgres(options: PostgresStoreOptions = {}): PostgresStore {\n const clock = options.clock ?? SYSTEM_CLOCK\n const generateId = options.ids ?? RANDOM_IDS\n // oxlint-disable-next-line a2/adapter-time-is-injected -- ephemeral notification source, never a stored event ID\n const instanceId = crypto.randomUUID()\n let closed = false\n if (options.listenConnectionString === '')\n throw new TypeError(\n 'listenConnectionString must be a direct Postgres connection string',\n )\n\n if (!options.client && !options.connectionString) {\n throw new TypeError(\n 'postgres() needs a connectionString or an injected client',\n )\n }\n\n const injected = options.client\n ? sharePostgresClient(options.client)\n : undefined\n const connection = retryableLazy(async () => {\n let lease = injected\n if (!lease) {\n const pg = await loadPg()\n const connectionString = options.connectionString!\n const key = postgresConnectionKey({\n connectionString,\n defaults: pg.default.defaults,\n })\n lease = sharePostgresPool({\n key,\n create: () => {\n const pool = new pg.default.Pool({ connectionString })\n // pg removes failed idle clients; active queries report their own errors.\n pool.on('error', () => {})\n return postgresPoolClient(pool)\n },\n })\n }\n try {\n const listen = Boolean(options.listenConnectionString)\n const resource = lease.value\n if (injected) await initializeSchema({ client: resource.client, listen })\n else\n await resource.initialize({\n schema: SCHEMA + (listen ? POSTGRES_NOTIFICATIONS_SQL : ''),\n run: () => initializeSchema({ client: resource.client, listen }),\n })\n return lease\n } catch (error) {\n if (!injected) await lease.release().catch(() => {})\n throw error\n }\n })\n const client = async (): Promise<PostgresClient> => {\n if (closed) throw new A2Error('STORE_UNAVAILABLE', 'the store was closed')\n const lease = await connection.get()\n if (closed) throw new A2Error('STORE_UNAVAILABLE', 'the store was closed')\n return lease.value.client\n }\n\n const notifications = options.listenConnectionString\n ? postgresNotificationScope(async () => {\n await client()\n const pg = await loadPg()\n const connectionString = options.listenConnectionString!\n const key = postgresConnectionKey({\n connectionString,\n defaults: pg.default.defaults,\n })\n return sharePostgresListener({\n key,\n create: () => createListener({ pg, connectionString }),\n })\n })\n : undefined\n const presenceSubscriptions = new Map<\n string,\n {\n listeners: Set<(patch: PresencePatch) => void>\n ready: Promise<void>\n poll: ReturnType<typeof pollPresence>\n stop(): void\n }\n >()\n\n const readStateBatch = async (\n requests: readonly StoreStateReadRequest[],\n reducerName: string,\n ): Promise<StoreStateRead[]> => {\n if (requests.length === 0) return []\n const c = await client()\n const input = requests.map((request, position) => ({\n position,\n sessionId: request.sessionId,\n throughIndex: request.throughIndex ?? null,\n snapshotThroughIndex:\n request.snapshotThroughIndex ?? request.throughIndex ?? null,\n }))\n const { rows } = await c.query(\n `with request as materialized (\n select * from jsonb_to_recordset($1::jsonb) as value(\n position integer,\n \"sessionId\" text,\n \"throughIndex\" bigint,\n \"snapshotThroughIndex\" bigint\n )\n ), selected as materialized (\n select request.position,\n request.\"sessionId\" as session_id,\n request.\"throughIndex\" as through_index,\n head.up_to_index as head_index,\n candidate.up_to_index as snapshot_index,\n candidate.state as snapshot_state\n from request\n left join a2_snapshots head\n on head.session_id = request.\"sessionId\"\n and head.reducer_name = $2\n left join lateral (\n select up_to_index, state from (\n select head.up_to_index, head.state\n where request.\"snapshotThroughIndex\" is null\n or head.up_to_index <= request.\"snapshotThroughIndex\"\n union all\n select history.up_to_index, history.state\n from a2_snapshot_history history\n where history.session_id = request.\"sessionId\"\n and history.reducer_name = $2\n and (\n request.\"snapshotThroughIndex\" is null\n or history.up_to_index <= request.\"snapshotThroughIndex\"\n )\n ) candidates\n order by up_to_index desc\n limit 1\n ) candidate on true\n )\n select selected.position, 0 as row_order, selected.session_id,\n selected.head_index, selected.snapshot_index,\n selected.snapshot_state,\n null::bigint as idx, null::text as event_type,\n null::jsonb as payload, null::text as event_id,\n null::bigint as created_at\n from selected\n union all\n select selected.position, 1 as row_order, event.session_id,\n selected.head_index, null::bigint as snapshot_index,\n null::jsonb as snapshot_state,\n event.idx, event.event_type, event.payload,\n event.event_id, event.created_at\n from selected\n join a2_events event on event.session_id = selected.session_id\n where event.idx > coalesce(selected.snapshot_index, 0)\n and (\n selected.through_index is null\n or event.idx <= selected.through_index\n )\n order by position, row_order, idx`,\n [jsonb(input), reducerName],\n )\n const reads = requests.map<StoreStateRead>(() => ({\n headIndex: null,\n snapshot: null,\n events: [],\n }))\n for (const row of rows) {\n const read = reads[asNumber(row['position'])]!\n if (row['row_order'] === 0 || row['row_order'] === '0') {\n read.headIndex =\n row['head_index'] === null ? null : asNumber(row['head_index'])\n if (row['snapshot_index'] !== null) {\n read.snapshot = {\n index: asNumber(row['snapshot_index']),\n state: row['snapshot_state'],\n }\n }\n } else {\n read.events.push(toEvent(row))\n }\n }\n return reads\n }\n\n const begin = notifications\n ? `begin; set local a2.presence_source = '${instanceId}'`\n : 'begin'\n const withTx = async <T>(\n fn: (query: PostgresConnection['query']) => Promise<T>,\n ): Promise<T> => {\n const c = await client()\n if (c.connect) {\n const conn = await c.connect()\n try {\n await conn.query(begin)\n const result = await fn((text, values) => conn.query(text, values))\n await conn.query('commit')\n return result\n } catch (err) {\n await conn.query('rollback').catch(() => {})\n throw err\n } finally {\n conn.release()\n }\n }\n const resource = (await connection.get()).value\n return resource.exclusive(async () => {\n const raw = resource.raw\n await raw.query(begin)\n try {\n const result = await fn((text, values) => raw.query(text, values))\n await raw.query('commit')\n return result\n } catch (err) {\n await raw.query('rollback').catch(() => {})\n throw err\n }\n })\n }\n\n const store: PostgresStore = {\n async append(sessionId, events) {\n if (events.length === 0) {\n return wrap(async () => {\n const c = await client()\n const { rows } = await c.query(\n `select exists(\n select 1 from a2_events\n where session_id = $1 and processed_at is null\n ) as has_pending`,\n [sessionId],\n )\n return {\n events: [],\n hasPending: rows[0]?.['has_pending'] === true,\n }\n })\n }\n return wrap(() =>\n withTx(async (query) => {\n // Serialize appends per session — index assignment must not\n // race across writers. Transaction-scoped, self-releasing.\n await query('select pg_advisory_xact_lock(hashtext($1))', [sessionId])\n\n const supplied = events.filter((e) => e.id !== undefined)\n const suppliedIds = supplied.map((e) => e.id as string)\n if (new Set(suppliedIds).size !== suppliedIds.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'batch contains the same event id more than once',\n )\n }\n if (suppliedIds.length > 0) {\n const { rows: existing } = await query(\n `select stored.*,\n exists(\n select 1 from a2_events pending\n where pending.session_id = $2\n and pending.processed_at is null\n ) as session_has_pending\n from a2_events stored\n where stored.event_id = any($1)\n order by stored.idx`,\n [suppliedIds, sessionId],\n )\n if (existing.length > 0) {\n const foreign = existing.find(\n (row) => row['session_id'] !== sessionId,\n )\n if (foreign) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `event id '${String(foreign['event_id'])}' already exists in another session`,\n )\n }\n if (existing.length === events.length) {\n return {\n events: idempotentReplay(events, existing.map(toStored)),\n hasPending: existing[0]?.['session_has_pending'] === true,\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\n // Attempt-currency fence: a fresh handler append commits only\n // while its causal attempt is still the parent's latest\n // (store.ts `append`). `for update` serializes this read against\n // a concurrent `claimAvailable` — an unlocked read admits write\n // skew.\n const causeIndexes = [\n ...new Set(\n events\n .filter((e) => e.cause !== undefined)\n .map((e) => e.cause!.index),\n ),\n ]\n if (causeIndexes.length > 0) {\n const { rows: parents } = await query(\n `select idx, attempt_count, failed_at from a2_events\n where session_id = $1 and idx = any($2::bigint[])\n for update`,\n [sessionId, causeIndexes],\n )\n const byIndex = new Map(\n parents.map((row) => [asNumber(row['idx']), row]),\n )\n for (const e of events) {\n if (!e.cause) continue\n const parent = byIndex.get(e.cause.index)\n if (!parent) {\n throw new TypeError(\n `no event at index ${e.cause.index} in session '${sessionId}'`,\n )\n }\n if (\n asNumber(parent['attempt_count']) !== e.cause.attempt ||\n parent['failed_at'] !== null\n ) {\n throw new A2Error(\n 'SUPERSEDED_ATTEMPT',\n `attempt ${e.cause.attempt} no longer owns event ${e.cause.index} in session '${sessionId}'`,\n )\n }\n }\n }\n\n const { rows: maxRows } = await query(\n `select coalesce(max(idx), 0) as max,\n exists(\n select 1 from a2_events pending\n where pending.session_id = $1\n and pending.processed_at is null\n ) as session_has_pending\n from a2_events\n where session_id = $1`,\n [sessionId],\n )\n const base = asNumber(maxRows[0]?.['max'])\n const now = clock.now().getTime()\n const inserted: StoredEvent[] = []\n for (let i = 0; i < events.length; i += 1) {\n const e = events[i]!\n const id = e.id ?? generateId()\n const index = base + 1 + i\n // oxlint-disable-next-line no-await-in-loop -- one tx, ordered inserts\n await query(\n `insert into a2_events\n (session_id, idx, event_type, payload, event_id, created_at,\n cause, lane, lane_ready, processed_at)\n values (\n $1, $2, $3, $4::jsonb, $5, $6, $7::jsonb, $8,\n $8::text is null or not exists (\n select 1 from a2_events\n where session_id = $1 and lane = $8 and processed_at is null\n ),\n $9\n )`,\n [\n sessionId,\n index,\n e.type,\n jsonb(e.payload),\n id,\n now,\n e.cause ? jsonb(e.cause) : null,\n e.lane ?? null,\n e.settled ? now : null,\n ],\n )\n inserted.push({\n id,\n type: e.type,\n payload: structuredClone(e.payload),\n index,\n sessionId,\n createdAt: new Date(now),\n cause: e.cause ? { ...e.cause } : null,\n lane: e.lane ?? null,\n processedAt: e.settled ? new Date(now) : null,\n processedByAttempt: null,\n returnedEventIds: null,\n firstClaimedAt: null,\n lastClaimedAt: null,\n attemptCount: 0,\n failureCount: 0,\n lastFailedAt: null,\n lastFailedAttempt: null,\n lastError: null,\n failedAt: null,\n claimHolder: null,\n claimExpiresAt: null,\n })\n }\n return {\n events: inserted,\n hasPending:\n maxRows[0]?.['session_has_pending'] === true ||\n events.some((event) => event.settled !== true),\n }\n }),\n )\n },\n\n async read(sessionId, opts) {\n return wrap(async () => {\n const c = await client()\n const conditions = ['session_id = $1']\n const params: unknown[] = [sessionId]\n if (opts?.afterIndex !== undefined) {\n params.push(opts.afterIndex)\n conditions.push(`idx > $${params.length}`)\n }\n if (opts?.throughIndex !== undefined) {\n params.push(opts.throughIndex)\n conditions.push(`idx <= $${params.length}`)\n }\n const { rows } = await c.query(\n `select * from a2_events where ${conditions.join(' and ')} order by idx`,\n params,\n )\n return rows.map(toStored)\n })\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n excludeIndexes = [],\n }) {\n return wrap(() =>\n withTx(async (query) => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const { rows } = await query(\n `with eligible as materialized (\n select event.idx\n from a2_events event\n where event.session_id = $1\n and event.processed_at is null\n and event.failed_at is null\n and event.lane_ready\n and (event.claim_expires_at is null or event.claim_expires_at <= $2)\n and not (event.idx = any($5::bigint[]))\n for update of event\n )\n update a2_events claimed set\n attempt_count = claimed.attempt_count + 1,\n first_claimed_at = coalesce(claimed.first_claimed_at, $2),\n last_claimed_at = $2,\n claim_holder = $3,\n claim_expires_at = $4\n from eligible\n where claimed.session_id = $1 and claimed.idx = eligible.idx\n returning claimed.*`,\n [sessionId, now, holder, expiresAt, [...excludeIndexes]],\n )\n if (rows.length > 0) {\n const events = rows\n .map(toStored)\n .toSorted((a, b) => a.index - b.index)\n return { outcome: 'claimed', events }\n }\n const { rows: activeRows } = await query(\n `select min(claim_expires_at) as retry_at\n from a2_events\n where session_id = $1\n and processed_at is null\n and failed_at is null\n and lane_ready\n and claim_expires_at > $2`,\n [sessionId, now],\n )\n const retryAt = activeRows[0]?.['retry_at']\n return retryAt === null || retryAt === undefined\n ? { outcome: 'settled' }\n : { outcome: 'busy', retryAt: new Date(asNumber(retryAt)) }\n }),\n )\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n if (claims.length === 0) return { renewed: [], superseded: [] }\n return wrap(async () => {\n const c = await client()\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const indexes = claims.map((claim) => claim.index)\n const attempts = new Map(\n claims.map((claim) => [claim.index, claim.attempt]),\n )\n const { rows } = await c.query(\n `with listed as (\n select idx, attempt\n from unnest($3::bigint[], $6::int[]) as listed(idx, attempt)\n ),\n renewed as (\n update a2_events event set claim_expires_at = $1\n from listed\n where event.session_id = $2\n and event.idx = listed.idx\n and event.attempt_count = listed.attempt\n and event.processed_at is null\n and event.failed_at is null\n and event.claim_holder = $4\n and event.claim_expires_at > $5\n returning event.idx\n )\n select event.idx, event.attempt_count,\n exists(select 1 from renewed where renewed.idx = event.idx)\n as renewed\n from a2_events event\n where event.session_id = $2 and event.idx = any($3::bigint[])`,\n [\n expiresAt,\n sessionId,\n indexes,\n holder,\n now,\n claims.map((claim) => claim.attempt),\n ],\n )\n const renewed: number[] = []\n const superseded: number[] = []\n for (const row of rows) {\n const index = asNumber(row['idx'])\n if (row['renewed'] === true) renewed.push(index)\n else if (\n asNumber(row['attempt_count']) > (attempts.get(index) ?? 0)\n ) {\n superseded.push(index)\n }\n }\n return {\n renewed: renewed.toSorted((a, b) => a - b),\n superseded: superseded.toSorted((a, b) => a - b),\n }\n })\n },\n\n async completeAttempt({ sessionId, index, attempt, events }) {\n return wrap(() =>\n withTx(async (query) => {\n const { rows: parentRows } = await query(\n `select * from a2_events\n where session_id = $1 and idx = $2\n for update`,\n [sessionId, index],\n )\n const parent = parentRows[0]\n if (!parent) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n const ids = events.map((event) => event.id)\n if (new Set(ids).size !== ids.length) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains the same event id more than once',\n )\n }\n if (parent['processed_at'] !== null) {\n if (asNumber(parent['processed_by_attempt']) !== attempt) {\n return { outcome: 'superseded' }\n }\n const returnedIds =\n parent['returned_event_ids'] === null ||\n parent['returned_event_ids'] === undefined\n ? null\n : decodeReturnedEventIds(parent['returned_event_ids'])\n if (\n returnedIds === null ||\n returnedIds.length !== ids.length ||\n returnedIds.some((id, offset) => id !== ids[offset])\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'completed attempt does not match the returned event batch',\n )\n }\n if (events.length === 0) {\n return { outcome: 'completed', events: [] }\n }\n const { rows: existing } = await query(\n `select * from a2_events\n where event_id = any($1::text[])`,\n [ids],\n )\n const byId = new Map(\n existing.map((row) => [row['event_id'] as string, row]),\n )\n const ordered = ids.map((id) => byId.get(id))\n if (\n ordered.some((row) => {\n const cause = row ? toCause(row['cause']) : null\n return (\n !row ||\n row['session_id'] !== sessionId ||\n cause?.index !== index ||\n cause.attempt !== attempt\n )\n })\n ) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event retry does not match the committed batch',\n )\n }\n return {\n outcome: 'completed',\n events: idempotentReplay(\n events,\n (ordered as Record<string, unknown>[]).map(toStored),\n ),\n }\n }\n if (\n asNumber(parent['attempt_count']) !== attempt ||\n parent['claim_holder'] === null ||\n parent['claim_holder'] === undefined ||\n parent['failed_at'] !== null\n ) {\n return { outcome: 'superseded' }\n }\n\n await query('select pg_advisory_xact_lock(hashtext($1))', [sessionId])\n if (ids.length > 0) {\n const { rows: existing } = await query(\n 'select event_id from a2_events where event_id = any($1::text[])',\n [ids],\n )\n if (existing.length > 0) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains an already-appended event id',\n )\n }\n }\n\n const { rows: maxRows } = await query(\n 'select coalesce(max(idx), 0) as max from a2_events where session_id = $1',\n [sessionId],\n )\n const base = asNumber(maxRows[0]?.['max'])\n const now = clock.now().getTime()\n const inserted: StoredEvent[] = []\n for (let offset = 0; offset < events.length; offset += 1) {\n const event = events[offset]!\n const childIndex = base + offset + 1\n // oxlint-disable-next-line no-await-in-loop -- one tx, ordered inserts\n const { rows } = await query(\n `insert into a2_events\n (session_id, idx, event_type, payload, event_id, created_at,\n cause, lane, lane_ready, processed_at)\n values (\n $1, $2, $3, $4::jsonb, $5, $6, $7::jsonb, $8,\n $8::text is null or not exists (\n select 1 from a2_events\n where session_id = $1 and lane = $8 and processed_at is null\n ),\n $9\n )\n returning *`,\n [\n sessionId,\n childIndex,\n event.type,\n jsonb(event.payload),\n event.id,\n now,\n jsonb({ index, attempt }),\n event.lane ?? null,\n event.settled ? now : null,\n ],\n )\n inserted.push(toStored(rows[0]!))\n }\n await query(\n `update a2_events set\n processed_at = $1,\n processed_by_attempt = $2,\n returned_event_ids = $5::jsonb,\n lane_ready = false,\n claim_holder = null,\n claim_expires_at = null\n where session_id = $3 and idx = $4`,\n [now, attempt, sessionId, index, jsonb(ids)],\n )\n await query(\n `delete from a2_snapshot_pins\n where session_id = $1 and event_index = $2`,\n [sessionId, index],\n )\n await query(\n `delete from a2_snapshot_history history\n where history.session_id = $1\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = history.session_id\n and pin.reducer_name = history.reducer_name\n and pin.up_to_index = history.up_to_index\n )`,\n [sessionId],\n )\n const parentLane = (parent['lane'] as string | null) ?? null\n if (parentLane !== null) {\n await query(\n `update a2_events set lane_ready = true\n where session_id = $1\n and idx = (\n select idx from a2_events\n where session_id = $1 and lane = $2 and processed_at is null\n order by idx limit 1\n )`,\n [sessionId, parentLane],\n )\n }\n return { outcome: 'completed', events: inserted }\n }),\n )\n },\n\n async failAttempt({ sessionId, index, attempt, error, maxFailures }) {\n return wrap(() =>\n withTx(async (query) => {\n const { rows } = await query(\n `with current as materialized (\n select e.* from a2_events e\n where e.session_id = $1 and e.idx = $2\n for update of e\n ), updated as (\n update a2_events e set\n failure_count = e.failure_count + 1,\n last_error = $4,\n last_failed_at = $6,\n last_failed_attempt = $3,\n failed_at = case\n when e.failure_count + 1 >= $5 then $6\n else e.failed_at\n end,\n claim_holder = null,\n claim_expires_at = null\n from current\n where e.session_id = $1\n and e.idx = $2\n and current.processed_at is null\n and current.failed_at is null\n and current.attempt_count = $3\n and current.claim_holder is not null\n returning e.failure_count, e.failed_at\n )\n select case\n when not exists (select 1 from current) then 'missing'\n when exists (\n select 1 from updated where failed_at is not null\n ) then 'dead_lettered'\n when exists (select 1 from updated) then 'failed'\n when exists (\n select 1 from current\n where processed_at is not null or attempt_count <> $3\n ) then 'superseded'\n when exists (\n select 1 from current where failed_at is not null\n ) then 'dead_lettered'\n when exists (\n select 1 from current\n where claim_holder is null and last_failed_attempt = $3\n ) then 'failed'\n else 'superseded'\n end as outcome,\n coalesce(\n (select failure_count from updated),\n (select failure_count from current)\n ) as failure_count`,\n [\n sessionId,\n index,\n attempt,\n error,\n maxFailures,\n clock.now().getTime(),\n ],\n )\n const row = rows[0]\n if (!row || row['outcome'] === 'missing') {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n if (row['outcome'] === 'dead_lettered') {\n await query(\n `delete from a2_snapshot_pins\n where session_id = $1 and event_index = $2`,\n [sessionId, index],\n )\n await query(\n `delete from a2_snapshot_history history\n where history.session_id = $1\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = history.session_id\n and pin.reducer_name = history.reducer_name\n and pin.up_to_index = history.up_to_index\n )`,\n [sessionId],\n )\n }\n return {\n outcome: row['outcome'] as\n 'failed' | 'dead_lettered' | 'superseded',\n failureCount: asNumber(row['failure_count']),\n }\n }),\n )\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return wrap(\n async () =>\n (\n await readStateBatch(\n [\n {\n sessionId,\n ...(stateOptions?.throughIndex === undefined\n ? {}\n : { throughIndex: stateOptions.throughIndex }),\n ...(stateOptions?.snapshotThroughIndex === undefined\n ? {}\n : {\n snapshotThroughIndex: stateOptions.snapshotThroughIndex,\n }),\n },\n ],\n reducerName,\n )\n )[0]!,\n )\n },\n\n async readStates(requests, reducerName) {\n return wrap(() => readStateBatch(requests, reducerName))\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n await wrap(() =>\n withTx(async (query) => {\n const normalized = [\n ...new Map(writes.map((write) => [write.index, write])).values(),\n ].toSorted((a, b) => a.index - b.index)\n if (normalized.length === 0) return\n\n await query(\n 'select pg_advisory_xact_lock(hashtext($1), hashtext($2))',\n [sessionId, reducerName],\n )\n\n const requestedPins = normalized.flatMap((write) =>\n (write.pinEventIndexes ?? []).map((eventIndex) => ({\n eventIndex,\n snapshotIndex: write.index,\n })),\n )\n if (requestedPins.length > 0) {\n const eventIndexes = [\n ...new Set(requestedPins.map((pin) => pin.eventIndex)),\n ]\n const { rows: events } = await query(\n `select idx, processed_at, failed_at from a2_events\n where session_id = $1 and idx = any($2::bigint[])\n for update`,\n [sessionId, eventIndexes],\n )\n const unfinished = new Set(\n events\n .filter(\n (row) =>\n row['processed_at'] === null && row['failed_at'] === null,\n )\n .map((row) => asNumber(row['idx'])),\n )\n const accepted = requestedPins.filter((pin) =>\n unfinished.has(pin.eventIndex),\n )\n if (accepted.length > 0) {\n await query(\n `insert into a2_snapshot_pins\n (session_id, event_index, reducer_name, up_to_index)\n select $1, pin.\"eventIndex\", $2, pin.\"snapshotIndex\"\n from jsonb_to_recordset($3::jsonb) as pin(\n \"eventIndex\" bigint,\n \"snapshotIndex\" bigint\n )\n on conflict do nothing`,\n [sessionId, reducerName, jsonb(accepted)],\n )\n }\n }\n\n const { rows: headRows } = await query(\n `select up_to_index, state, updated_at from a2_snapshots\n where session_id = $1 and reducer_name = $2\n for update`,\n [sessionId, reducerName],\n )\n const currentHead = headRows[0]\n const latestWrite = normalized.at(-1)!\n const writesHead =\n !currentHead ||\n latestWrite.index >= asNumber(currentHead['up_to_index'])\n const advancesHead =\n !currentHead ||\n latestWrite.index > asNumber(currentHead['up_to_index'])\n const finalHeadIndex = advancesHead\n ? latestWrite.index\n : asNumber(currentHead['up_to_index'])\n const relevantIndexes = [\n ...new Set([\n ...normalized.map((write) => write.index),\n ...(currentHead ? [asNumber(currentHead['up_to_index'])] : []),\n ]),\n ]\n const { rows: pinRows } = await query(\n `select distinct up_to_index from a2_snapshot_pins\n where session_id = $1 and reducer_name = $2\n and up_to_index = any($3::bigint[])`,\n [sessionId, reducerName, relevantIndexes],\n )\n const pinned = new Set(\n pinRows.map((row) => asNumber(row['up_to_index'])),\n )\n const historical = new Map<\n number,\n { index: number; state: unknown; updatedAt: number }\n >()\n if (\n advancesHead &&\n currentHead &&\n pinned.has(asNumber(currentHead['up_to_index']))\n ) {\n const index = asNumber(currentHead['up_to_index'])\n historical.set(index, {\n index,\n state: currentHead['state'],\n updatedAt: asNumber(currentHead['updated_at']),\n })\n }\n const updatedAt = clock.now().getTime()\n for (const write of normalized) {\n if (write.index < finalHeadIndex && pinned.has(write.index)) {\n historical.set(write.index, {\n index: write.index,\n state: write.state,\n updatedAt,\n })\n }\n }\n if (historical.size > 0) {\n await query(\n `insert into a2_snapshot_history\n (session_id, reducer_name, up_to_index, state, updated_at)\n select $1, $2, snapshot.index, snapshot.state, snapshot.\"updatedAt\"\n from jsonb_to_recordset($3::jsonb) as snapshot(\n index bigint,\n state jsonb,\n \"updatedAt\" bigint\n )\n on conflict (session_id, reducer_name, up_to_index) do update set\n state = excluded.state,\n updated_at = excluded.updated_at`,\n [sessionId, reducerName, jsonb([...historical.values()])],\n )\n }\n if (writesHead) {\n await query(\n `insert into a2_snapshots\n (session_id, reducer_name, up_to_index, state, updated_at)\n values ($1, $2, $3, $4::jsonb, $5)\n on conflict (session_id, reducer_name) do update set\n up_to_index = excluded.up_to_index,\n state = excluded.state,\n updated_at = excluded.updated_at\n where excluded.up_to_index >= a2_snapshots.up_to_index`,\n [\n sessionId,\n reducerName,\n latestWrite.index,\n jsonb(latestWrite.state),\n clock.now().getTime(),\n ],\n )\n await query(\n `delete from a2_snapshot_history\n where session_id = $1 and reducer_name = $2 and up_to_index = $3`,\n [sessionId, reducerName, latestWrite.index],\n )\n }\n }),\n )\n },\n\n presence: {\n async set(ns, participant, values, meta) {\n const entries = Object.entries(values)\n if (entries.length === 0) return\n await wrap(async () => {\n const atMs = meta.at.getTime()\n // Expiry anchors on the storage clock — the sender's `at`\n // orders writes but never anchors their lifetime.\n const expiresAtMs = clock.now().getTime() + meta.ttlMs\n const applied: Record<string, unknown> = nullProtoRecord()\n const write = async (\n query: PostgresConnection['query'],\n ): Promise<void> => {\n for (const [field, value] of entries) {\n // Field-wise LWW: a stored row with a strictly newer `at`\n // survives; ties go to the incoming write (memory parity).\n // oxlint-disable-next-line no-await-in-loop -- independent per-field registers\n const result = await (value === null\n ? query(\n `delete from a2_presence\n where ns = $1 and participant = $2 and field = $3\n and at <= $4${notifications ? ' returning field' : ''}`,\n [ns, participant, field, atMs],\n )\n : query(\n `insert into a2_presence\n (ns, participant, field, value, seen, at, expires_at)\n values ($1, $2, $3, $4::jsonb, $5, $6, $7)\n on conflict (ns, participant, field) do update set\n value = excluded.value,\n seen = excluded.seen,\n at = excluded.at,\n expires_at = excluded.expires_at\n where a2_presence.at <= excluded.at${notifications ? ' returning field' : ''}`,\n [\n ns,\n participant,\n field,\n jsonb(value),\n meta.seen,\n atMs,\n expiresAtMs,\n ],\n ))\n if (result.rows.length) applied[field] = structuredClone(value)\n }\n }\n if (notifications) await withTx(write)\n else {\n const c = await client()\n await write((text, parameters) => c.query(text, parameters))\n }\n if (Object.keys(applied).length) {\n const patch = {\n participant,\n values: applied,\n seen: meta.seen,\n at: new Date(atMs),\n }\n const subscription = presenceSubscriptions.get(ns)\n subscription?.poll.update(patch)\n for (const listener of subscription?.listeners ?? [])\n listener(patch)\n }\n })\n },\n\n async read(ns) {\n return wrap(async () => {\n const c = await client()\n const now = clock.now().getTime()\n // Sweep and read share one statement (one snapshot): the\n // delete is opportunistic cleanup; the filter alone keeps\n // expired rows out of this result.\n const { rows } = await c.query(\n `with swept as (\n delete from a2_presence\n where ns = $1 and expires_at <= $2\n )\n select participant, field, value, seen, at, expires_at\n from a2_presence\n where ns = $1 and expires_at > $2\n order by participant, field`,\n [ns, now],\n )\n return rows.map((row) => ({\n participant: row['participant'] as string,\n field: row['field'] as string,\n value: row['value'],\n seen: asNumber(row['seen']),\n at: new Date(asNumber(row['at'])),\n expiresAt: new Date(asNumber(row['expires_at'])),\n }))\n })\n },\n },\n\n stream(sessionId, opts) {\n const readAfter = (afterIndex: number): Promise<Event[]> =>\n wrap(async () => {\n const c = await client()\n const { rows } = await c.query(\n `select session_id, idx, event_type, payload, event_id, created_at\n from a2_events\n where session_id = $1 and idx > $2\n order by idx`,\n [sessionId, afterIndex],\n )\n return rows.map(toEvent)\n })\n if (notifications)\n return notifications.stream({\n channel: postgresNotificationChannel({ kind: 'events', sessionId }),\n read: readAfter,\n startAfter: opts?.startAfter ?? 0,\n })\n return pollingStream(\n readAfter,\n opts?.startAfter !== undefined ? { startAfter: opts.startAfter } : {},\n )\n },\n\n async close() {\n if (closed) return\n closed = true\n for (const subscription of presenceSubscriptions.values())\n subscription.stop()\n presenceSubscriptions.clear()\n await notifications?.close()\n const lease = await connection.peek()?.catch(() => undefined)\n await (injected ?? lease)?.release()\n },\n }\n if (notifications) {\n const presence = store.presence!\n const read = presence.read\n presence.read = async (ns) => {\n const subscription = presenceSubscriptions.get(ns)\n await subscription?.ready\n await subscription?.poll.ready\n return read(ns)\n }\n presence.subscribe = (ns, onPatch) => {\n if (closed) throw new A2Error('STORE_UNAVAILABLE', 'the store was closed')\n let subscription = presenceSubscriptions.get(ns)\n if (!subscription) {\n const listeners = new Set<(patch: PresencePatch) => void>()\n let dirty = false\n let ended = false\n let waiting: Sleeper | undefined\n const lease = notifications.watch({\n channel: postgresNotificationChannel({\n kind: 'presence',\n sessionId: ns,\n }),\n notify: (source) => {\n if (source === instanceId) return\n dirty = true\n waiting?.cancel()\n },\n })\n const poll = pollPresence({\n read: async () => {\n await lease.ready\n if (ended || closed) return []\n dirty = false\n return read(ns)\n },\n deliver: (patch) => {\n for (const listener of listeners) listener(patch)\n },\n sleep: () => {\n waiting = defaultSleep(dirty ? 0 : NOTIFY_TIMINGS.safetyReadMs)\n return waiting\n },\n })\n void lease.ready.catch(() => poll.stop())\n subscription = {\n listeners,\n ready: lease.ready,\n poll,\n stop() {\n ended = true\n poll.stop()\n lease.stop()\n waiting?.cancel()\n },\n }\n presenceSubscriptions.set(ns, subscription)\n }\n subscription.listeners.add(onPatch)\n let stopped = false\n return () => {\n if (stopped) return\n stopped = true\n subscription.listeners.delete(onPatch)\n if (subscription.listeners.size === 0) {\n presenceSubscriptions.delete(ns)\n subscription.stop()\n }\n }\n }\n }\n return store\n}\n"],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import { i as POLL_TIMINGS } from "./internal-Dq2qYxou.js";
2
+ import { t as defaultSleep } from "./store-polling-CmxUbV93.js";
3
+ //#region src/store-presence-polling.ts
4
+ const key = ({ participant, field }) => JSON.stringify([participant, field]);
5
+ function pollPresence({ read, deliver, sleep = defaultSleep }) {
6
+ let stopped = false;
7
+ let pending;
8
+ let interval = Math.min(POLL_TIMINGS.activeFloorMs, POLL_TIMINGS.idleCeilingMs);
9
+ let version = 0;
10
+ const seen = /* @__PURE__ */ new Map();
11
+ const activate = () => {
12
+ version += 1;
13
+ interval = Math.min(POLL_TIMINGS.activeFloorMs, POLL_TIMINGS.idleCeilingMs);
14
+ pending?.cancel();
15
+ };
16
+ const ready = read().then((rows) => {
17
+ if (stopped) return;
18
+ for (const row of rows) seen.set(key(row), row);
19
+ }).catch(() => void 0);
20
+ const run = async () => {
21
+ await ready;
22
+ for (;;) {
23
+ if (stopped) return;
24
+ const before = version;
25
+ pending = sleep(interval);
26
+ await pending.promise;
27
+ if (stopped) return;
28
+ try {
29
+ const reading = version;
30
+ const rows = await read();
31
+ if (stopped) return;
32
+ if (version !== reading) continue;
33
+ const live = /* @__PURE__ */ new Set();
34
+ let changed = false;
35
+ for (const row of rows) {
36
+ const id = key(row);
37
+ live.add(id);
38
+ const prior = seen.get(id);
39
+ seen.set(id, row);
40
+ if (prior?.seen === row.seen && prior.at.getTime() === row.at.getTime() && JSON.stringify(prior.value) === JSON.stringify(row.value)) continue;
41
+ changed = true;
42
+ deliver({
43
+ participant: row.participant,
44
+ values: { [row.field]: row.value },
45
+ seen: row.seen,
46
+ at: row.at
47
+ });
48
+ }
49
+ for (const [id, row] of seen) {
50
+ if (live.has(id)) continue;
51
+ seen.delete(id);
52
+ changed = true;
53
+ deliver({
54
+ participant: row.participant,
55
+ values: { [row.field]: null },
56
+ seen: row.seen,
57
+ at: /* @__PURE__ */ new Date()
58
+ });
59
+ }
60
+ if (changed) activate();
61
+ else if (version === before) interval = Math.min(interval * 2, POLL_TIMINGS.idleCeilingMs);
62
+ } catch {
63
+ interval = POLL_TIMINGS.idleCeilingMs;
64
+ }
65
+ }
66
+ };
67
+ run();
68
+ return {
69
+ ready,
70
+ wake: activate,
71
+ update(patch) {
72
+ for (const [field, value] of Object.entries(patch.values)) seen.set(key({
73
+ participant: patch.participant,
74
+ field
75
+ }), {
76
+ participant: patch.participant,
77
+ field,
78
+ value,
79
+ seen: patch.seen,
80
+ at: patch.at
81
+ });
82
+ activate();
83
+ },
84
+ stop() {
85
+ stopped = true;
86
+ pending?.cancel();
87
+ seen.clear();
88
+ }
89
+ };
90
+ }
91
+ //#endregion
92
+ export { pollPresence as t };
93
+
94
+ //# sourceMappingURL=store-presence-polling-C7-XZyW9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-presence-polling-C7-XZyW9.js","names":[],"sources":["../src/store-presence-polling.ts"],"sourcesContent":["import type { PresencePatch } from './contract.ts'\nimport { POLL_TIMINGS } from './internal.ts'\nimport { defaultSleep, type Sleeper } from './store-polling.ts'\nimport type { PresenceRow } from './store.ts'\n\nconst key = ({\n participant,\n field,\n}: Pick<PresenceRow, 'participant' | 'field'>): string =>\n JSON.stringify([participant, field])\n\nexport function pollPresence({\n read,\n deliver,\n sleep = defaultSleep,\n}: {\n read: () => Promise<PresenceRow[]>\n deliver: (patch: PresencePatch) => void\n sleep?: (ms: number) => Sleeper\n}): {\n ready: Promise<void>\n update(patch: PresencePatch): void\n wake(): void\n stop(): void\n} {\n let stopped = false\n let pending: Sleeper | undefined\n let interval = Math.min(\n POLL_TIMINGS.activeFloorMs,\n POLL_TIMINGS.idleCeilingMs,\n )\n let version = 0\n const seen = new Map<string, Omit<PresenceRow, 'expiresAt'>>()\n const activate = (): void => {\n version += 1\n interval = Math.min(POLL_TIMINGS.activeFloorMs, POLL_TIMINGS.idleCeilingMs)\n pending?.cancel()\n }\n const ready = read()\n .then((rows) => {\n if (stopped) return\n for (const row of rows) seen.set(key(row), row)\n return undefined\n })\n // Seeding is best effort; foreground snapshots still report read errors.\n .catch(() => undefined)\n\n const run = async (): Promise<void> => {\n await ready\n for (;;) {\n if (stopped) return\n const before = version\n pending = sleep(interval)\n // oxlint-disable-next-line no-await-in-loop -- adaptive presence polling\n await pending.promise\n if (stopped) return\n try {\n const reading = version\n // oxlint-disable-next-line no-await-in-loop -- read each new presence snapshot\n const rows = await read()\n if (stopped) return\n if (version !== reading) continue\n const live = new Set<string>()\n let changed = false\n for (const row of rows) {\n const id = key(row)\n live.add(id)\n const prior = seen.get(id)\n seen.set(id, row)\n if (\n prior?.seen === row.seen &&\n prior.at.getTime() === row.at.getTime() &&\n JSON.stringify(prior.value) === JSON.stringify(row.value)\n )\n continue\n changed = true\n deliver({\n participant: row.participant,\n values: { [row.field]: row.value },\n seen: row.seen,\n at: row.at,\n })\n }\n for (const [id, row] of seen) {\n if (live.has(id)) continue\n seen.delete(id)\n changed = true\n deliver({\n participant: row.participant,\n values: { [row.field]: null },\n seen: row.seen,\n at: new Date(),\n })\n }\n if (changed) activate()\n else if (version === before)\n interval = Math.min(interval * 2, POLL_TIMINGS.idleCeilingMs)\n } catch {\n interval = POLL_TIMINGS.idleCeilingMs\n }\n }\n }\n void run()\n return {\n ready,\n wake: activate,\n update(patch) {\n for (const [field, value] of Object.entries(patch.values)) {\n seen.set(key({ participant: patch.participant, field }), {\n participant: patch.participant,\n field,\n value,\n seen: patch.seen,\n at: patch.at,\n })\n }\n activate()\n },\n stop() {\n stopped = true\n pending?.cancel()\n seen.clear()\n },\n }\n}\n"],"mappings":";;;AAKA,MAAM,OAAO,EACX,aACA,YAEA,KAAK,UAAU,CAAC,aAAa,KAAK,CAAC;AAErC,SAAgB,aAAa,EAC3B,MACA,SACA,QAAQ,gBAUR;CACA,IAAI,UAAU;CACd,IAAI;CACJ,IAAI,WAAW,KAAK,IAClB,aAAa,eACb,aAAa,aACf;CACA,IAAI,UAAU;CACd,MAAM,uBAAO,IAAI,IAA4C;CAC7D,MAAM,iBAAuB;EAC3B,WAAW;EACX,WAAW,KAAK,IAAI,aAAa,eAAe,aAAa,aAAa;EAC1E,SAAS,OAAO;CAClB;CACA,MAAM,QAAQ,KAAK,CAAC,CACjB,MAAM,SAAS;EACd,IAAI,SAAS;EACb,KAAK,MAAM,OAAO,MAAM,KAAK,IAAI,IAAI,GAAG,GAAG,GAAG;CAEhD,CAAC,CAAC,CAED,YAAY,KAAA,CAAS;CAExB,MAAM,MAAM,YAA2B;EACrC,MAAM;EACN,SAAS;GACP,IAAI,SAAS;GACb,MAAM,SAAS;GACf,UAAU,MAAM,QAAQ;GAExB,MAAM,QAAQ;GACd,IAAI,SAAS;GACb,IAAI;IACF,MAAM,UAAU;IAEhB,MAAM,OAAO,MAAM,KAAK;IACxB,IAAI,SAAS;IACb,IAAI,YAAY,SAAS;IACzB,MAAM,uBAAO,IAAI,IAAY;IAC7B,IAAI,UAAU;IACd,KAAK,MAAM,OAAO,MAAM;KACtB,MAAM,KAAK,IAAI,GAAG;KAClB,KAAK,IAAI,EAAE;KACX,MAAM,QAAQ,KAAK,IAAI,EAAE;KACzB,KAAK,IAAI,IAAI,GAAG;KAChB,IACE,OAAO,SAAS,IAAI,QACpB,MAAM,GAAG,QAAQ,MAAM,IAAI,GAAG,QAAQ,KACtC,KAAK,UAAU,MAAM,KAAK,MAAM,KAAK,UAAU,IAAI,KAAK,GAExD;KACF,UAAU;KACV,QAAQ;MACN,aAAa,IAAI;MACjB,QAAQ,GAAG,IAAI,QAAQ,IAAI,MAAM;MACjC,MAAM,IAAI;MACV,IAAI,IAAI;KACV,CAAC;IACH;IACA,KAAK,MAAM,CAAC,IAAI,QAAQ,MAAM;KAC5B,IAAI,KAAK,IAAI,EAAE,GAAG;KAClB,KAAK,OAAO,EAAE;KACd,UAAU;KACV,QAAQ;MACN,aAAa,IAAI;MACjB,QAAQ,GAAG,IAAI,QAAQ,KAAK;MAC5B,MAAM,IAAI;MACV,oBAAI,IAAI,KAAK;KACf,CAAC;IACH;IACA,IAAI,SAAS,SAAS;SACjB,IAAI,YAAY,QACnB,WAAW,KAAK,IAAI,WAAW,GAAG,aAAa,aAAa;GAChE,QAAQ;IACN,WAAW,aAAa;GAC1B;EACF;CACF;CACA,IAAS;CACT,OAAO;EACL;EACA,MAAM;EACN,OAAO,OAAO;GACZ,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,MAAM,MAAM,GACtD,KAAK,IAAI,IAAI;IAAE,aAAa,MAAM;IAAa;GAAM,CAAC,GAAG;IACvD,aAAa,MAAM;IACnB;IACA;IACA,MAAM,MAAM;IACZ,IAAI,MAAM;GACZ,CAAC;GAEH,SAAS;EACX;EACA,OAAO;GACL,UAAU;GACV,SAAS,OAAO;GAChB,KAAK,MAAM;EACb;CACF;AACF"}
@@ -1,4 +1,4 @@
1
- import { r as Clock, s as IdSource, t as A2Store } from "./store-DGHeBtIQ.js";
1
+ import { r as Clock, s as IdSource, t as A2Store } from "./store-DtDOWLSn.js";
2
2
  //#region src/store-redis-http.d.ts
3
3
  type RedisHttpStoreOptions = {
4
4
  /** REST endpoint, e.g. `process.env.UPSTASH_REDIS_REST_URL`. */
@@ -13,7 +13,7 @@ type RedisHttpStoreOptions = {
13
13
  ids?: IdSource;
14
14
  };
15
15
  type RedisHttpStore = A2Store & {
16
- /** Nothing to disconnect; present so callers can treat stores uniformly. */
16
+ /** Close live subscriptions and pending reconnects. */
17
17
  close(): Promise<void>;
18
18
  };
19
19
  declare function redisHttp(options: RedisHttpStoreOptions): RedisHttpStore;
@@ -1 +1 @@
1
- {"version":3,"file":"store-redis-http.d.ts","names":[],"sources":["../src/store-redis-http.ts"],"mappings":";;KA0BY;;EAEV;;EAEA;;EAEA;;EAEA,QAAQ;;EAER,MAAM;;KAGI,iBAAiB;;EAE3B,SAAS;;iBAGK,UAAU,SAAS,wBAAwB"}
1
+ {"version":3,"file":"store-redis-http.d.ts","names":[],"sources":["../src/store-redis-http.ts"],"mappings":";;KAeY;;EAEV;;EAEA;;EAEA;;EAEA,QAAQ;;EAER,MAAM;;KAGI,iBAAiB;;EAE3B,SAAS;;iBAGK,UAAU,SAAS,wBAAwB"}