experimental-a2 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/{actor-CqibLgjQ.d.ts → actor-Bqyb7gwf.d.ts} +3 -3
- package/dist/{actor-CqibLgjQ.d.ts.map → actor-Bqyb7gwf.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-client.js +2 -2
- package/dist/actor-react.d.ts +2 -2
- package/dist/actor-react.js +5 -5
- package/dist/{actor-shared-DFET5q16.js → actor-shared-B4OB6Qo7.js} +3 -3
- package/dist/{actor-shared-DFET5q16.js.map → actor-shared-B4OB6Qo7.js.map} +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +4 -4
- package/dist/{ai-DAu77CDr.d.ts → ai-BZUu5-b7.d.ts} +2 -2
- package/dist/{ai-DAu77CDr.d.ts.map → ai-BZUu5-b7.d.ts.map} +1 -1
- package/dist/{ai-EEfKyc-z.js → ai-CCCNxd3L.js} +3 -3
- package/dist/{ai-EEfKyc-z.js.map → ai-CCCNxd3L.js.map} +1 -1
- package/dist/ai-client.d.ts +2 -2
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.d.ts +1 -1
- package/dist/ai-fx.js +25 -3
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-DTjkAr2T.d.ts → ai-harness-C8aV869s.d.ts} +2 -2
- package/dist/{ai-harness-DTjkAr2T.d.ts.map → ai-harness-C8aV869s.d.ts.map} +1 -1
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-react.d.ts +4 -4
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +3 -3
- package/dist/ai-server.js +4 -4
- package/dist/ai.d.ts +1 -1
- package/dist/ai.js +1 -1
- package/dist/{client-CUMbS_38.d.ts → client-CCJASjUM.d.ts} +2 -2
- package/dist/{client-CUMbS_38.d.ts.map → client-CCJASjUM.d.ts.map} +1 -1
- package/dist/{client-BQS4i_V9.js → client-sq-djxhD.js} +4 -4
- package/dist/{client-BQS4i_V9.js.map → client-sq-djxhD.js.map} +1 -1
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/{contract-CWukt01d.js → contract-DqfBSIqJ.js} +2 -2
- package/dist/{contract-CWukt01d.js.map → contract-DqfBSIqJ.js.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/{initial-snapshot-D3Lve43o.d.ts → initial-snapshot-BA6xwv10.d.ts} +2 -2
- package/dist/initial-snapshot-BA6xwv10.d.ts.map +1 -0
- package/dist/{react-BoHvC3v9.d.ts → react-KC4eW7yG.d.ts} +3 -3
- package/dist/{react-BoHvC3v9.d.ts.map → react-KC4eW7yG.d.ts.map} +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{reducer-CjmvZSAb.js → reducer-Bswr11ar.js} +2 -2
- package/dist/{reducer-CjmvZSAb.js.map → reducer-Bswr11ar.js.map} +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +2 -2
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +1 -1
- package/dist/{server-qgez0Xny.d.ts → server-B9gu0qi1.d.ts} +9 -3
- package/dist/server-B9gu0qi1.d.ts.map +1 -0
- package/dist/{server-DtFrWxi3.js → server-DX7mM6nm.js} +22 -14
- package/dist/server-DX7mM6nm.js.map +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/{session-clear-wHd5GCpZ.js → session-clear-XJYpmUpg.js} +14 -2
- package/dist/session-clear-XJYpmUpg.js.map +1 -0
- package/dist/{session-clear-D1PFe7xl.d.ts → session-clear-vfonou9S.d.ts} +2 -2
- package/dist/session-clear-vfonou9S.d.ts.map +1 -0
- package/dist/{store-D0MhBvg6.d.ts → store-DEzcHZOA.d.ts} +11 -1
- package/dist/store-DEzcHZOA.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/{store-codec-DwrWlgQx.js → store-codec-pAG2oPgQ.js} +4 -4
- package/dist/store-codec-pAG2oPgQ.js.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +11 -4
- package/dist/store-memory.js.map +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +26 -7
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +2 -2
- package/dist/{store-redis-notify-CsJw_hfD.js → store-redis-notify-ys_-VrSP.js} +34 -18
- package/dist/store-redis-notify-ys_-VrSP.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +2 -2
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +11 -4
- package/dist/store-sqlite.js.map +1 -1
- package/dist/store-valkey.d.ts +33 -0
- package/dist/store-valkey.d.ts.map +1 -0
- package/dist/store-valkey.js +218 -0
- package/dist/store-valkey.js.map +1 -0
- package/dist/{wire-CV-Z7wz-.js → wire-BFE9sIQf.js} +3 -2
- package/dist/wire-BFE9sIQf.js.map +1 -0
- package/docs/client/03-presence.mdx +1 -0
- package/docs/contract/03-sessions.mdx +51 -0
- package/docs/reference/01-core.mdx +1 -0
- package/docs/reference/02-server.mdx +44 -4
- package/docs/reference/06-errors.mdx +1 -0
- package/docs/stores/01-overview.mdx +1 -0
- package/docs/stores/02-redis.mdx +3 -1
- package/docs/stores/07-valkey.mdx +97 -0
- package/examples/playground/app/a2slack/consumers/search.ts +1 -1
- package/examples/playground/app/a2slack/db/migrations/0005_a2slack_feed_v2.sql +7 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0005_snapshot.json +1165 -0
- package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +8 -1
- package/examples/playground/app/a2slack/db/queries.ts +1 -1
- package/examples/playground/app/a2slack/drills/a2slack.drill.ts +17 -14
- package/examples/playground/app/a2slack/search-index.ts +1 -1
- package/examples/playground/app/a2slack/server.ts +2 -2
- package/examples/playground/lib/store.ts +2 -0
- package/examples/playground/package.json +1 -1
- package/package.json +8 -2
- package/src/ai-fx.ts +46 -3
- package/src/errors.ts +39 -0
- package/src/idempotent-replay.ts +4 -3
- package/src/libfx.d.ts +2 -0
- package/src/server.ts +32 -9
- package/src/store-memory.ts +26 -4
- package/src/store-postgres.ts +36 -6
- package/src/store-redis-core.ts +40 -15
- package/src/store-redis-notify.ts +8 -2
- package/src/store-sqlite.ts +25 -4
- package/src/store-valkey.ts +376 -0
- package/src/store.ts +10 -0
- package/src/wire.ts +1 -0
- package/dist/initial-snapshot-D3Lve43o.d.ts.map +0 -1
- package/dist/server-DtFrWxi3.js.map +0 -1
- package/dist/server-qgez0Xny.d.ts.map +0 -1
- package/dist/session-clear-D1PFe7xl.d.ts.map +0 -1
- package/dist/session-clear-wHd5GCpZ.js.map +0 -1
- package/dist/store-D0MhBvg6.d.ts.map +0 -1
- package/dist/store-codec-DwrWlgQx.js.map +0 -1
- package/dist/store-redis-notify-CsJw_hfD.js.map +0 -1
- package/dist/wire-CV-Z7wz-.js.map +0 -1
package/dist/store-redis.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as Clock, s as IdSource, t as A2Store } from "./store-
|
|
1
|
+
import { r as Clock, s as IdSource, t as A2Store } from "./store-DEzcHZOA.js";
|
|
2
2
|
//#region src/store-redis-notify.d.ts
|
|
3
3
|
type RedisConnection = {
|
|
4
4
|
call(command: string, ...args: Array<string | number>): Promise<unknown>;
|
package/dist/store-redis.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { i as A2Error } from "./session-clear-
|
|
1
|
+
import { i as A2Error } from "./session-clear-XJYpmUpg.js";
|
|
2
2
|
import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
|
|
3
3
|
import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
|
|
4
|
-
import { t as createRedisNotifyStore } from "./store-redis-notify-
|
|
4
|
+
import { t as createRedisNotifyStore } from "./store-redis-notify-ys_-VrSP.js";
|
|
5
5
|
//#region src/store-redis.ts
|
|
6
6
|
/**
|
|
7
7
|
* experimental-a2/store-redis — the Redis-protocol store backend, on Redis
|
package/dist/store-sqlite.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as Clock, s as IdSource, t as A2Store } from "./store-
|
|
1
|
+
import { r as Clock, s as IdSource, t as A2Store } from "./store-DEzcHZOA.js";
|
|
2
2
|
//#region src/store-sqlite.d.ts
|
|
3
3
|
type SqliteStoreOptions = {
|
|
4
4
|
/** Database file path. Defaults to `.a2/dev.db`; `:memory:` works. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-sqlite.d.ts","names":[],"sources":["../src/store-sqlite.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"store-sqlite.d.ts","names":[],"sources":["../src/store-sqlite.ts"],"mappings":";;KAgDY;;EAEV;;EAEA,QAAQ;;EAER,MAAM;;KAGI,cAAc;;EAExB;;iBAySc,OAAO,UAAS,qBAA0B"}
|
package/dist/store-sqlite.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as A2Error, r as sessionClearedError, s as
|
|
1
|
+
import { c as lastIndexMismatchError, i as A2Error, l as supersededAttemptError, r as sessionClearedError, s as duplicateBatchError, t as CLEAR_EVENT_TYPE } from "./session-clear-XJYpmUpg.js";
|
|
2
2
|
import { n as pollingStream } from "./store-polling-BZt2Rf3D.js";
|
|
3
|
-
import { a as idempotentReplay, i as idleLogCursor, n as decodeCause, r as decodeReturnedEventIds, t as cursorKeyOf } from "./store-codec-
|
|
3
|
+
import { a as idempotentReplay, i as idleLogCursor, n as decodeCause, r as decodeReturnedEventIds, t as cursorKeyOf } from "./store-codec-pAG2oPgQ.js";
|
|
4
4
|
import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
6
|
import { mkdirSync } from "node:fs";
|
|
@@ -470,14 +470,20 @@ function sqlite(options = {}) {
|
|
|
470
470
|
const clearIndex = clearIndexOf(sessionId);
|
|
471
471
|
const suppliedIds = events.filter((e) => e.id !== void 0).map((e) => e.id);
|
|
472
472
|
if (new Set(suppliedIds).size !== suppliedIds.length) throw new A2Error("PARTIAL_DUPLICATE_BATCH", "batch contains the same event id more than once");
|
|
473
|
+
const ifLastIndex = appendOptions?.ifLastIndex;
|
|
474
|
+
const conditional = ifLastIndex === void 0 ? void 0 : {
|
|
475
|
+
sessionId,
|
|
476
|
+
ifLastIndex,
|
|
477
|
+
head: headOf(sessionId)
|
|
478
|
+
};
|
|
473
479
|
if (suppliedIds.length > 0) {
|
|
474
480
|
const existing = liveOwnersOf(sessionId, clearIndex, suppliedIds);
|
|
475
481
|
if (existing.length > 0) {
|
|
476
482
|
if (existing.length === events.length) return {
|
|
477
|
-
events: idempotentReplay(events, existing.map(toStored)),
|
|
483
|
+
events: idempotentReplay(events, existing.map(toStored), conditional),
|
|
478
484
|
hasPending: hasPending(sessionId, clearIndex, consumers)
|
|
479
485
|
};
|
|
480
|
-
throw
|
|
486
|
+
throw duplicateBatchError(`batch mixes ${existing.length} already-appended and ${events.length - existing.length} fresh events`, conditional);
|
|
481
487
|
}
|
|
482
488
|
}
|
|
483
489
|
const originIndex = appendOptions?.originIndex;
|
|
@@ -487,6 +493,7 @@ function sqlite(options = {}) {
|
|
|
487
493
|
cause: e.cause,
|
|
488
494
|
clearIndex
|
|
489
495
|
});
|
|
496
|
+
if (conditional && conditional.head !== conditional.ifLastIndex) throw lastIndexMismatchError(sessionId, conditional.ifLastIndex, conditional.head);
|
|
490
497
|
return {
|
|
491
498
|
events: insertBatch({
|
|
492
499
|
sessionId,
|
package/dist/store-sqlite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-sqlite.js","names":[],"sources":["../src/store-sqlite.ts"],"sourcesContent":["/**\n * experimental-a2/store-sqlite — sqlite store backend (the dev default, `.a2/dev.db`).\n *\n * Built on `node:sqlite` (Node ≥ 22.13) so it ships with zero\n * dependencies. Implements the A2Store interface; the conformance suite\n * in test/conformance is the executable contract.\n *\n * The spec's `index` column is stored as `idx` (a2-implementation.md\n * §11 — `index` collides with a reserved word) and mapped back at the\n * API boundary. Timestamps are epoch milliseconds written from the\n * injected clock — never SQL `now()` — so tests can time-travel against\n * real storage.\n */\n\nimport { mkdirSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { DatabaseSync } from 'node:sqlite'\nimport { A2Error, supersededAttemptError } from './errors.ts'\nimport { CLEAR_EVENT_TYPE, sessionClearedError } from './session-clear.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport {\n cursorKeyOf,\n decodeCause,\n decodeReturnedEventIds,\n idleLogCursor,\n} from './store-codec.ts'\nimport { pollingStream } from './store-polling.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type AppendEvent,\n type Clock,\n type Event,\n type EventCause,\n type HeadEvent,\n type HeadRef,\n type IdSource,\n type StoreConsumer,\n type StoredCursor,\n type StoredEvent,\n} from './store.ts'\n\nexport type SqliteStoreOptions = {\n /** Database file path. Defaults to `.a2/dev.db`; `:memory:` works. */\n path?: string\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 SqliteStore = A2Store & {\n /** Close the underlying database handle. */\n close(): void\n}\n\n/** The cursor layout; databases from before it are rejected, not migrated. */\nconst LAYOUT = 'cursors-1'\n\nconst SCHEMA = `\ncreate table if not exists a2_meta (\n key text not null primary key,\n value text not null\n) strict;\n\ncreate table if not exists a2_events (\n session_id text not null,\n idx integer not null,\n event_type text not null,\n payload text not null,\n event_id text not null,\n created_at integer not null,\n cause text,\n primary key (session_id, idx)\n) strict;\n\ncreate unique index if not exists a2_events_session_event_id\n on a2_events (session_id, event_id);\ndrop index if exists a2_events_event_id;\ncreate index if not exists a2_events_types on a2_events (session_id, event_type, idx);\n\ncreate table if not exists a2_cursors (\n session_id text not null,\n consumer text not null,\n cursor_key text not null,\n lane text,\n kind text not null,\n position integer not null,\n head integer,\n types text,\n attempt_count integer not null default 0,\n first_claimed_at integer,\n last_claimed_at integer,\n claim_holder text,\n claim_expires_at integer,\n failure_count integer not null default 0,\n last_failed_at integer,\n last_failed_attempt integer,\n last_error text,\n failed_at integer,\n completed_idx integer,\n completed_attempt integer,\n returned_event_ids text,\n primary key (session_id, consumer, cursor_key)\n) strict;\n\ncreate index if not exists a2_cursors_heads\n on a2_cursors (session_id, head) where head is not null;\n\ncreate table if not exists a2_lane_events (\n session_id text not null,\n consumer text not null,\n lane text not null,\n idx integer not null,\n done integer not null default 0,\n completed_attempt integer,\n returned_event_ids text,\n primary key (session_id, consumer, lane, idx)\n) strict;\n\ncreate table if not exists a2_snapshots (\n session_id text not null,\n reducer_name text not null,\n up_to_index integer not null,\n state text not null,\n updated_at integer not null,\n primary key (session_id, reducer_name)\n) strict;\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 integer not null,\n state text not null,\n updated_at integer not null,\n primary key (session_id, reducer_name, up_to_index)\n) strict;\n\ncreate table if not exists a2_snapshot_pins (\n session_id text not null,\n consumer text not null,\n cursor_key text not null,\n event_index integer not null,\n reducer_name text not null,\n up_to_index integer not null,\n primary key (session_id, consumer, cursor_key, event_index, reducer_name, up_to_index)\n) strict;\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_clears (\n session_id text not null primary key,\n clear_index integer not null\n) strict;\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 text not null,\n seen integer not null,\n at integer not null,\n expires_at integer not null,\n primary key (ns, participant, field)\n) strict;\n`\n\n/**\n * Switching journal modes takes an exclusive lock, and — unlike normal\n * statements — the switch can return SQLITE_BUSY without consulting the\n * busy handler while several connections race it (instances cold-booting\n * against one file). Bounded synchronous retry; the window is boot-only\n * and tiny. Found by the multi-process torture test.\n */\nconst setWalJournalMode = (db: DatabaseSync): void => {\n for (let attempt = 0; ; attempt += 1) {\n try {\n db.exec('pragma journal_mode = wal')\n return\n } catch (err) {\n const busy = (err as { errcode?: number }).errcode === 5\n if (!busy || attempt >= 100) throw err\n // Synchronous 5ms sleep — sqlite() is a sync constructor.\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5)\n }\n }\n}\n\nconst wrap = <T>(fn: () => T): T => {\n try {\n return fn()\n } catch (err) {\n if (err instanceof A2Error || err instanceof TypeError) throw err\n throw new A2Error('STORE_UNAVAILABLE', 'sqlite store operation failed', {\n cause: err,\n })\n }\n}\n\nconst toDate = (ms: number | bigint | null): Date | null =>\n ms === null ? null : new Date(Number(ms))\n\nconst toNumber = (value: number | bigint | null): number | null =>\n value === null ? null : Number(value)\n\ntype EventRow = {\n session_id: string\n idx: number | bigint\n event_type: string\n payload: string\n event_id: string\n created_at: number | bigint\n cause: string | null\n}\n\ntype CursorRow = {\n session_id: string\n consumer: string\n cursor_key: string\n lane: string | null\n kind: 'log' | 'lane' | 'event'\n position: number | bigint\n head: number | bigint | null\n types: string | null\n attempt_count: number | bigint\n first_claimed_at: number | bigint | null\n last_claimed_at: number | bigint | null\n claim_holder: string | null\n claim_expires_at: number | bigint | null\n failure_count: number | bigint\n last_failed_at: number | bigint | null\n last_failed_attempt: number | bigint | null\n last_error: string | null\n failed_at: number | bigint | null\n completed_idx: number | bigint | null\n completed_attempt: number | bigint | null\n returned_event_ids: string | null\n}\n\n/** A cursor joined with its head event's columns, prefixed `e_`. */\ntype HeadRow = CursorRow & {\n e_idx: number | bigint\n e_event_type: string\n e_payload: string\n e_event_id: string\n e_created_at: number | bigint\n e_cause: string | null\n}\n\nconst toStored = (row: EventRow): StoredEvent => ({\n id: row.event_id,\n type: row.event_type,\n payload: JSON.parse(row.payload),\n index: Number(row.idx),\n sessionId: row.session_id,\n createdAt: new Date(Number(row.created_at)),\n cause: decodeCause(row.cause),\n})\n\nconst toEvent = (row: EventRow): Event => ({\n id: row.event_id,\n type: row.event_type,\n payload: JSON.parse(row.payload),\n index: Number(row.idx),\n sessionId: row.session_id,\n createdAt: new Date(Number(row.created_at)),\n})\n\nconst toCursor = (row: CursorRow): StoredCursor => ({\n consumer: row.consumer,\n lane: row.lane,\n position: Number(row.position),\n head: toNumber(row.head),\n firstClaimedAt: toDate(row.first_claimed_at),\n lastClaimedAt: toDate(row.last_claimed_at),\n attemptCount: Number(row.attempt_count),\n claimHolder: row.claim_holder,\n claimExpiresAt: toDate(row.claim_expires_at),\n failureCount: Number(row.failure_count),\n lastFailedAt: toDate(row.last_failed_at),\n lastFailedAttempt: toNumber(row.last_failed_attempt),\n lastError: row.last_error,\n failedAt: toDate(row.failed_at),\n})\n\nconst toHead = (row: HeadRow): HeadEvent => {\n const { position: _position, head: _head, ...dispatch } = toCursor(row)\n return {\n ...toStored({\n session_id: row.session_id,\n idx: row.e_idx,\n event_type: row.e_event_type,\n payload: row.e_payload,\n event_id: row.e_event_id,\n created_at: row.e_created_at,\n cause: row.e_cause,\n }),\n ...dispatch,\n }\n}\n\ntype PresenceDbRow = {\n participant: string\n field: string\n value: string\n seen: number | bigint\n at: number | bigint\n expires_at: number | bigint\n}\n\ntype StateReadRow = Partial<EventRow> & {\n row_kind: 'snapshot' | 'event'\n clear_index: number | bigint | null\n head_index: number | bigint | null\n snapshot_index: number | bigint | null\n snapshot_state: string | null\n}\n\ntype SnapshotHeadRow = {\n up_to_index: number | bigint\n state: string\n updated_at: number | bigint\n}\n\nconst RECLAIM_BATCH = 256\n\n/** The head columns every claim, dead letter, and listing joins in. */\nconst HEAD_SELECT = `\n select cursor.*,\n event.idx as e_idx, event.event_type as e_event_type,\n event.payload as e_payload, event.event_id as e_event_id,\n event.created_at as e_created_at, event.cause as e_cause\n from a2_cursors cursor\n join a2_events event\n on event.session_id = cursor.session_id and event.idx = cursor.head`\n\n/** A fresh head's dispatch columns, for `set` clauses. */\nconst FRESH_DISPATCH = `\n attempt_count = 0, first_claimed_at = null, last_claimed_at = null,\n claim_holder = null, claim_expires_at = null, failure_count = 0,\n last_failed_at = null, last_failed_attempt = null, last_error = null,\n failed_at = null`\n\nconst placeholders = (count: number): string =>\n Array.from({ length: count }, () => '?').join(', ')\n\nconst compareText = (a: string, b: string): number =>\n a < b ? -1 : a > b ? 1 : 0\n\nexport function sqlite(options: SqliteStoreOptions = {}): SqliteStore {\n const path = options.path ?? '.a2/dev.db'\n const clock = options.clock ?? SYSTEM_CLOCK\n const generateId = options.ids ?? RANDOM_IDS\n\n if (path !== ':memory:') {\n // The dev-default backend only ever runs outside production, but\n // bundlers can't know that: without the ignore, Turbopack sees a\n // dynamic fs path and traces the entire project into the output.\n mkdirSync(dirname(resolve(/* turbopackIgnore: true */ path)), {\n recursive: true,\n })\n }\n const db = new DatabaseSync(path)\n // busy_timeout before anything that takes locks: concurrent\n // cold-opens (several instances booting at once) race the WAL switch\n // and schema creation.\n db.exec('pragma busy_timeout = 5000')\n setWalJournalMode(db)\n db.exec('pragma synchronous = normal')\n const legacy = db\n .prepare(\"select 1 from pragma_table_info('a2_events') where name = ?\")\n .get('processed_at')\n if (legacy) {\n db.close()\n throw new A2Error(\n 'STORE_NOT_CONFIGURED',\n `the sqlite database at '${path}' has the A2 layout from before consumers; delete it, or point the store at a fresh database file`,\n )\n }\n db.exec(SCHEMA)\n db.prepare(\n \"insert into a2_meta (key, value) values ('layout', ?) on conflict (key) do nothing\",\n ).run(LAYOUT)\n\n const insertEvent = db.prepare(\n `insert into a2_events\n (session_id, idx, event_type, payload, event_id, created_at, cause)\n values (?, ?, ?, ?, ?, ?, ?)`,\n )\n const selectHead = db.prepare(\n 'select coalesce(max(idx), 0) as max from a2_events where session_id = ?',\n )\n const headOf = (sessionId: string): number =>\n Number((selectHead.get(sessionId) as { max: number | bigint }).max)\n const selectEvent = db.prepare(\n 'select 1 from a2_events where session_id = ? and idx = ?',\n )\n const selectCursor = db.prepare(\n 'select * from a2_cursors where session_id = ? and consumer = ? and cursor_key = ?',\n )\n const cursorOf = (sessionId: string, ref: HeadRef): CursorRow | undefined =>\n selectCursor.get(\n sessionId,\n ref.consumer,\n cursorKeyOf(ref.lane, ref.index),\n ) as CursorRow | undefined\n const insertEventCursor = db.prepare(\n `insert into a2_cursors\n (session_id, consumer, cursor_key, lane, kind, position, head)\n values (?, ?, ?, null, 'event', ?, ?)`,\n )\n const insertLaneEvent = db.prepare(\n `insert into a2_lane_events (session_id, consumer, lane, idx)\n values (?, ?, ?, ?)`,\n )\n // A lane cursor whose head lies at or below the clear marker is idle: the\n // marker superseded everything it had queued.\n const queueLaneHead = db.prepare(\n `insert into a2_cursors\n (session_id, consumer, cursor_key, lane, kind, position, head)\n values (?, ?, ?, ?, 'lane', ?, ?)\n on conflict (session_id, consumer, cursor_key) do update set\n head = excluded.head,\n position = max(a2_cursors.position, excluded.position),\n ${FRESH_DISPATCH}\n where a2_cursors.head is null or a2_cursors.head <= excluded.position`,\n )\n const openLogCursor = db.prepare(\n `insert into a2_cursors\n (session_id, consumer, cursor_key, lane, kind, position, head, types)\n values (?, ?, ?, ?, 'log', ?, ?, ?)`,\n )\n const wakeLogCursor = db.prepare(\n `update a2_cursors set types = ?, head = ?\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n const refreshTypes = db.prepare(\n `update a2_cursors set types = ?\n where session_id = ? and consumer = ? and kind = 'log'\n and types is not ?`,\n )\n const nextLogHeadAll = db.prepare(\n `select min(idx) as next from a2_events\n where session_id = ? and idx > ? and event_type != ?`,\n )\n const nextLogHeadOf = db.prepare(\n `select min(idx) as next from a2_events\n where session_id = ? and idx > ? and event_type != ?\n and event_type in (select value from json_each(?))`,\n )\n const nextLogHead = (\n sessionId: string,\n after: number,\n types: string | null,\n ): number | null =>\n toNumber(\n (\n (types === null\n ? nextLogHeadAll.get(sessionId, after, CLEAR_EVENT_TYPE)\n : nextLogHeadOf.get(sessionId, after, CLEAR_EVENT_TYPE, types)) as {\n next: number | bigint | null\n }\n ).next,\n )\n const nextLaneHead = db.prepare(\n `select min(idx) as next from a2_lane_events\n where session_id = ? and consumer = ? and lane = ? and done = 0\n and idx > ?`,\n )\n const selectLaneEvent = db.prepare(\n `select completed_attempt, returned_event_ids from a2_lane_events\n where session_id = ? and consumer = ? and lane = ? and idx = ?`,\n )\n const finishLaneEvent = db.prepare(\n `update a2_lane_events\n set done = 1, completed_attempt = ?, returned_event_ids = ?\n where session_id = ? and consumer = ? and lane = ? and idx = ?`,\n )\n const moveCursor = db.prepare(\n `update a2_cursors set position = ?, head = ?, ${FRESH_DISPATCH}\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n const recordCompletion = db.prepare(\n `update a2_cursors\n set completed_idx = ?, completed_attempt = ?, returned_event_ids = ?\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n // A JSON null value is a tombstone: a delete's `at`, kept until it\n // expires so an older set arriving after it still loses.\n const upsertPresence = db.prepare(\n `insert into a2_presence (ns, participant, field, value, seen, at, expires_at)\n values (?, ?, ?, ?, ?, ?, ?)\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 or a2_presence.expires_at <= ?`,\n )\n const sweepPresence = db.prepare(\n 'delete from a2_presence where ns = ? and expires_at <= ?',\n )\n const selectPresence = db.prepare(\n `select participant, field, value, seen, at, expires_at\n from a2_presence\n where ns = ? and expires_at > ? and value != 'null'\n order by participant, field`,\n )\n const selectByIds = (count: number) =>\n db.prepare(\n `select * from a2_events\n where session_id = ? and event_id in (${placeholders(count)})\n order by idx`,\n )\n\n const selectClearIndex = db.prepare(\n 'select clear_index from a2_clears where session_id = ?',\n )\n const clearIndexOf = (sessionId: string): number =>\n Number(\n (\n selectClearIndex.get(sessionId) as\n { clear_index: number | bigint } | undefined\n )?.clear_index ?? 0,\n )\n const deleteEventById = db.prepare(\n 'delete from a2_events where session_id = ? and event_id = ?',\n )\n\n /** Served consumers as a SQL condition on `cursor.consumer`. */\n const servedClause = (\n consumers: readonly StoreConsumer[] | undefined,\n ): { sql: string; params: string[] } =>\n consumers === undefined\n ? { sql: '', params: [] }\n : consumers.length === 0\n ? { sql: 'and 0', params: [] }\n : {\n sql: `and cursor.consumer in (${placeholders(consumers.length)})`,\n params: consumers.map((consumer) => consumer.name),\n }\n\n const hasPending = (\n sessionId: string,\n clearIndex: number,\n consumers: readonly StoreConsumer[] | undefined,\n ): boolean => {\n const served = servedClause(consumers)\n return (\n db\n .prepare(\n `select 1 from a2_cursors cursor\n where cursor.session_id = ? and cursor.head > ?\n and cursor.failed_at is null ${served.sql}\n limit 1`,\n )\n .get(sessionId, clearIndex, ...served.params) !== undefined\n )\n }\n\n /**\n * The session's rows owning `ids`. Rows below its clear marker do not\n * own their ids: they are deleted here so the ids can be reused.\n */\n const liveOwnersOf = (\n sessionId: string,\n clearIndex: number,\n ids: readonly string[],\n ): EventRow[] => {\n if (ids.length === 0) return []\n const rows = selectByIds(ids.length).all(\n sessionId,\n ...ids,\n ) as unknown as EventRow[]\n return rows.filter((row) => {\n if (Number(row.idx) >= clearIndex) return true\n deleteEventById.run(sessionId, row.event_id)\n return false\n })\n }\n\n /** The attempt-currency fence shared by consumer appends and clears. */\n const assertCurrentCause = ({\n sessionId,\n cause,\n clearIndex,\n }: {\n sessionId: string\n cause: EventCause\n clearIndex: number\n }): void => {\n if (cause.index <= clearIndex)\n throw supersededAttemptError(sessionId, cause)\n const cursor = cursorOf(sessionId, cause)\n if (\n !cursor ||\n toNumber(cursor.head) !== cause.index ||\n Number(cursor.attempt_count) !== cause.attempt ||\n cursor.failed_at !== null\n ) {\n throw supersededAttemptError(sessionId, cause)\n }\n }\n\n const tx = <T>(fn: () => T): T => {\n db.exec('begin immediate')\n try {\n const result = fn()\n db.exec('commit')\n return result\n } catch (err) {\n try {\n db.exec('rollback')\n } catch {\n // Rollback can fail if the transaction never started; the\n // original error is what matters.\n }\n throw err\n }\n }\n\n /** A finished or dead head's snapshot pins, and history no pin keeps. */\n const releasePins = (\n sessionId: string,\n consumer: string,\n cursorKey: string,\n index: number,\n ): void => {\n db.prepare(\n `delete from a2_snapshot_pins\n where session_id = ? and consumer = ? and cursor_key = ? and event_index = ?`,\n ).run(sessionId, consumer, cursorKey, index)\n db.prepare(\n `delete from a2_snapshot_history\n where session_id = ?\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = a2_snapshot_history.session_id\n and pin.reducer_name = a2_snapshot_history.reducer_name\n and pin.up_to_index = a2_snapshot_history.up_to_index\n )`,\n ).run(sessionId)\n }\n\n /** Moves a cursor past its head; the next head starts fresh. */\n const advance = (sessionId: string, cursor: CursorRow): void => {\n const done = Number(cursor.head)\n releasePins(sessionId, cursor.consumer, cursor.cursor_key, done)\n let next: number | null\n if (cursor.kind === 'event') {\n next = null\n } else if (cursor.kind === 'lane') {\n next = toNumber(\n (\n nextLaneHead.get(\n sessionId,\n cursor.consumer,\n cursor.lane,\n Math.max(done, clearIndexOf(sessionId)),\n ) as { next: number | bigint | null }\n ).next,\n )\n } else {\n const { types } = selectCursor.get(\n sessionId,\n cursor.consumer,\n cursor.cursor_key,\n ) as CursorRow\n next = nextLogHead(sessionId, done, types)\n }\n moveCursor.run(done, next, sessionId, cursor.consumer, cursor.cursor_key)\n }\n\n /**\n * Brings a log consumer's cursor up to date and records its types. A\n * missing cursor opens at the clear marker with `backfill`, otherwise at\n * `base`; an idle one takes the next event of its types after its\n * position; one with a head keeps it, claim and all. Returns its key.\n */\n const wakeLogAt = ({\n sessionId,\n consumer,\n log,\n base,\n clearIndex,\n }: {\n sessionId: string\n consumer: string\n log: NonNullable<StoreConsumer['log']>\n base: number\n clearIndex: number\n }): string => {\n const types = log.types === null ? null : JSON.stringify(log.types)\n const key = cursorKeyOf(log.lane, 0)\n const existing = selectCursor.get(sessionId, consumer, key) as\n CursorRow | undefined\n if (existing) {\n wakeLogCursor.run(\n types,\n existing.head === null\n ? // A deployment that doesn't serve this consumer may have\n // appended events it takes since it went idle.\n nextLogHead(sessionId, Number(existing.position), types)\n : Number(existing.head),\n sessionId,\n consumer,\n key,\n )\n return key\n }\n const position = log.backfill ? clearIndex : base\n openLogCursor.run(\n sessionId,\n consumer,\n key,\n log.lane,\n position,\n nextLogHead(sessionId, position, types),\n types,\n )\n return key\n }\n\n /** Starts the cursor work an inserted batch carries. */\n const enqueue = ({\n sessionId,\n events,\n base,\n clearIndex,\n consumers,\n }: {\n sessionId: string\n events: readonly AppendEvent[]\n base: number\n clearIndex: number\n consumers: readonly StoreConsumer[] | undefined\n }): void => {\n events.forEach((event, offset) => {\n const index = base + 1 + offset\n for (const target of event.targets ?? []) {\n if (target.lane === null) {\n insertEventCursor.run(\n sessionId,\n target.consumer,\n cursorKeyOf(null, index),\n index - 1,\n index,\n )\n continue\n }\n insertLaneEvent.run(sessionId, target.consumer, target.lane, index)\n queueLaneHead.run(\n sessionId,\n target.consumer,\n cursorKeyOf(target.lane, index),\n target.lane,\n clearIndex,\n index,\n )\n }\n })\n // The batch is inserted already, so a cursor it wakes finds the batch's\n // first event of its types, or an earlier one.\n for (const consumer of consumers ?? []) {\n const log = consumer.log\n if (!log) continue\n const carries = events.some(\n (event) =>\n event.type !== CLEAR_EVENT_TYPE &&\n (log.types === null || log.types.includes(event.type)),\n )\n if (!carries) continue\n wakeLogAt({ sessionId, consumer: consumer.name, log, base, clearIndex })\n }\n }\n\n const refreshLogTypes = (\n sessionId: string,\n consumers: readonly StoreConsumer[] | undefined,\n ): void => {\n for (const consumer of consumers ?? []) {\n if (!consumer.log) continue\n const types =\n consumer.log.types === null ? null : JSON.stringify(consumer.log.types)\n refreshTypes.run(types, sessionId, consumer.name, types)\n }\n }\n\n const insertBatch = ({\n sessionId,\n events,\n clearIndex,\n consumers,\n cause,\n }: {\n sessionId: string\n events: readonly AppendEvent[]\n clearIndex: number\n consumers: readonly StoreConsumer[] | undefined\n cause?: EventCause\n }): StoredEvent[] => {\n const base = headOf(sessionId)\n const now = clock.now().getTime()\n const inserted = events.map((event, offset): StoredEvent => {\n const id = event.id ?? generateId()\n const index = base + 1 + offset\n const eventCause = cause ?? event.cause ?? null\n insertEvent.run(\n sessionId,\n index,\n event.type,\n JSON.stringify(event.payload) ?? 'null',\n id,\n now,\n eventCause ? JSON.stringify(eventCause) : null,\n )\n return {\n id,\n type: event.type,\n payload: structuredClone(event.payload),\n index,\n sessionId,\n createdAt: new Date(now),\n cause: eventCause ? { ...eventCause } : null,\n }\n })\n enqueue({ sessionId, events, base, clearIndex, consumers })\n return inserted\n }\n\n return {\n async append(sessionId, events, appendOptions) {\n const consumers = appendOptions?.consumers\n if (events.length === 0) {\n return wrap(() => ({\n events: [],\n hasPending: hasPending(sessionId, clearIndexOf(sessionId), consumers),\n }))\n }\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\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 existing = liveOwnersOf(sessionId, clearIndex, suppliedIds)\n if (existing.length > 0) {\n if (existing.length === events.length) {\n return {\n events: idempotentReplay(events, existing.map(toStored)),\n hasPending: hasPending(sessionId, clearIndex, consumers),\n }\n }\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n `batch mixes ${existing.length} already-appended and ${events.length - existing.length} fresh events`,\n )\n }\n }\n\n const originIndex = appendOptions?.originIndex\n if (originIndex !== undefined && originIndex <= clearIndex) {\n throw sessionClearedError(\n `scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,\n )\n }\n // Attempt-currency fence: a fresh consumer append commits only\n // while its causal attempt still owns the cursor's head\n // (store.ts `append`). `begin immediate` serializes this read\n // against a concurrent claim.\n for (const e of events) {\n if (e.cause)\n assertCurrentCause({ sessionId, cause: e.cause, clearIndex })\n }\n\n const inserted = insertBatch({\n sessionId,\n events,\n clearIndex,\n consumers,\n })\n return {\n events: inserted,\n hasPending: hasPending(sessionId, clearIndex, consumers),\n }\n }),\n )\n },\n\n async wakeLog({ sessionId, consumer }) {\n return wrap(() => {\n // Nothing after the latest clear: a missing cursor stays missing,\n // and nothing takes the write lock.\n const { lane } = consumer.log\n const marker = clearIndexOf(sessionId)\n if (\n headOf(sessionId) === marker &&\n !selectCursor.get(sessionId, consumer.name, cursorKeyOf(lane, 0))\n ) {\n return {\n cursor: idleLogCursor(consumer.name, lane, marker),\n head: marker,\n }\n }\n return tx(() => {\n const head = headOf(sessionId)\n const key = wakeLogAt({\n sessionId,\n consumer: consumer.name,\n log: consumer.log,\n base: head,\n clearIndex: clearIndexOf(sessionId),\n })\n const cursor = selectCursor.get(sessionId, consumer.name, key)\n return { cursor: toCursor(cursor as CursorRow), head }\n })\n })\n },\n\n async cursors(sessionId) {\n return wrap(() => {\n const clearIndex = clearIndexOf(sessionId)\n const rows = db\n .prepare(\n `select * from a2_cursors\n where session_id = ?\n and (\n kind = 'log'\n or head > ?\n or (kind = 'lane' and head is null and position > ?)\n )\n order by consumer, lane is null, lane, head`,\n )\n .all(sessionId, clearIndex, clearIndex) as unknown as CursorRow[]\n return rows.map(toCursor)\n })\n },\n\n async read(sessionId, opts) {\n return wrap(() => {\n // Reads start at the latest clear marker.\n const conditions = [\n 'session_id = ?',\n 'idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)',\n ]\n const params: (string | number)[] = [sessionId, sessionId]\n if (opts?.afterIndex !== undefined) {\n conditions.push('idx > ?')\n params.push(opts.afterIndex)\n }\n if (opts?.throughIndex !== undefined) {\n conditions.push('idx <= ?')\n params.push(opts.throughIndex)\n }\n const rows = db\n .prepare(\n `select * from a2_events where ${conditions.join(' and ')} order by idx`,\n )\n .all(...params) as unknown as EventRow[]\n return rows.map(toStored)\n })\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n exclude = [],\n consumers,\n maxFailures,\n through,\n }) {\n return wrap(() =>\n tx(() => {\n refreshLogTypes(sessionId, consumers)\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const clearIndex = clearIndexOf(sessionId)\n const served = servedClause(consumers)\n const excludedKeys = exclude.map(\n (ref) => `${ref.consumer}\\u0000${cursorKeyOf(ref.lane, ref.index)}`,\n )\n const exclusion =\n excludedKeys.length === 0\n ? ''\n : `and cursor.consumer || char(0) || cursor.cursor_key\n not in (${placeholders(excludedKeys.length)})`\n const bounded = through === undefined ? '' : 'and cursor.head <= ?'\n const filter = `\n where cursor.session_id = ?\n and cursor.head > ?\n and cursor.failed_at is null\n ${bounded}\n ${served.sql}\n ${exclusion}`\n const params = [\n sessionId,\n clearIndex,\n ...(through === undefined ? [] : [through]),\n ...served.params,\n ...excludedKeys,\n now,\n ]\n const eligible = db\n .prepare(\n `select cursor.consumer, cursor.cursor_key,\n cursor.claim_holder, cursor.failure_count\n from a2_cursors cursor\n ${filter}\n and (cursor.claim_expires_at is null\n or cursor.claim_expires_at <= ?)\n order by cursor.head, cursor.consumer`,\n )\n .all(...params) as unknown as Array<\n Pick<\n CursorRow,\n 'consumer' | 'cursor_key' | 'claim_holder' | 'failure_count'\n >\n >\n if (eligible.length > 0) {\n const claimHead = db.prepare(\n `update a2_cursors set\n attempt_count = attempt_count + 1,\n failure_count = failure_count\n + case when claim_holder is null then 0 else 1 end,\n first_claimed_at = coalesce(first_claimed_at, ?),\n last_claimed_at = ?,\n claim_holder = ?,\n claim_expires_at = ?\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n // Advancing the attempt fences the lapsed one; nothing runs.\n const deadLetterHead = db.prepare(\n `update a2_cursors set\n attempt_count = attempt_count + 1,\n failure_count = failure_count + 1,\n failed_at = ?,\n claim_holder = null,\n claim_expires_at = null\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n const selectHeadRow = db.prepare(\n `${HEAD_SELECT}\n where cursor.session_id = ? and cursor.consumer = ?\n and cursor.cursor_key = ?`,\n )\n const events = eligible.map((row) => {\n // A lapsed claim's takeover that spends the last failed attempt.\n const exhausted =\n row.claim_holder !== null &&\n maxFailures !== undefined &&\n Number(row.failure_count) + 1 >= maxFailures\n if (exhausted) {\n deadLetterHead.run(now, sessionId, row.consumer, row.cursor_key)\n } else {\n claimHead.run(\n now,\n now,\n holder,\n expiresAt,\n sessionId,\n row.consumer,\n row.cursor_key,\n )\n }\n const claimed = toHead(\n selectHeadRow.get(\n sessionId,\n row.consumer,\n row.cursor_key,\n ) as HeadRow,\n )\n if (exhausted) {\n releasePins(\n sessionId,\n row.consumer,\n row.cursor_key,\n claimed.index,\n )\n }\n if (clearIndex > 0) claimed.clearIndex = clearIndex\n return claimed\n })\n return { outcome: 'claimed', events, head: headOf(sessionId) }\n }\n\n const active = db\n .prepare(\n `select min(cursor.claim_expires_at) as retry_at\n from a2_cursors cursor\n ${filter}\n and cursor.claim_expires_at > ?`,\n )\n .get(...params) as { retry_at: number | bigint | null }\n return active.retry_at === null\n ? { outcome: 'settled' }\n : { outcome: 'busy', retryAt: new Date(Number(active.retry_at)) }\n }),\n )\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n if (claims.length === 0) return { renewed: [], superseded: [] }\n return wrap(() =>\n tx(() => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const renewed: HeadRef[] = []\n const superseded: HeadRef[] = []\n const extend = db.prepare(\n `update a2_cursors set claim_expires_at = ?\n where session_id = ? and consumer = ? and cursor_key = ?\n and head = ?\n and attempt_count = ?\n and failed_at is null\n and claim_holder = ?\n and claim_expires_at > ?`,\n )\n const clearIndex = clearIndexOf(sessionId)\n const ordered = claims.toSorted(\n (a, b) => a.index - b.index || compareText(a.consumer, b.consumer),\n )\n for (const { attempt, ...ref } of ordered) {\n if (ref.index <= clearIndex) {\n superseded.push(ref)\n continue\n }\n const cursor = cursorOf(sessionId, ref)\n if (!cursor) continue\n const head = toNumber(cursor.head)\n // An event the cursor has not reached yet holds no claim to renew.\n if (head !== ref.index && Number(cursor.position) < ref.index)\n continue\n if (head !== ref.index || Number(cursor.attempt_count) > attempt) {\n superseded.push(ref)\n continue\n }\n const updated = extend.run(\n expiresAt,\n sessionId,\n ref.consumer,\n cursor.cursor_key,\n ref.index,\n attempt,\n holder,\n now,\n )\n if (Number(updated.changes) === 1) renewed.push(ref)\n }\n return { renewed, superseded }\n }),\n )\n },\n\n async completeAttempt({\n sessionId,\n consumer,\n lane,\n index,\n attempt,\n events,\n consumers,\n }) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n if (index <= clearIndex) return { outcome: 'superseded' }\n const cursor = cursorOf(sessionId, { consumer, lane, index })\n if (!cursor) {\n if (!selectEvent.get(sessionId, index)) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n return { outcome: 'superseded' }\n }\n refreshLogTypes(sessionId, consumers)\n 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\n if (toNumber(cursor.head) !== index) {\n let completion:\n { attempt: number | null; ids: string | null } | undefined\n if (cursor.kind === 'lane') {\n const row = selectLaneEvent.get(\n sessionId,\n consumer,\n lane,\n index,\n ) as\n | {\n completed_attempt: number | bigint | null\n returned_event_ids: string | null\n }\n | undefined\n completion = row && {\n attempt: toNumber(row.completed_attempt),\n ids: row.returned_event_ids,\n }\n } else if (toNumber(cursor.completed_idx) === index) {\n completion = {\n attempt: toNumber(cursor.completed_attempt),\n ids: cursor.returned_event_ids,\n }\n }\n if (!completion || completion.attempt !== attempt) {\n return { outcome: 'superseded' }\n }\n const returnedIds =\n completion.ids === null\n ? null\n : decodeReturnedEventIds(completion.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 const existing =\n ids.length === 0\n ? []\n : (selectByIds(ids.length).all(\n sessionId,\n ...ids,\n ) as unknown as EventRow[])\n const byId = new Map(existing.map((row) => [row.event_id, row]))\n const ordered = ids.map((id) => byId.get(id))\n if (\n ordered.some((row) => {\n const cause = row ? decodeCause(row.cause) : null\n return (\n !row ||\n cause?.consumer !== consumer ||\n cause.lane !== lane ||\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:\n events.length === 0\n ? []\n : idempotentReplay(\n events,\n (ordered as EventRow[]).map(toStored),\n ),\n }\n }\n if (\n Number(cursor.attempt_count) !== attempt ||\n cursor.claim_holder === null ||\n cursor.failed_at !== null\n ) {\n return { outcome: 'superseded' }\n }\n\n if (liveOwnersOf(sessionId, clearIndex, ids).length > 0) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains an already-appended event id',\n )\n }\n\n const inserted = insertBatch({\n sessionId,\n events,\n clearIndex,\n consumers,\n cause: { consumer, lane, index, attempt },\n })\n const returned = JSON.stringify(ids)\n if (cursor.kind === 'lane') {\n finishLaneEvent.run(\n attempt,\n returned,\n sessionId,\n consumer,\n lane,\n index,\n )\n } else {\n recordCompletion.run(\n index,\n attempt,\n returned,\n sessionId,\n consumer,\n cursor.cursor_key,\n )\n }\n advance(sessionId, cursor)\n return { outcome: 'completed', events: inserted }\n }),\n )\n },\n\n async failAttempt({\n sessionId,\n consumer,\n lane,\n index,\n attempt,\n error,\n maxFailures,\n retryAfterMs,\n }) {\n return wrap(() =>\n tx(() => {\n if (index <= clearIndexOf(sessionId)) {\n return { outcome: 'superseded', failureCount: 0 }\n }\n const current = cursorOf(sessionId, { consumer, lane, index })\n if (!current) {\n if (!selectEvent.get(sessionId, index)) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n return { outcome: 'superseded', failureCount: 0 }\n }\n if (toNumber(current.head) !== index) {\n return { outcome: 'superseded', failureCount: 0 }\n }\n const failureCount = Number(current.failure_count)\n if (Number(current.attempt_count) !== attempt) {\n return { outcome: 'superseded', failureCount }\n }\n if (\n current.claim_holder === null &&\n current.last_failed_attempt !== null &&\n Number(current.last_failed_attempt) === attempt\n ) {\n return {\n outcome: current.failed_at === null ? 'failed' : 'dead_lettered',\n failureCount,\n }\n }\n if (current.failed_at !== null) {\n return { outcome: 'dead_lettered', failureCount }\n }\n if (current.claim_holder === null) {\n return { outcome: 'superseded', failureCount }\n }\n\n const now = clock.now().getTime()\n const nextFailureCount = failureCount + 1\n const deadLettered = nextFailureCount >= maxFailures\n db.prepare(\n `update a2_cursors set\n failure_count = ?,\n last_error = ?,\n last_failed_at = ?,\n last_failed_attempt = ?,\n failed_at = ?,\n claim_holder = null,\n claim_expires_at = ?\n where session_id = ? and consumer = ? and cursor_key = ?`,\n ).run(\n nextFailureCount,\n error,\n now,\n attempt,\n deadLettered ? now : null,\n deadLettered || retryAfterMs === undefined\n ? null\n : now + retryAfterMs,\n sessionId,\n consumer,\n current.cursor_key,\n )\n if (deadLettered) {\n releasePins(sessionId, consumer, current.cursor_key, index)\n }\n return {\n outcome: deadLettered ? 'dead_lettered' : 'failed',\n failureCount: nextFailureCount,\n }\n }),\n )\n },\n\n async deadLetters(sessionId) {\n return wrap(() => {\n const rows = db\n .prepare(\n `${HEAD_SELECT}\n where cursor.session_id = ?\n and cursor.failed_at is not null\n and cursor.head > coalesce(\n (select clear_index from a2_clears where session_id = ?), 0\n )\n order by cursor.head, cursor.consumer`,\n )\n .all(sessionId, sessionId) as unknown as HeadRow[]\n return rows.map(toHead)\n })\n },\n\n async redrive({ sessionId, consumer, index, action, consumers }) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n const cursor =\n index > clearIndex\n ? (db\n .prepare(\n `select * from a2_cursors\n where session_id = ? and consumer = ? and head = ?\n and failed_at is not null`,\n )\n .get(sessionId, consumer, index) as CursorRow | undefined)\n : undefined\n if (cursor) {\n refreshLogTypes(sessionId, consumers)\n if (action === 'skip') {\n if (cursor.kind === 'lane') {\n finishLaneEvent.run(\n null,\n null,\n sessionId,\n consumer,\n cursor.lane,\n index,\n )\n }\n advance(sessionId, cursor)\n } else {\n db.prepare(\n `update a2_cursors set failed_at = null, failure_count = 0\n where session_id = ? and consumer = ? and cursor_key = ?`,\n ).run(sessionId, consumer, cursor.cursor_key)\n }\n }\n return {\n redriven: cursor !== undefined,\n hasPending: hasPending(sessionId, clearIndex, consumers),\n }\n }),\n )\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return wrap(() => {\n const throughIndex = stateOptions?.throughIndex ?? null\n const snapshotThroughIndex =\n stateOptions?.snapshotThroughIndex ?? throughIndex\n const rows = db\n .prepare(\n `with boundary as materialized (\n select coalesce(\n (select clear_index from a2_clears where session_id = ?), 0\n ) as clear_index\n ), head as materialized (\n select up_to_index, state, updated_at\n from a2_snapshots\n where session_id = ? and reducer_name = ?\n and up_to_index >= (select clear_index from boundary)\n ), snapshot as materialized (\n select up_to_index, state from (\n select up_to_index, state from head\n where (? is null or up_to_index <= ?)\n union all\n select up_to_index, state\n from a2_snapshot_history\n where session_id = ? and reducer_name = ?\n and up_to_index >= (select clear_index from boundary)\n and (? is null or up_to_index <= ?)\n ) order by up_to_index desc limit 1\n )\n select 0 as row_order, 'snapshot' as row_kind,\n (select clear_index from boundary) as clear_index,\n (select up_to_index from head) as head_index,\n (select up_to_index from snapshot) as snapshot_index,\n (select state from snapshot) as snapshot_state,\n null as session_id, null as idx, null as event_type,\n null as payload, null as event_id, null as created_at,\n null as cause\n union all\n select 1 as row_order, 'event' as row_kind,\n null as clear_index,\n (select up_to_index from head) as head_index,\n null as snapshot_index, null as snapshot_state,\n event.session_id, event.idx, event.event_type,\n event.payload, event.event_id, event.created_at,\n event.cause\n from a2_events as event\n where event.session_id = ?\n and event.idx > max(\n coalesce((select up_to_index from snapshot), 0),\n (select clear_index from boundary) - 1\n )\n and (? is null or event.idx <= ?)\n order by row_order, idx`,\n )\n .all(\n sessionId,\n sessionId,\n reducerName,\n snapshotThroughIndex,\n snapshotThroughIndex,\n sessionId,\n reducerName,\n snapshotThroughIndex,\n snapshotThroughIndex,\n sessionId,\n throughIndex,\n throughIndex,\n ) as unknown as StateReadRow[]\n const snapshot = rows.find((row) => row.row_kind === 'snapshot')\n const clearIndex = Number(snapshot?.clear_index ?? 0)\n return {\n ...(clearIndex > 0 ? { clearIndex } : {}),\n headIndex:\n snapshot?.head_index === null || snapshot?.head_index === undefined\n ? null\n : Number(snapshot.head_index),\n snapshot:\n snapshot?.snapshot_index !== null &&\n snapshot?.snapshot_index !== undefined\n ? {\n index: Number(snapshot.snapshot_index),\n state: JSON.parse(snapshot.snapshot_state!),\n }\n : null,\n events: rows\n .filter(\n (row): row is StateReadRow & EventRow => row.row_kind === 'event',\n )\n .map(toEvent),\n }\n })\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n await wrap(() =>\n tx(() => {\n const selectSnapshotHead = db.prepare(\n `select up_to_index, state, updated_at from a2_snapshots\n where session_id = ? and reducer_name = ?`,\n )\n const unfinished = db.prepare(\n `select 1 from a2_cursors\n where session_id = ? and consumer = ? and cursor_key = ?\n and head = ? and head > ? and failed_at is null`,\n )\n const insertPin = db.prepare(\n `insert or ignore into a2_snapshot_pins\n (session_id, consumer, cursor_key, event_index, reducer_name, up_to_index)\n values (?, ?, ?, ?, ?, ?)`,\n )\n const hasPin = db.prepare(\n `select 1 from a2_snapshot_pins\n where session_id = ? and reducer_name = ? and up_to_index = ?\n limit 1`,\n )\n const putHistory = db.prepare(\n `insert into a2_snapshot_history\n (session_id, reducer_name, up_to_index, state, updated_at)\n values (?, ?, ?, ?, ?)\n on conflict (session_id, reducer_name, up_to_index) do update set\n state = excluded.state, updated_at = excluded.updated_at`,\n )\n const putHead = db.prepare(\n `insert into a2_snapshots\n (session_id, reducer_name, up_to_index, state, updated_at)\n values (?, ?, ?, ?, ?)\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 const deleteHistory = db.prepare(\n `delete from a2_snapshot_history\n where session_id = ? and reducer_name = ? and up_to_index = ?`,\n )\n const clearIndex = clearIndexOf(sessionId)\n for (const write of writes.toSorted((a, b) => a.index - b.index)) {\n // A fold from before the latest clear describes a lifetime that is gone.\n if (write.index < clearIndex) continue\n for (const pin of write.pins ?? []) {\n const cursorKey = cursorKeyOf(pin.lane, pin.index)\n if (\n unfinished.get(\n sessionId,\n pin.consumer,\n cursorKey,\n pin.index,\n clearIndex,\n )\n ) {\n insertPin.run(\n sessionId,\n pin.consumer,\n cursorKey,\n pin.index,\n reducerName,\n write.index,\n )\n }\n }\n const head = selectSnapshotHead.get(sessionId, reducerName) as\n SnapshotHeadRow | undefined\n const now = clock.now().getTime()\n if (!head || write.index >= Number(head.up_to_index)) {\n if (\n head &&\n write.index > Number(head.up_to_index) &&\n Number(head.up_to_index) >= clearIndex &&\n hasPin.get(sessionId, reducerName, Number(head.up_to_index))\n ) {\n putHistory.run(\n sessionId,\n reducerName,\n Number(head.up_to_index),\n head.state,\n Number(head.updated_at),\n )\n }\n putHead.run(\n sessionId,\n reducerName,\n write.index,\n JSON.stringify(write.state) ?? 'null',\n now,\n )\n deleteHistory.run(sessionId, reducerName, write.index)\n } else if (\n write.index < Number(head.up_to_index) &&\n hasPin.get(sessionId, reducerName, write.index)\n ) {\n putHistory.run(\n sessionId,\n reducerName,\n write.index,\n JSON.stringify(write.state) ?? 'null',\n now,\n )\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 wrap(() =>\n tx(() => {\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 nowMs = clock.now().getTime()\n const expiresAtMs = nowMs + meta.ttlMs\n for (const [field, value] of entries) {\n // Field-wise LWW against values and tombstones alike: an\n // unexpired stored row with a strictly newer `at` survives;\n // ties go to the incoming write (memory parity).\n upsertPresence.run(\n ns,\n participant,\n field,\n JSON.stringify(value) ?? 'null',\n meta.seen,\n atMs,\n expiresAtMs,\n nowMs,\n )\n }\n }),\n )\n },\n\n async read(ns) {\n return wrap(() => {\n const now = clock.now().getTime()\n // The sweep is opportunistic cleanup; the select's filter\n // alone keeps expired rows out of this result.\n sweepPresence.run(ns, now)\n const rows = selectPresence.all(ns, now) as unknown as PresenceDbRow[]\n return rows.map((row) => ({\n participant: row.participant,\n field: row.field,\n value: JSON.parse(row.value) as unknown,\n seen: Number(row.seen),\n at: new Date(Number(row.at)),\n expiresAt: new Date(Number(row.expires_at)),\n }))\n })\n },\n },\n\n stream(sessionId, opts) {\n const readAfter = (afterIndex: number): StoredEvent[] =>\n wrap(() => {\n // A cursor below the latest marker resumes at it.\n const rows = db\n .prepare(\n `select * from a2_events\n where session_id = ? and idx > ?\n and idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)\n order by idx`,\n )\n .all(sessionId, afterIndex, sessionId) as unknown as EventRow[]\n return rows.map(toStored)\n })\n return pollingStream(\n readAfter,\n opts?.startAfter !== undefined ? { startAfter: opts.startAfter } : {},\n )\n },\n\n async clear({ sessionId, ifLastIndex, cause }) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n if (cause) assertCurrentCause({ sessionId, cause, clearIndex })\n const head = headOf(sessionId)\n if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {\n return { cleared: true, index: head, clearIndex }\n }\n if (ifLastIndex !== undefined && ifLastIndex !== head) {\n return { cleared: false, index: head, clearIndex }\n }\n if (head === 0 || clearIndex === head) {\n return { cleared: true, index: head, clearIndex }\n }\n const marker = head + 1\n insertEvent.run(\n sessionId,\n marker,\n CLEAR_EVENT_TYPE,\n 'null',\n generateId(),\n clock.now().getTime(),\n null,\n )\n db.prepare(\n `insert into a2_clears (session_id, clear_index) values (?, ?)\n on conflict (session_id) do update set clear_index = excluded.clear_index`,\n ).run(sessionId, marker)\n // One row per log consumer: they count from the marker. Lane and\n // concurrent cursors at or below it are superseded by the marker\n // alone, and reclaim deletes them.\n db.prepare(\n `update a2_cursors\n set position = ?, head = null, ${FRESH_DISPATCH},\n completed_idx = null, completed_attempt = null,\n returned_event_ids = null\n where session_id = ? and kind = 'log'`,\n ).run(marker, sessionId)\n db.prepare(\n 'delete from a2_snapshot_pins where session_id = ? and event_index < ?',\n ).run(sessionId, marker)\n return { cleared: true, index: marker, clearIndex: marker }\n }),\n )\n },\n\n async reclaim(sessionId) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n if (clearIndex === 0) return { deleted: 0, done: true }\n const last = db\n .prepare(\n `select max(idx) as last from (\n select idx from a2_events\n where session_id = ? and idx < ?\n order by idx limit ?\n )`,\n )\n .get(sessionId, clearIndex, RECLAIM_BATCH) as {\n last: number | bigint | null\n }\n let deleted = 0\n if (last.last !== null) {\n const through = Number(last.last)\n deleted = Number(\n db\n .prepare(\n 'delete from a2_events where session_id = ? and idx <= ?',\n )\n .run(sessionId, through).changes,\n )\n db.prepare(\n 'delete from a2_lane_events where session_id = ? and idx <= ?',\n ).run(sessionId, through)\n db.prepare(\n `delete from a2_cursors\n where session_id = ? and kind = 'event' and position < ?`,\n ).run(sessionId, through)\n }\n const done =\n db\n .prepare(\n 'select 1 from a2_events where session_id = ? and idx < ? limit 1',\n )\n .get(sessionId, clearIndex) === undefined\n // Writes serialize with the clear, so none lands below the marker later.\n if (done) {\n db.prepare(\n `delete from a2_cursors\n where session_id = ? and kind != 'log'\n and (head is null or head <= ?) and position <= ?`,\n ).run(sessionId, clearIndex, clearIndex)\n for (const table of [\n 'a2_snapshot_pins',\n 'a2_snapshot_history',\n 'a2_snapshots',\n ]) {\n db.prepare(\n `delete from ${table} where session_id = ? and up_to_index < ?`,\n ).run(sessionId, clearIndex)\n }\n }\n return { deleted, done }\n }),\n )\n },\n\n close() {\n db.close()\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"store-sqlite.js","names":[],"sources":["../src/store-sqlite.ts"],"sourcesContent":["/**\n * experimental-a2/store-sqlite — sqlite store backend (the dev default, `.a2/dev.db`).\n *\n * Built on `node:sqlite` (Node ≥ 22.13) so it ships with zero\n * dependencies. Implements the A2Store interface; the conformance suite\n * in test/conformance is the executable contract.\n *\n * The spec's `index` column is stored as `idx` (a2-implementation.md\n * §11 — `index` collides with a reserved word) and mapped back at the\n * API boundary. Timestamps are epoch milliseconds written from the\n * injected clock — never SQL `now()` — so tests can time-travel against\n * real storage.\n */\n\nimport { mkdirSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { DatabaseSync } from 'node:sqlite'\nimport {\n A2Error,\n duplicateBatchError,\n lastIndexMismatchError,\n supersededAttemptError,\n} from './errors.ts'\nimport { CLEAR_EVENT_TYPE, sessionClearedError } from './session-clear.ts'\nimport { idempotentReplay } from './idempotent-replay.ts'\nimport {\n cursorKeyOf,\n decodeCause,\n decodeReturnedEventIds,\n idleLogCursor,\n} from './store-codec.ts'\nimport { pollingStream } from './store-polling.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type AppendEvent,\n type Clock,\n type Event,\n type EventCause,\n type HeadEvent,\n type HeadRef,\n type IdSource,\n type StoreConsumer,\n type StoredCursor,\n type StoredEvent,\n} from './store.ts'\n\nexport type SqliteStoreOptions = {\n /** Database file path. Defaults to `.a2/dev.db`; `:memory:` works. */\n path?: string\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 SqliteStore = A2Store & {\n /** Close the underlying database handle. */\n close(): void\n}\n\n/** The cursor layout; databases from before it are rejected, not migrated. */\nconst LAYOUT = 'cursors-1'\n\nconst SCHEMA = `\ncreate table if not exists a2_meta (\n key text not null primary key,\n value text not null\n) strict;\n\ncreate table if not exists a2_events (\n session_id text not null,\n idx integer not null,\n event_type text not null,\n payload text not null,\n event_id text not null,\n created_at integer not null,\n cause text,\n primary key (session_id, idx)\n) strict;\n\ncreate unique index if not exists a2_events_session_event_id\n on a2_events (session_id, event_id);\ndrop index if exists a2_events_event_id;\ncreate index if not exists a2_events_types on a2_events (session_id, event_type, idx);\n\ncreate table if not exists a2_cursors (\n session_id text not null,\n consumer text not null,\n cursor_key text not null,\n lane text,\n kind text not null,\n position integer not null,\n head integer,\n types text,\n attempt_count integer not null default 0,\n first_claimed_at integer,\n last_claimed_at integer,\n claim_holder text,\n claim_expires_at integer,\n failure_count integer not null default 0,\n last_failed_at integer,\n last_failed_attempt integer,\n last_error text,\n failed_at integer,\n completed_idx integer,\n completed_attempt integer,\n returned_event_ids text,\n primary key (session_id, consumer, cursor_key)\n) strict;\n\ncreate index if not exists a2_cursors_heads\n on a2_cursors (session_id, head) where head is not null;\n\ncreate table if not exists a2_lane_events (\n session_id text not null,\n consumer text not null,\n lane text not null,\n idx integer not null,\n done integer not null default 0,\n completed_attempt integer,\n returned_event_ids text,\n primary key (session_id, consumer, lane, idx)\n) strict;\n\ncreate table if not exists a2_snapshots (\n session_id text not null,\n reducer_name text not null,\n up_to_index integer not null,\n state text not null,\n updated_at integer not null,\n primary key (session_id, reducer_name)\n) strict;\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 integer not null,\n state text not null,\n updated_at integer not null,\n primary key (session_id, reducer_name, up_to_index)\n) strict;\n\ncreate table if not exists a2_snapshot_pins (\n session_id text not null,\n consumer text not null,\n cursor_key text not null,\n event_index integer not null,\n reducer_name text not null,\n up_to_index integer not null,\n primary key (session_id, consumer, cursor_key, event_index, reducer_name, up_to_index)\n) strict;\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_clears (\n session_id text not null primary key,\n clear_index integer not null\n) strict;\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 text not null,\n seen integer not null,\n at integer not null,\n expires_at integer not null,\n primary key (ns, participant, field)\n) strict;\n`\n\n/**\n * Switching journal modes takes an exclusive lock, and — unlike normal\n * statements — the switch can return SQLITE_BUSY without consulting the\n * busy handler while several connections race it (instances cold-booting\n * against one file). Bounded synchronous retry; the window is boot-only\n * and tiny. Found by the multi-process torture test.\n */\nconst setWalJournalMode = (db: DatabaseSync): void => {\n for (let attempt = 0; ; attempt += 1) {\n try {\n db.exec('pragma journal_mode = wal')\n return\n } catch (err) {\n const busy = (err as { errcode?: number }).errcode === 5\n if (!busy || attempt >= 100) throw err\n // Synchronous 5ms sleep — sqlite() is a sync constructor.\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5)\n }\n }\n}\n\nconst wrap = <T>(fn: () => T): T => {\n try {\n return fn()\n } catch (err) {\n if (err instanceof A2Error || err instanceof TypeError) throw err\n throw new A2Error('STORE_UNAVAILABLE', 'sqlite store operation failed', {\n cause: err,\n })\n }\n}\n\nconst toDate = (ms: number | bigint | null): Date | null =>\n ms === null ? null : new Date(Number(ms))\n\nconst toNumber = (value: number | bigint | null): number | null =>\n value === null ? null : Number(value)\n\ntype EventRow = {\n session_id: string\n idx: number | bigint\n event_type: string\n payload: string\n event_id: string\n created_at: number | bigint\n cause: string | null\n}\n\ntype CursorRow = {\n session_id: string\n consumer: string\n cursor_key: string\n lane: string | null\n kind: 'log' | 'lane' | 'event'\n position: number | bigint\n head: number | bigint | null\n types: string | null\n attempt_count: number | bigint\n first_claimed_at: number | bigint | null\n last_claimed_at: number | bigint | null\n claim_holder: string | null\n claim_expires_at: number | bigint | null\n failure_count: number | bigint\n last_failed_at: number | bigint | null\n last_failed_attempt: number | bigint | null\n last_error: string | null\n failed_at: number | bigint | null\n completed_idx: number | bigint | null\n completed_attempt: number | bigint | null\n returned_event_ids: string | null\n}\n\n/** A cursor joined with its head event's columns, prefixed `e_`. */\ntype HeadRow = CursorRow & {\n e_idx: number | bigint\n e_event_type: string\n e_payload: string\n e_event_id: string\n e_created_at: number | bigint\n e_cause: string | null\n}\n\nconst toStored = (row: EventRow): StoredEvent => ({\n id: row.event_id,\n type: row.event_type,\n payload: JSON.parse(row.payload),\n index: Number(row.idx),\n sessionId: row.session_id,\n createdAt: new Date(Number(row.created_at)),\n cause: decodeCause(row.cause),\n})\n\nconst toEvent = (row: EventRow): Event => ({\n id: row.event_id,\n type: row.event_type,\n payload: JSON.parse(row.payload),\n index: Number(row.idx),\n sessionId: row.session_id,\n createdAt: new Date(Number(row.created_at)),\n})\n\nconst toCursor = (row: CursorRow): StoredCursor => ({\n consumer: row.consumer,\n lane: row.lane,\n position: Number(row.position),\n head: toNumber(row.head),\n firstClaimedAt: toDate(row.first_claimed_at),\n lastClaimedAt: toDate(row.last_claimed_at),\n attemptCount: Number(row.attempt_count),\n claimHolder: row.claim_holder,\n claimExpiresAt: toDate(row.claim_expires_at),\n failureCount: Number(row.failure_count),\n lastFailedAt: toDate(row.last_failed_at),\n lastFailedAttempt: toNumber(row.last_failed_attempt),\n lastError: row.last_error,\n failedAt: toDate(row.failed_at),\n})\n\nconst toHead = (row: HeadRow): HeadEvent => {\n const { position: _position, head: _head, ...dispatch } = toCursor(row)\n return {\n ...toStored({\n session_id: row.session_id,\n idx: row.e_idx,\n event_type: row.e_event_type,\n payload: row.e_payload,\n event_id: row.e_event_id,\n created_at: row.e_created_at,\n cause: row.e_cause,\n }),\n ...dispatch,\n }\n}\n\ntype PresenceDbRow = {\n participant: string\n field: string\n value: string\n seen: number | bigint\n at: number | bigint\n expires_at: number | bigint\n}\n\ntype StateReadRow = Partial<EventRow> & {\n row_kind: 'snapshot' | 'event'\n clear_index: number | bigint | null\n head_index: number | bigint | null\n snapshot_index: number | bigint | null\n snapshot_state: string | null\n}\n\ntype SnapshotHeadRow = {\n up_to_index: number | bigint\n state: string\n updated_at: number | bigint\n}\n\nconst RECLAIM_BATCH = 256\n\n/** The head columns every claim, dead letter, and listing joins in. */\nconst HEAD_SELECT = `\n select cursor.*,\n event.idx as e_idx, event.event_type as e_event_type,\n event.payload as e_payload, event.event_id as e_event_id,\n event.created_at as e_created_at, event.cause as e_cause\n from a2_cursors cursor\n join a2_events event\n on event.session_id = cursor.session_id and event.idx = cursor.head`\n\n/** A fresh head's dispatch columns, for `set` clauses. */\nconst FRESH_DISPATCH = `\n attempt_count = 0, first_claimed_at = null, last_claimed_at = null,\n claim_holder = null, claim_expires_at = null, failure_count = 0,\n last_failed_at = null, last_failed_attempt = null, last_error = null,\n failed_at = null`\n\nconst placeholders = (count: number): string =>\n Array.from({ length: count }, () => '?').join(', ')\n\nconst compareText = (a: string, b: string): number =>\n a < b ? -1 : a > b ? 1 : 0\n\nexport function sqlite(options: SqliteStoreOptions = {}): SqliteStore {\n const path = options.path ?? '.a2/dev.db'\n const clock = options.clock ?? SYSTEM_CLOCK\n const generateId = options.ids ?? RANDOM_IDS\n\n if (path !== ':memory:') {\n // The dev-default backend only ever runs outside production, but\n // bundlers can't know that: without the ignore, Turbopack sees a\n // dynamic fs path and traces the entire project into the output.\n mkdirSync(dirname(resolve(/* turbopackIgnore: true */ path)), {\n recursive: true,\n })\n }\n const db = new DatabaseSync(path)\n // busy_timeout before anything that takes locks: concurrent\n // cold-opens (several instances booting at once) race the WAL switch\n // and schema creation.\n db.exec('pragma busy_timeout = 5000')\n setWalJournalMode(db)\n db.exec('pragma synchronous = normal')\n const legacy = db\n .prepare(\"select 1 from pragma_table_info('a2_events') where name = ?\")\n .get('processed_at')\n if (legacy) {\n db.close()\n throw new A2Error(\n 'STORE_NOT_CONFIGURED',\n `the sqlite database at '${path}' has the A2 layout from before consumers; delete it, or point the store at a fresh database file`,\n )\n }\n db.exec(SCHEMA)\n db.prepare(\n \"insert into a2_meta (key, value) values ('layout', ?) on conflict (key) do nothing\",\n ).run(LAYOUT)\n\n const insertEvent = db.prepare(\n `insert into a2_events\n (session_id, idx, event_type, payload, event_id, created_at, cause)\n values (?, ?, ?, ?, ?, ?, ?)`,\n )\n const selectHead = db.prepare(\n 'select coalesce(max(idx), 0) as max from a2_events where session_id = ?',\n )\n const headOf = (sessionId: string): number =>\n Number((selectHead.get(sessionId) as { max: number | bigint }).max)\n const selectEvent = db.prepare(\n 'select 1 from a2_events where session_id = ? and idx = ?',\n )\n const selectCursor = db.prepare(\n 'select * from a2_cursors where session_id = ? and consumer = ? and cursor_key = ?',\n )\n const cursorOf = (sessionId: string, ref: HeadRef): CursorRow | undefined =>\n selectCursor.get(\n sessionId,\n ref.consumer,\n cursorKeyOf(ref.lane, ref.index),\n ) as CursorRow | undefined\n const insertEventCursor = db.prepare(\n `insert into a2_cursors\n (session_id, consumer, cursor_key, lane, kind, position, head)\n values (?, ?, ?, null, 'event', ?, ?)`,\n )\n const insertLaneEvent = db.prepare(\n `insert into a2_lane_events (session_id, consumer, lane, idx)\n values (?, ?, ?, ?)`,\n )\n // A lane cursor whose head lies at or below the clear marker is idle: the\n // marker superseded everything it had queued.\n const queueLaneHead = db.prepare(\n `insert into a2_cursors\n (session_id, consumer, cursor_key, lane, kind, position, head)\n values (?, ?, ?, ?, 'lane', ?, ?)\n on conflict (session_id, consumer, cursor_key) do update set\n head = excluded.head,\n position = max(a2_cursors.position, excluded.position),\n ${FRESH_DISPATCH}\n where a2_cursors.head is null or a2_cursors.head <= excluded.position`,\n )\n const openLogCursor = db.prepare(\n `insert into a2_cursors\n (session_id, consumer, cursor_key, lane, kind, position, head, types)\n values (?, ?, ?, ?, 'log', ?, ?, ?)`,\n )\n const wakeLogCursor = db.prepare(\n `update a2_cursors set types = ?, head = ?\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n const refreshTypes = db.prepare(\n `update a2_cursors set types = ?\n where session_id = ? and consumer = ? and kind = 'log'\n and types is not ?`,\n )\n const nextLogHeadAll = db.prepare(\n `select min(idx) as next from a2_events\n where session_id = ? and idx > ? and event_type != ?`,\n )\n const nextLogHeadOf = db.prepare(\n `select min(idx) as next from a2_events\n where session_id = ? and idx > ? and event_type != ?\n and event_type in (select value from json_each(?))`,\n )\n const nextLogHead = (\n sessionId: string,\n after: number,\n types: string | null,\n ): number | null =>\n toNumber(\n (\n (types === null\n ? nextLogHeadAll.get(sessionId, after, CLEAR_EVENT_TYPE)\n : nextLogHeadOf.get(sessionId, after, CLEAR_EVENT_TYPE, types)) as {\n next: number | bigint | null\n }\n ).next,\n )\n const nextLaneHead = db.prepare(\n `select min(idx) as next from a2_lane_events\n where session_id = ? and consumer = ? and lane = ? and done = 0\n and idx > ?`,\n )\n const selectLaneEvent = db.prepare(\n `select completed_attempt, returned_event_ids from a2_lane_events\n where session_id = ? and consumer = ? and lane = ? and idx = ?`,\n )\n const finishLaneEvent = db.prepare(\n `update a2_lane_events\n set done = 1, completed_attempt = ?, returned_event_ids = ?\n where session_id = ? and consumer = ? and lane = ? and idx = ?`,\n )\n const moveCursor = db.prepare(\n `update a2_cursors set position = ?, head = ?, ${FRESH_DISPATCH}\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n const recordCompletion = db.prepare(\n `update a2_cursors\n set completed_idx = ?, completed_attempt = ?, returned_event_ids = ?\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n // A JSON null value is a tombstone: a delete's `at`, kept until it\n // expires so an older set arriving after it still loses.\n const upsertPresence = db.prepare(\n `insert into a2_presence (ns, participant, field, value, seen, at, expires_at)\n values (?, ?, ?, ?, ?, ?, ?)\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 or a2_presence.expires_at <= ?`,\n )\n const sweepPresence = db.prepare(\n 'delete from a2_presence where ns = ? and expires_at <= ?',\n )\n const selectPresence = db.prepare(\n `select participant, field, value, seen, at, expires_at\n from a2_presence\n where ns = ? and expires_at > ? and value != 'null'\n order by participant, field`,\n )\n const selectByIds = (count: number) =>\n db.prepare(\n `select * from a2_events\n where session_id = ? and event_id in (${placeholders(count)})\n order by idx`,\n )\n\n const selectClearIndex = db.prepare(\n 'select clear_index from a2_clears where session_id = ?',\n )\n const clearIndexOf = (sessionId: string): number =>\n Number(\n (\n selectClearIndex.get(sessionId) as\n { clear_index: number | bigint } | undefined\n )?.clear_index ?? 0,\n )\n const deleteEventById = db.prepare(\n 'delete from a2_events where session_id = ? and event_id = ?',\n )\n\n /** Served consumers as a SQL condition on `cursor.consumer`. */\n const servedClause = (\n consumers: readonly StoreConsumer[] | undefined,\n ): { sql: string; params: string[] } =>\n consumers === undefined\n ? { sql: '', params: [] }\n : consumers.length === 0\n ? { sql: 'and 0', params: [] }\n : {\n sql: `and cursor.consumer in (${placeholders(consumers.length)})`,\n params: consumers.map((consumer) => consumer.name),\n }\n\n const hasPending = (\n sessionId: string,\n clearIndex: number,\n consumers: readonly StoreConsumer[] | undefined,\n ): boolean => {\n const served = servedClause(consumers)\n return (\n db\n .prepare(\n `select 1 from a2_cursors cursor\n where cursor.session_id = ? and cursor.head > ?\n and cursor.failed_at is null ${served.sql}\n limit 1`,\n )\n .get(sessionId, clearIndex, ...served.params) !== undefined\n )\n }\n\n /**\n * The session's rows owning `ids`. Rows below its clear marker do not\n * own their ids: they are deleted here so the ids can be reused.\n */\n const liveOwnersOf = (\n sessionId: string,\n clearIndex: number,\n ids: readonly string[],\n ): EventRow[] => {\n if (ids.length === 0) return []\n const rows = selectByIds(ids.length).all(\n sessionId,\n ...ids,\n ) as unknown as EventRow[]\n return rows.filter((row) => {\n if (Number(row.idx) >= clearIndex) return true\n deleteEventById.run(sessionId, row.event_id)\n return false\n })\n }\n\n /** The attempt-currency fence shared by consumer appends and clears. */\n const assertCurrentCause = ({\n sessionId,\n cause,\n clearIndex,\n }: {\n sessionId: string\n cause: EventCause\n clearIndex: number\n }): void => {\n if (cause.index <= clearIndex)\n throw supersededAttemptError(sessionId, cause)\n const cursor = cursorOf(sessionId, cause)\n if (\n !cursor ||\n toNumber(cursor.head) !== cause.index ||\n Number(cursor.attempt_count) !== cause.attempt ||\n cursor.failed_at !== null\n ) {\n throw supersededAttemptError(sessionId, cause)\n }\n }\n\n const tx = <T>(fn: () => T): T => {\n db.exec('begin immediate')\n try {\n const result = fn()\n db.exec('commit')\n return result\n } catch (err) {\n try {\n db.exec('rollback')\n } catch {\n // Rollback can fail if the transaction never started; the\n // original error is what matters.\n }\n throw err\n }\n }\n\n /** A finished or dead head's snapshot pins, and history no pin keeps. */\n const releasePins = (\n sessionId: string,\n consumer: string,\n cursorKey: string,\n index: number,\n ): void => {\n db.prepare(\n `delete from a2_snapshot_pins\n where session_id = ? and consumer = ? and cursor_key = ? and event_index = ?`,\n ).run(sessionId, consumer, cursorKey, index)\n db.prepare(\n `delete from a2_snapshot_history\n where session_id = ?\n and not exists (\n select 1 from a2_snapshot_pins pin\n where pin.session_id = a2_snapshot_history.session_id\n and pin.reducer_name = a2_snapshot_history.reducer_name\n and pin.up_to_index = a2_snapshot_history.up_to_index\n )`,\n ).run(sessionId)\n }\n\n /** Moves a cursor past its head; the next head starts fresh. */\n const advance = (sessionId: string, cursor: CursorRow): void => {\n const done = Number(cursor.head)\n releasePins(sessionId, cursor.consumer, cursor.cursor_key, done)\n let next: number | null\n if (cursor.kind === 'event') {\n next = null\n } else if (cursor.kind === 'lane') {\n next = toNumber(\n (\n nextLaneHead.get(\n sessionId,\n cursor.consumer,\n cursor.lane,\n Math.max(done, clearIndexOf(sessionId)),\n ) as { next: number | bigint | null }\n ).next,\n )\n } else {\n const { types } = selectCursor.get(\n sessionId,\n cursor.consumer,\n cursor.cursor_key,\n ) as CursorRow\n next = nextLogHead(sessionId, done, types)\n }\n moveCursor.run(done, next, sessionId, cursor.consumer, cursor.cursor_key)\n }\n\n /**\n * Brings a log consumer's cursor up to date and records its types. A\n * missing cursor opens at the clear marker with `backfill`, otherwise at\n * `base`; an idle one takes the next event of its types after its\n * position; one with a head keeps it, claim and all. Returns its key.\n */\n const wakeLogAt = ({\n sessionId,\n consumer,\n log,\n base,\n clearIndex,\n }: {\n sessionId: string\n consumer: string\n log: NonNullable<StoreConsumer['log']>\n base: number\n clearIndex: number\n }): string => {\n const types = log.types === null ? null : JSON.stringify(log.types)\n const key = cursorKeyOf(log.lane, 0)\n const existing = selectCursor.get(sessionId, consumer, key) as\n CursorRow | undefined\n if (existing) {\n wakeLogCursor.run(\n types,\n existing.head === null\n ? // A deployment that doesn't serve this consumer may have\n // appended events it takes since it went idle.\n nextLogHead(sessionId, Number(existing.position), types)\n : Number(existing.head),\n sessionId,\n consumer,\n key,\n )\n return key\n }\n const position = log.backfill ? clearIndex : base\n openLogCursor.run(\n sessionId,\n consumer,\n key,\n log.lane,\n position,\n nextLogHead(sessionId, position, types),\n types,\n )\n return key\n }\n\n /** Starts the cursor work an inserted batch carries. */\n const enqueue = ({\n sessionId,\n events,\n base,\n clearIndex,\n consumers,\n }: {\n sessionId: string\n events: readonly AppendEvent[]\n base: number\n clearIndex: number\n consumers: readonly StoreConsumer[] | undefined\n }): void => {\n events.forEach((event, offset) => {\n const index = base + 1 + offset\n for (const target of event.targets ?? []) {\n if (target.lane === null) {\n insertEventCursor.run(\n sessionId,\n target.consumer,\n cursorKeyOf(null, index),\n index - 1,\n index,\n )\n continue\n }\n insertLaneEvent.run(sessionId, target.consumer, target.lane, index)\n queueLaneHead.run(\n sessionId,\n target.consumer,\n cursorKeyOf(target.lane, index),\n target.lane,\n clearIndex,\n index,\n )\n }\n })\n // The batch is inserted already, so a cursor it wakes finds the batch's\n // first event of its types, or an earlier one.\n for (const consumer of consumers ?? []) {\n const log = consumer.log\n if (!log) continue\n const carries = events.some(\n (event) =>\n event.type !== CLEAR_EVENT_TYPE &&\n (log.types === null || log.types.includes(event.type)),\n )\n if (!carries) continue\n wakeLogAt({ sessionId, consumer: consumer.name, log, base, clearIndex })\n }\n }\n\n const refreshLogTypes = (\n sessionId: string,\n consumers: readonly StoreConsumer[] | undefined,\n ): void => {\n for (const consumer of consumers ?? []) {\n if (!consumer.log) continue\n const types =\n consumer.log.types === null ? null : JSON.stringify(consumer.log.types)\n refreshTypes.run(types, sessionId, consumer.name, types)\n }\n }\n\n const insertBatch = ({\n sessionId,\n events,\n clearIndex,\n consumers,\n cause,\n }: {\n sessionId: string\n events: readonly AppendEvent[]\n clearIndex: number\n consumers: readonly StoreConsumer[] | undefined\n cause?: EventCause\n }): StoredEvent[] => {\n const base = headOf(sessionId)\n const now = clock.now().getTime()\n const inserted = events.map((event, offset): StoredEvent => {\n const id = event.id ?? generateId()\n const index = base + 1 + offset\n const eventCause = cause ?? event.cause ?? null\n insertEvent.run(\n sessionId,\n index,\n event.type,\n JSON.stringify(event.payload) ?? 'null',\n id,\n now,\n eventCause ? JSON.stringify(eventCause) : null,\n )\n return {\n id,\n type: event.type,\n payload: structuredClone(event.payload),\n index,\n sessionId,\n createdAt: new Date(now),\n cause: eventCause ? { ...eventCause } : null,\n }\n })\n enqueue({ sessionId, events, base, clearIndex, consumers })\n return inserted\n }\n\n return {\n async append(sessionId, events, appendOptions) {\n const consumers = appendOptions?.consumers\n if (events.length === 0) {\n return wrap(() => ({\n events: [],\n hasPending: hasPending(sessionId, clearIndexOf(sessionId), consumers),\n }))\n }\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\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 const ifLastIndex = appendOptions?.ifLastIndex\n const conditional =\n ifLastIndex === undefined\n ? undefined\n : { sessionId, ifLastIndex, head: headOf(sessionId) }\n if (suppliedIds.length > 0) {\n const existing = liveOwnersOf(sessionId, clearIndex, suppliedIds)\n if (existing.length > 0) {\n if (existing.length === events.length) {\n return {\n events: idempotentReplay(\n events,\n existing.map(toStored),\n conditional,\n ),\n hasPending: hasPending(sessionId, clearIndex, consumers),\n }\n }\n throw duplicateBatchError(\n `batch mixes ${existing.length} already-appended and ${events.length - existing.length} fresh events`,\n conditional,\n )\n }\n }\n\n const originIndex = appendOptions?.originIndex\n if (originIndex !== undefined && originIndex <= clearIndex) {\n throw sessionClearedError(\n `scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,\n )\n }\n // Attempt-currency fence: a fresh consumer append commits only\n // while its causal attempt still owns the cursor's head\n // (store.ts `append`). `begin immediate` serializes this read\n // against a concurrent claim.\n for (const e of events) {\n if (e.cause)\n assertCurrentCause({ sessionId, cause: e.cause, clearIndex })\n }\n if (conditional && conditional.head !== conditional.ifLastIndex) {\n throw lastIndexMismatchError(\n sessionId,\n conditional.ifLastIndex,\n conditional.head,\n )\n }\n\n const inserted = insertBatch({\n sessionId,\n events,\n clearIndex,\n consumers,\n })\n return {\n events: inserted,\n hasPending: hasPending(sessionId, clearIndex, consumers),\n }\n }),\n )\n },\n\n async wakeLog({ sessionId, consumer }) {\n return wrap(() => {\n // Nothing after the latest clear: a missing cursor stays missing,\n // and nothing takes the write lock.\n const { lane } = consumer.log\n const marker = clearIndexOf(sessionId)\n if (\n headOf(sessionId) === marker &&\n !selectCursor.get(sessionId, consumer.name, cursorKeyOf(lane, 0))\n ) {\n return {\n cursor: idleLogCursor(consumer.name, lane, marker),\n head: marker,\n }\n }\n return tx(() => {\n const head = headOf(sessionId)\n const key = wakeLogAt({\n sessionId,\n consumer: consumer.name,\n log: consumer.log,\n base: head,\n clearIndex: clearIndexOf(sessionId),\n })\n const cursor = selectCursor.get(sessionId, consumer.name, key)\n return { cursor: toCursor(cursor as CursorRow), head }\n })\n })\n },\n\n async cursors(sessionId) {\n return wrap(() => {\n const clearIndex = clearIndexOf(sessionId)\n const rows = db\n .prepare(\n `select * from a2_cursors\n where session_id = ?\n and (\n kind = 'log'\n or head > ?\n or (kind = 'lane' and head is null and position > ?)\n )\n order by consumer, lane is null, lane, head`,\n )\n .all(sessionId, clearIndex, clearIndex) as unknown as CursorRow[]\n return rows.map(toCursor)\n })\n },\n\n async read(sessionId, opts) {\n return wrap(() => {\n // Reads start at the latest clear marker.\n const conditions = [\n 'session_id = ?',\n 'idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)',\n ]\n const params: (string | number)[] = [sessionId, sessionId]\n if (opts?.afterIndex !== undefined) {\n conditions.push('idx > ?')\n params.push(opts.afterIndex)\n }\n if (opts?.throughIndex !== undefined) {\n conditions.push('idx <= ?')\n params.push(opts.throughIndex)\n }\n const rows = db\n .prepare(\n `select * from a2_events where ${conditions.join(' and ')} order by idx`,\n )\n .all(...params) as unknown as EventRow[]\n return rows.map(toStored)\n })\n },\n\n async claimAvailable({\n sessionId,\n holder,\n ttlMs,\n expiresAtMs,\n exclude = [],\n consumers,\n maxFailures,\n through,\n }) {\n return wrap(() =>\n tx(() => {\n refreshLogTypes(sessionId, consumers)\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const clearIndex = clearIndexOf(sessionId)\n const served = servedClause(consumers)\n const excludedKeys = exclude.map(\n (ref) => `${ref.consumer}\\u0000${cursorKeyOf(ref.lane, ref.index)}`,\n )\n const exclusion =\n excludedKeys.length === 0\n ? ''\n : `and cursor.consumer || char(0) || cursor.cursor_key\n not in (${placeholders(excludedKeys.length)})`\n const bounded = through === undefined ? '' : 'and cursor.head <= ?'\n const filter = `\n where cursor.session_id = ?\n and cursor.head > ?\n and cursor.failed_at is null\n ${bounded}\n ${served.sql}\n ${exclusion}`\n const params = [\n sessionId,\n clearIndex,\n ...(through === undefined ? [] : [through]),\n ...served.params,\n ...excludedKeys,\n now,\n ]\n const eligible = db\n .prepare(\n `select cursor.consumer, cursor.cursor_key,\n cursor.claim_holder, cursor.failure_count\n from a2_cursors cursor\n ${filter}\n and (cursor.claim_expires_at is null\n or cursor.claim_expires_at <= ?)\n order by cursor.head, cursor.consumer`,\n )\n .all(...params) as unknown as Array<\n Pick<\n CursorRow,\n 'consumer' | 'cursor_key' | 'claim_holder' | 'failure_count'\n >\n >\n if (eligible.length > 0) {\n const claimHead = db.prepare(\n `update a2_cursors set\n attempt_count = attempt_count + 1,\n failure_count = failure_count\n + case when claim_holder is null then 0 else 1 end,\n first_claimed_at = coalesce(first_claimed_at, ?),\n last_claimed_at = ?,\n claim_holder = ?,\n claim_expires_at = ?\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n // Advancing the attempt fences the lapsed one; nothing runs.\n const deadLetterHead = db.prepare(\n `update a2_cursors set\n attempt_count = attempt_count + 1,\n failure_count = failure_count + 1,\n failed_at = ?,\n claim_holder = null,\n claim_expires_at = null\n where session_id = ? and consumer = ? and cursor_key = ?`,\n )\n const selectHeadRow = db.prepare(\n `${HEAD_SELECT}\n where cursor.session_id = ? and cursor.consumer = ?\n and cursor.cursor_key = ?`,\n )\n const events = eligible.map((row) => {\n // A lapsed claim's takeover that spends the last failed attempt.\n const exhausted =\n row.claim_holder !== null &&\n maxFailures !== undefined &&\n Number(row.failure_count) + 1 >= maxFailures\n if (exhausted) {\n deadLetterHead.run(now, sessionId, row.consumer, row.cursor_key)\n } else {\n claimHead.run(\n now,\n now,\n holder,\n expiresAt,\n sessionId,\n row.consumer,\n row.cursor_key,\n )\n }\n const claimed = toHead(\n selectHeadRow.get(\n sessionId,\n row.consumer,\n row.cursor_key,\n ) as HeadRow,\n )\n if (exhausted) {\n releasePins(\n sessionId,\n row.consumer,\n row.cursor_key,\n claimed.index,\n )\n }\n if (clearIndex > 0) claimed.clearIndex = clearIndex\n return claimed\n })\n return { outcome: 'claimed', events, head: headOf(sessionId) }\n }\n\n const active = db\n .prepare(\n `select min(cursor.claim_expires_at) as retry_at\n from a2_cursors cursor\n ${filter}\n and cursor.claim_expires_at > ?`,\n )\n .get(...params) as { retry_at: number | bigint | null }\n return active.retry_at === null\n ? { outcome: 'settled' }\n : { outcome: 'busy', retryAt: new Date(Number(active.retry_at)) }\n }),\n )\n },\n\n async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {\n if (claims.length === 0) return { renewed: [], superseded: [] }\n return wrap(() =>\n tx(() => {\n const now = clock.now().getTime()\n const expiresAt = expiresAtMs ?? now + ttlMs\n const renewed: HeadRef[] = []\n const superseded: HeadRef[] = []\n const extend = db.prepare(\n `update a2_cursors set claim_expires_at = ?\n where session_id = ? and consumer = ? and cursor_key = ?\n and head = ?\n and attempt_count = ?\n and failed_at is null\n and claim_holder = ?\n and claim_expires_at > ?`,\n )\n const clearIndex = clearIndexOf(sessionId)\n const ordered = claims.toSorted(\n (a, b) => a.index - b.index || compareText(a.consumer, b.consumer),\n )\n for (const { attempt, ...ref } of ordered) {\n if (ref.index <= clearIndex) {\n superseded.push(ref)\n continue\n }\n const cursor = cursorOf(sessionId, ref)\n if (!cursor) continue\n const head = toNumber(cursor.head)\n // An event the cursor has not reached yet holds no claim to renew.\n if (head !== ref.index && Number(cursor.position) < ref.index)\n continue\n if (head !== ref.index || Number(cursor.attempt_count) > attempt) {\n superseded.push(ref)\n continue\n }\n const updated = extend.run(\n expiresAt,\n sessionId,\n ref.consumer,\n cursor.cursor_key,\n ref.index,\n attempt,\n holder,\n now,\n )\n if (Number(updated.changes) === 1) renewed.push(ref)\n }\n return { renewed, superseded }\n }),\n )\n },\n\n async completeAttempt({\n sessionId,\n consumer,\n lane,\n index,\n attempt,\n events,\n consumers,\n }) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n if (index <= clearIndex) return { outcome: 'superseded' }\n const cursor = cursorOf(sessionId, { consumer, lane, index })\n if (!cursor) {\n if (!selectEvent.get(sessionId, index)) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n return { outcome: 'superseded' }\n }\n refreshLogTypes(sessionId, consumers)\n 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\n if (toNumber(cursor.head) !== index) {\n let completion:\n { attempt: number | null; ids: string | null } | undefined\n if (cursor.kind === 'lane') {\n const row = selectLaneEvent.get(\n sessionId,\n consumer,\n lane,\n index,\n ) as\n | {\n completed_attempt: number | bigint | null\n returned_event_ids: string | null\n }\n | undefined\n completion = row && {\n attempt: toNumber(row.completed_attempt),\n ids: row.returned_event_ids,\n }\n } else if (toNumber(cursor.completed_idx) === index) {\n completion = {\n attempt: toNumber(cursor.completed_attempt),\n ids: cursor.returned_event_ids,\n }\n }\n if (!completion || completion.attempt !== attempt) {\n return { outcome: 'superseded' }\n }\n const returnedIds =\n completion.ids === null\n ? null\n : decodeReturnedEventIds(completion.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 const existing =\n ids.length === 0\n ? []\n : (selectByIds(ids.length).all(\n sessionId,\n ...ids,\n ) as unknown as EventRow[])\n const byId = new Map(existing.map((row) => [row.event_id, row]))\n const ordered = ids.map((id) => byId.get(id))\n if (\n ordered.some((row) => {\n const cause = row ? decodeCause(row.cause) : null\n return (\n !row ||\n cause?.consumer !== consumer ||\n cause.lane !== lane ||\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:\n events.length === 0\n ? []\n : idempotentReplay(\n events,\n (ordered as EventRow[]).map(toStored),\n ),\n }\n }\n if (\n Number(cursor.attempt_count) !== attempt ||\n cursor.claim_holder === null ||\n cursor.failed_at !== null\n ) {\n return { outcome: 'superseded' }\n }\n\n if (liveOwnersOf(sessionId, clearIndex, ids).length > 0) {\n throw new A2Error(\n 'PARTIAL_DUPLICATE_BATCH',\n 'returned event batch contains an already-appended event id',\n )\n }\n\n const inserted = insertBatch({\n sessionId,\n events,\n clearIndex,\n consumers,\n cause: { consumer, lane, index, attempt },\n })\n const returned = JSON.stringify(ids)\n if (cursor.kind === 'lane') {\n finishLaneEvent.run(\n attempt,\n returned,\n sessionId,\n consumer,\n lane,\n index,\n )\n } else {\n recordCompletion.run(\n index,\n attempt,\n returned,\n sessionId,\n consumer,\n cursor.cursor_key,\n )\n }\n advance(sessionId, cursor)\n return { outcome: 'completed', events: inserted }\n }),\n )\n },\n\n async failAttempt({\n sessionId,\n consumer,\n lane,\n index,\n attempt,\n error,\n maxFailures,\n retryAfterMs,\n }) {\n return wrap(() =>\n tx(() => {\n if (index <= clearIndexOf(sessionId)) {\n return { outcome: 'superseded', failureCount: 0 }\n }\n const current = cursorOf(sessionId, { consumer, lane, index })\n if (!current) {\n if (!selectEvent.get(sessionId, index)) {\n throw new TypeError(\n `no event at index ${index} in session '${sessionId}'`,\n )\n }\n return { outcome: 'superseded', failureCount: 0 }\n }\n if (toNumber(current.head) !== index) {\n return { outcome: 'superseded', failureCount: 0 }\n }\n const failureCount = Number(current.failure_count)\n if (Number(current.attempt_count) !== attempt) {\n return { outcome: 'superseded', failureCount }\n }\n if (\n current.claim_holder === null &&\n current.last_failed_attempt !== null &&\n Number(current.last_failed_attempt) === attempt\n ) {\n return {\n outcome: current.failed_at === null ? 'failed' : 'dead_lettered',\n failureCount,\n }\n }\n if (current.failed_at !== null) {\n return { outcome: 'dead_lettered', failureCount }\n }\n if (current.claim_holder === null) {\n return { outcome: 'superseded', failureCount }\n }\n\n const now = clock.now().getTime()\n const nextFailureCount = failureCount + 1\n const deadLettered = nextFailureCount >= maxFailures\n db.prepare(\n `update a2_cursors set\n failure_count = ?,\n last_error = ?,\n last_failed_at = ?,\n last_failed_attempt = ?,\n failed_at = ?,\n claim_holder = null,\n claim_expires_at = ?\n where session_id = ? and consumer = ? and cursor_key = ?`,\n ).run(\n nextFailureCount,\n error,\n now,\n attempt,\n deadLettered ? now : null,\n deadLettered || retryAfterMs === undefined\n ? null\n : now + retryAfterMs,\n sessionId,\n consumer,\n current.cursor_key,\n )\n if (deadLettered) {\n releasePins(sessionId, consumer, current.cursor_key, index)\n }\n return {\n outcome: deadLettered ? 'dead_lettered' : 'failed',\n failureCount: nextFailureCount,\n }\n }),\n )\n },\n\n async deadLetters(sessionId) {\n return wrap(() => {\n const rows = db\n .prepare(\n `${HEAD_SELECT}\n where cursor.session_id = ?\n and cursor.failed_at is not null\n and cursor.head > coalesce(\n (select clear_index from a2_clears where session_id = ?), 0\n )\n order by cursor.head, cursor.consumer`,\n )\n .all(sessionId, sessionId) as unknown as HeadRow[]\n return rows.map(toHead)\n })\n },\n\n async redrive({ sessionId, consumer, index, action, consumers }) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n const cursor =\n index > clearIndex\n ? (db\n .prepare(\n `select * from a2_cursors\n where session_id = ? and consumer = ? and head = ?\n and failed_at is not null`,\n )\n .get(sessionId, consumer, index) as CursorRow | undefined)\n : undefined\n if (cursor) {\n refreshLogTypes(sessionId, consumers)\n if (action === 'skip') {\n if (cursor.kind === 'lane') {\n finishLaneEvent.run(\n null,\n null,\n sessionId,\n consumer,\n cursor.lane,\n index,\n )\n }\n advance(sessionId, cursor)\n } else {\n db.prepare(\n `update a2_cursors set failed_at = null, failure_count = 0\n where session_id = ? and consumer = ? and cursor_key = ?`,\n ).run(sessionId, consumer, cursor.cursor_key)\n }\n }\n return {\n redriven: cursor !== undefined,\n hasPending: hasPending(sessionId, clearIndex, consumers),\n }\n }),\n )\n },\n\n async readState(sessionId, reducerName, stateOptions) {\n return wrap(() => {\n const throughIndex = stateOptions?.throughIndex ?? null\n const snapshotThroughIndex =\n stateOptions?.snapshotThroughIndex ?? throughIndex\n const rows = db\n .prepare(\n `with boundary as materialized (\n select coalesce(\n (select clear_index from a2_clears where session_id = ?), 0\n ) as clear_index\n ), head as materialized (\n select up_to_index, state, updated_at\n from a2_snapshots\n where session_id = ? and reducer_name = ?\n and up_to_index >= (select clear_index from boundary)\n ), snapshot as materialized (\n select up_to_index, state from (\n select up_to_index, state from head\n where (? is null or up_to_index <= ?)\n union all\n select up_to_index, state\n from a2_snapshot_history\n where session_id = ? and reducer_name = ?\n and up_to_index >= (select clear_index from boundary)\n and (? is null or up_to_index <= ?)\n ) order by up_to_index desc limit 1\n )\n select 0 as row_order, 'snapshot' as row_kind,\n (select clear_index from boundary) as clear_index,\n (select up_to_index from head) as head_index,\n (select up_to_index from snapshot) as snapshot_index,\n (select state from snapshot) as snapshot_state,\n null as session_id, null as idx, null as event_type,\n null as payload, null as event_id, null as created_at,\n null as cause\n union all\n select 1 as row_order, 'event' as row_kind,\n null as clear_index,\n (select up_to_index from head) as head_index,\n null as snapshot_index, null as snapshot_state,\n event.session_id, event.idx, event.event_type,\n event.payload, event.event_id, event.created_at,\n event.cause\n from a2_events as event\n where event.session_id = ?\n and event.idx > max(\n coalesce((select up_to_index from snapshot), 0),\n (select clear_index from boundary) - 1\n )\n and (? is null or event.idx <= ?)\n order by row_order, idx`,\n )\n .all(\n sessionId,\n sessionId,\n reducerName,\n snapshotThroughIndex,\n snapshotThroughIndex,\n sessionId,\n reducerName,\n snapshotThroughIndex,\n snapshotThroughIndex,\n sessionId,\n throughIndex,\n throughIndex,\n ) as unknown as StateReadRow[]\n const snapshot = rows.find((row) => row.row_kind === 'snapshot')\n const clearIndex = Number(snapshot?.clear_index ?? 0)\n return {\n ...(clearIndex > 0 ? { clearIndex } : {}),\n headIndex:\n snapshot?.head_index === null || snapshot?.head_index === undefined\n ? null\n : Number(snapshot.head_index),\n snapshot:\n snapshot?.snapshot_index !== null &&\n snapshot?.snapshot_index !== undefined\n ? {\n index: Number(snapshot.snapshot_index),\n state: JSON.parse(snapshot.snapshot_state!),\n }\n : null,\n events: rows\n .filter(\n (row): row is StateReadRow & EventRow => row.row_kind === 'event',\n )\n .map(toEvent),\n }\n })\n },\n\n async putSnapshots(sessionId, reducerName, writes) {\n await wrap(() =>\n tx(() => {\n const selectSnapshotHead = db.prepare(\n `select up_to_index, state, updated_at from a2_snapshots\n where session_id = ? and reducer_name = ?`,\n )\n const unfinished = db.prepare(\n `select 1 from a2_cursors\n where session_id = ? and consumer = ? and cursor_key = ?\n and head = ? and head > ? and failed_at is null`,\n )\n const insertPin = db.prepare(\n `insert or ignore into a2_snapshot_pins\n (session_id, consumer, cursor_key, event_index, reducer_name, up_to_index)\n values (?, ?, ?, ?, ?, ?)`,\n )\n const hasPin = db.prepare(\n `select 1 from a2_snapshot_pins\n where session_id = ? and reducer_name = ? and up_to_index = ?\n limit 1`,\n )\n const putHistory = db.prepare(\n `insert into a2_snapshot_history\n (session_id, reducer_name, up_to_index, state, updated_at)\n values (?, ?, ?, ?, ?)\n on conflict (session_id, reducer_name, up_to_index) do update set\n state = excluded.state, updated_at = excluded.updated_at`,\n )\n const putHead = db.prepare(\n `insert into a2_snapshots\n (session_id, reducer_name, up_to_index, state, updated_at)\n values (?, ?, ?, ?, ?)\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 const deleteHistory = db.prepare(\n `delete from a2_snapshot_history\n where session_id = ? and reducer_name = ? and up_to_index = ?`,\n )\n const clearIndex = clearIndexOf(sessionId)\n for (const write of writes.toSorted((a, b) => a.index - b.index)) {\n // A fold from before the latest clear describes a lifetime that is gone.\n if (write.index < clearIndex) continue\n for (const pin of write.pins ?? []) {\n const cursorKey = cursorKeyOf(pin.lane, pin.index)\n if (\n unfinished.get(\n sessionId,\n pin.consumer,\n cursorKey,\n pin.index,\n clearIndex,\n )\n ) {\n insertPin.run(\n sessionId,\n pin.consumer,\n cursorKey,\n pin.index,\n reducerName,\n write.index,\n )\n }\n }\n const head = selectSnapshotHead.get(sessionId, reducerName) as\n SnapshotHeadRow | undefined\n const now = clock.now().getTime()\n if (!head || write.index >= Number(head.up_to_index)) {\n if (\n head &&\n write.index > Number(head.up_to_index) &&\n Number(head.up_to_index) >= clearIndex &&\n hasPin.get(sessionId, reducerName, Number(head.up_to_index))\n ) {\n putHistory.run(\n sessionId,\n reducerName,\n Number(head.up_to_index),\n head.state,\n Number(head.updated_at),\n )\n }\n putHead.run(\n sessionId,\n reducerName,\n write.index,\n JSON.stringify(write.state) ?? 'null',\n now,\n )\n deleteHistory.run(sessionId, reducerName, write.index)\n } else if (\n write.index < Number(head.up_to_index) &&\n hasPin.get(sessionId, reducerName, write.index)\n ) {\n putHistory.run(\n sessionId,\n reducerName,\n write.index,\n JSON.stringify(write.state) ?? 'null',\n now,\n )\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 wrap(() =>\n tx(() => {\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 nowMs = clock.now().getTime()\n const expiresAtMs = nowMs + meta.ttlMs\n for (const [field, value] of entries) {\n // Field-wise LWW against values and tombstones alike: an\n // unexpired stored row with a strictly newer `at` survives;\n // ties go to the incoming write (memory parity).\n upsertPresence.run(\n ns,\n participant,\n field,\n JSON.stringify(value) ?? 'null',\n meta.seen,\n atMs,\n expiresAtMs,\n nowMs,\n )\n }\n }),\n )\n },\n\n async read(ns) {\n return wrap(() => {\n const now = clock.now().getTime()\n // The sweep is opportunistic cleanup; the select's filter\n // alone keeps expired rows out of this result.\n sweepPresence.run(ns, now)\n const rows = selectPresence.all(ns, now) as unknown as PresenceDbRow[]\n return rows.map((row) => ({\n participant: row.participant,\n field: row.field,\n value: JSON.parse(row.value) as unknown,\n seen: Number(row.seen),\n at: new Date(Number(row.at)),\n expiresAt: new Date(Number(row.expires_at)),\n }))\n })\n },\n },\n\n stream(sessionId, opts) {\n const readAfter = (afterIndex: number): StoredEvent[] =>\n wrap(() => {\n // A cursor below the latest marker resumes at it.\n const rows = db\n .prepare(\n `select * from a2_events\n where session_id = ? and idx > ?\n and idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)\n order by idx`,\n )\n .all(sessionId, afterIndex, sessionId) as unknown as EventRow[]\n return rows.map(toStored)\n })\n return pollingStream(\n readAfter,\n opts?.startAfter !== undefined ? { startAfter: opts.startAfter } : {},\n )\n },\n\n async clear({ sessionId, ifLastIndex, cause }) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n if (cause) assertCurrentCause({ sessionId, cause, clearIndex })\n const head = headOf(sessionId)\n if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {\n return { cleared: true, index: head, clearIndex }\n }\n if (ifLastIndex !== undefined && ifLastIndex !== head) {\n return { cleared: false, index: head, clearIndex }\n }\n if (head === 0 || clearIndex === head) {\n return { cleared: true, index: head, clearIndex }\n }\n const marker = head + 1\n insertEvent.run(\n sessionId,\n marker,\n CLEAR_EVENT_TYPE,\n 'null',\n generateId(),\n clock.now().getTime(),\n null,\n )\n db.prepare(\n `insert into a2_clears (session_id, clear_index) values (?, ?)\n on conflict (session_id) do update set clear_index = excluded.clear_index`,\n ).run(sessionId, marker)\n // One row per log consumer: they count from the marker. Lane and\n // concurrent cursors at or below it are superseded by the marker\n // alone, and reclaim deletes them.\n db.prepare(\n `update a2_cursors\n set position = ?, head = null, ${FRESH_DISPATCH},\n completed_idx = null, completed_attempt = null,\n returned_event_ids = null\n where session_id = ? and kind = 'log'`,\n ).run(marker, sessionId)\n db.prepare(\n 'delete from a2_snapshot_pins where session_id = ? and event_index < ?',\n ).run(sessionId, marker)\n return { cleared: true, index: marker, clearIndex: marker }\n }),\n )\n },\n\n async reclaim(sessionId) {\n return wrap(() =>\n tx(() => {\n const clearIndex = clearIndexOf(sessionId)\n if (clearIndex === 0) return { deleted: 0, done: true }\n const last = db\n .prepare(\n `select max(idx) as last from (\n select idx from a2_events\n where session_id = ? and idx < ?\n order by idx limit ?\n )`,\n )\n .get(sessionId, clearIndex, RECLAIM_BATCH) as {\n last: number | bigint | null\n }\n let deleted = 0\n if (last.last !== null) {\n const through = Number(last.last)\n deleted = Number(\n db\n .prepare(\n 'delete from a2_events where session_id = ? and idx <= ?',\n )\n .run(sessionId, through).changes,\n )\n db.prepare(\n 'delete from a2_lane_events where session_id = ? and idx <= ?',\n ).run(sessionId, through)\n db.prepare(\n `delete from a2_cursors\n where session_id = ? and kind = 'event' and position < ?`,\n ).run(sessionId, through)\n }\n const done =\n db\n .prepare(\n 'select 1 from a2_events where session_id = ? and idx < ? limit 1',\n )\n .get(sessionId, clearIndex) === undefined\n // Writes serialize with the clear, so none lands below the marker later.\n if (done) {\n db.prepare(\n `delete from a2_cursors\n where session_id = ? and kind != 'log'\n and (head is null or head <= ?) and position <= ?`,\n ).run(sessionId, clearIndex, clearIndex)\n for (const table of [\n 'a2_snapshot_pins',\n 'a2_snapshot_history',\n 'a2_snapshots',\n ]) {\n db.prepare(\n `delete from ${table} where session_id = ? and up_to_index < ?`,\n ).run(sessionId, clearIndex)\n }\n }\n return { deleted, done }\n }),\n )\n },\n\n close() {\n db.close()\n },\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { r as Clock, s as IdSource, t as A2Store } from "./store-DEzcHZOA.js";
|
|
2
|
+
import { RedisClientOptions } from "redis";
|
|
3
|
+
//#region src/store-valkey.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* node-redis `createClient` options. A2 reads replies in their RESP2
|
|
6
|
+
* shapes, so it always connects with `RESP: 2`, which rules out `RESP`
|
|
7
|
+
* and the options that need RESP3.
|
|
8
|
+
*/
|
|
9
|
+
type ValkeyClientOptions = Omit<RedisClientOptions, "RESP" | "clientSideCache" | "emitInvalidate" | "maintNotifications">;
|
|
10
|
+
type ValkeyStoreOptions = {
|
|
11
|
+
/** `redis://` or `rediss://` URL. Overrides `clientOptions.url`. */
|
|
12
|
+
url?: string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Options for both clients A2 creates: `socket` (TLS, connect timeout,
|
|
15
|
+
* `reconnectStrategy`), `credentialsProvider`, `commandOptions`,
|
|
16
|
+
* `disableOfflineQueue`, and so on.
|
|
17
|
+
*/
|
|
18
|
+
clientOptions?: ValkeyClientOptions;
|
|
19
|
+
/** Key prefix — isolates multiple apps on one server. Default `'a2'`. */
|
|
20
|
+
keyPrefix?: string;
|
|
21
|
+
/** Injectable clock — every stored timestamp comes from here. */
|
|
22
|
+
clock?: Clock;
|
|
23
|
+
/** Injectable id source for generated event ids. */
|
|
24
|
+
ids?: IdSource;
|
|
25
|
+
};
|
|
26
|
+
type ValkeyStore = A2Store & {
|
|
27
|
+
/** Destroy the command client and the subscriber. */
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
declare function valkey(options?: ValkeyStoreOptions): ValkeyStore;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { ValkeyClientOptions, ValkeyStore, ValkeyStoreOptions, valkey };
|
|
33
|
+
//# sourceMappingURL=store-valkey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-valkey.d.ts","names":[],"sources":["../src/store-valkey.ts"],"mappings":";;;;;;;;KAuCY,sBAAsB,KAChC;KAIU;;EAEV;;;;;;EAMA,gBAAgB;;EAEhB;;EAEA,QAAQ;;EAER,MAAM;;KAGI,cAAc;;EAExB,SAAS;;iBAkJK,OAAO,UAAS,qBAA0B"}
|