@loomwork/plugin-sqlite 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +62 -2
  4. package/dist/SqliteArchiveStore.d.ts +22 -0
  5. package/dist/SqliteArchiveStore.d.ts.map +1 -0
  6. package/dist/SqliteArchiveStore.js +129 -0
  7. package/dist/SqliteArchiveStore.js.map +1 -0
  8. package/dist/SqliteBranchRegistry.d.ts +26 -0
  9. package/dist/SqliteBranchRegistry.d.ts.map +1 -0
  10. package/dist/SqliteBranchRegistry.js +154 -0
  11. package/dist/SqliteBranchRegistry.js.map +1 -0
  12. package/dist/SqliteContentStore.d.ts +55 -0
  13. package/dist/SqliteContentStore.d.ts.map +1 -0
  14. package/dist/SqliteContentStore.js +150 -0
  15. package/dist/SqliteContentStore.js.map +1 -0
  16. package/dist/SqliteDurableStateStore.d.ts +26 -0
  17. package/dist/SqliteDurableStateStore.d.ts.map +1 -0
  18. package/dist/SqliteDurableStateStore.js +81 -0
  19. package/dist/SqliteDurableStateStore.js.map +1 -0
  20. package/dist/SqliteEventStore.d.ts +55 -0
  21. package/dist/SqliteEventStore.d.ts.map +1 -0
  22. package/dist/SqliteEventStore.js +297 -0
  23. package/dist/SqliteEventStore.js.map +1 -0
  24. package/dist/SqliteLeaseStore.d.ts +25 -0
  25. package/dist/SqliteLeaseStore.d.ts.map +1 -0
  26. package/dist/SqliteLeaseStore.js +107 -0
  27. package/dist/SqliteLeaseStore.js.map +1 -0
  28. package/dist/SqliteMemoryStore.d.ts +52 -0
  29. package/dist/SqliteMemoryStore.d.ts.map +1 -0
  30. package/dist/SqliteMemoryStore.js +149 -0
  31. package/dist/SqliteMemoryStore.js.map +1 -0
  32. package/dist/SqliteReadModelStore.d.ts +38 -0
  33. package/dist/SqliteReadModelStore.d.ts.map +1 -0
  34. package/dist/SqliteReadModelStore.js +292 -0
  35. package/dist/SqliteReadModelStore.js.map +1 -0
  36. package/dist/SqliteSnapshotStore.d.ts +31 -0
  37. package/dist/SqliteSnapshotStore.d.ts.map +1 -0
  38. package/dist/SqliteSnapshotStore.js +101 -0
  39. package/dist/SqliteSnapshotStore.js.map +1 -0
  40. package/dist/SqliteTimerStore.d.ts +19 -0
  41. package/dist/SqliteTimerStore.d.ts.map +1 -0
  42. package/dist/SqliteTimerStore.js +106 -0
  43. package/dist/SqliteTimerStore.js.map +1 -0
  44. package/dist/index.d.ts +13 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +26 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/preset.d.ts +31 -0
  49. package/dist/preset.d.ts.map +1 -0
  50. package/dist/preset.js +51 -0
  51. package/dist/preset.js.map +1 -0
  52. package/dist/schema.d.ts +3 -0
  53. package/dist/schema.d.ts.map +1 -0
  54. package/dist/schema.js +103 -0
  55. package/dist/schema.js.map +1 -0
  56. package/dist/snapshotSchema.d.ts +3 -0
  57. package/dist/snapshotSchema.d.ts.map +1 -0
  58. package/dist/snapshotSchema.js +19 -0
  59. package/dist/snapshotSchema.js.map +1 -0
  60. package/package.json +62 -3
  61. package/src/SqliteArchiveStore.ts +189 -0
  62. package/src/SqliteBranchRegistry.ts +199 -0
  63. package/src/SqliteContentStore.ts +195 -0
  64. package/src/SqliteDurableStateStore.ts +101 -0
  65. package/src/SqliteEventStore.ts +414 -0
  66. package/src/SqliteLeaseStore.ts +165 -0
  67. package/src/SqliteMemoryStore.ts +160 -0
  68. package/src/SqliteReadModelStore.ts +378 -0
  69. package/src/SqliteSnapshotStore.ts +157 -0
  70. package/src/SqliteTimerStore.ts +124 -0
  71. package/src/index.ts +13 -0
  72. package/src/preset.ts +66 -0
  73. package/src/schema.ts +100 -0
  74. package/src/snapshotSchema.ts +17 -0
@@ -0,0 +1,55 @@
1
+ import { type ContentListPage, type ContentListPageOptions, type ContentRef, type ContentStore } from "@loomwork/plugin-content";
2
+ /**
3
+ * SQLite-backed {@link ContentStore} (the first durable content backend —
4
+ * ADR 0046 deferred one "until a real requirement lands"; the db-copilot's
5
+ * evidence retention is that requirement, and `loom sweep content` needs a
6
+ * file-backed store to operate on). Content-addressed (`sha256:<hex>`),
7
+ * idempotent by hash, `putStream` per the ADR 0059 outcome contract
8
+ * (hash-while-spooling). Point it at its own file — blob I/O must not contend
9
+ * with event-append latency (TS-7 discipline).
10
+ *
11
+ * SPI 1.4.0 adds `touch` (a batched `UPDATE … RETURNING`). SPI 1.3.0 (ADR 0055 D5/D6): every blob carries `touched_at`, bumped by EVERY put (a dedup'd
12
+ * put is a re-citation), so `deleteIfUntouchedSince` is one conditional `DELETE` and
13
+ * `listPage` is a keyset scan on the `address` primary key. A pre-1.3.0 file is migrated on
14
+ * open: the column is added and existing blobs are backfilled to the migration time
15
+ * (conservative — none ages out until a full TTL after the upgrade).
16
+ */
17
+ export interface SqliteContentStoreOptions {
18
+ /** Clock for touch times (epoch ms). Default `Date.now`. */
19
+ now?: () => number;
20
+ }
21
+ export declare class SqliteContentStore implements ContentStore {
22
+ private readonly db;
23
+ private readonly now;
24
+ private readonly insertStmt;
25
+ private readonly getStmt;
26
+ private readonly deleteStmt;
27
+ private readonly deleteIfUntouchedStmt;
28
+ private readonly pageStmt;
29
+ private readonly pageBeforeStmt;
30
+ constructor(dbPath: string, options?: SqliteContentStoreOptions);
31
+ put(bytes: Uint8Array): Promise<ContentRef>;
32
+ putStream(stream: AsyncIterable<Uint8Array>): Promise<ContentRef>;
33
+ get(ref: ContentRef): Promise<Uint8Array | null>;
34
+ delete(ref: ContentRef): Promise<void>;
35
+ deleteIfUntouchedSince(ref: ContentRef, cutoffMs: number): Promise<boolean>;
36
+ touchClock(): Promise<number>;
37
+ /**
38
+ * SPI 1.4.0: bump `touched_at` for existing blobs without a put. One `UPDATE … WHERE address
39
+ * IN (…) RETURNING address` per chunk (bounded bind-variable count), all inside one
40
+ * transaction so a batch is all-or-nothing. `max()` keeps the time monotonic; an unknown
41
+ * address matches no row (no insert), and is reported back as missing.
42
+ */
43
+ /** One prepared `UPDATE` per distinct chunk size (at most {@link TOUCH_CHUNK} of them): a full chunk is prepared once, not per call. */
44
+ private readonly touchStatements;
45
+ private touchStatement;
46
+ touch(addresses: readonly string[]): Promise<string[]>;
47
+ listPage(opts?: ContentListPageOptions): Promise<ContentListPage>;
48
+ /**
49
+ * One-time upgrade of a pre-1.3.0 file. IMMEDIATE so two processes opening the same file
50
+ * cannot both add the column; the re-check inside the transaction makes it idempotent.
51
+ */
52
+ private migrateTouchedAt;
53
+ close(): Promise<void>;
54
+ }
55
+ //# sourceMappingURL=SqliteContentStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteContentStore.d.ts","sourceRoot":"","sources":["../src/SqliteContentStore.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,UAAU,EACf,KAAK,YAAY,EAElB,MAAM,0BAA0B,CAAC;AAMlC;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,yBAAyB;IACxC,4DAA4D;IAC5D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;gBAExC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,yBAA8B;IAsC7D,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAM3C,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IAgBjE,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAKhD,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,sBAAsB,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI3E,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAInC;;;;;OAKG;IACH,wIAAwI;IACxI,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyC;IAEzE,OAAO,CAAC,cAAc;IAYhB,KAAK,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiBtD,QAAQ,CAAC,IAAI,GAAE,sBAA2B,GAAG,OAAO,CAAC,eAAe,CAAC;IAc3E;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAelB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SqliteContentStore = void 0;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const plugin_content_1 = require("@loomwork/plugin-content");
9
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
10
+ /** Bind variables per `touch` UPDATE (SQLite builds cap at 999 on old versions). */
11
+ const TOUCH_CHUNK = 500;
12
+ class SqliteContentStore {
13
+ db;
14
+ now;
15
+ insertStmt;
16
+ getStmt;
17
+ deleteStmt;
18
+ deleteIfUntouchedStmt;
19
+ pageStmt;
20
+ pageBeforeStmt;
21
+ constructor(dbPath, options = {}) {
22
+ this.now = options.now ?? Date.now;
23
+ this.db = new better_sqlite3_1.default(dbPath);
24
+ try {
25
+ this.db.pragma("journal_mode = WAL");
26
+ this.db.pragma("synchronous = NORMAL");
27
+ this.db.exec(`
28
+ CREATE TABLE IF NOT EXISTS content_blobs (
29
+ address TEXT PRIMARY KEY,
30
+ bytes BLOB NOT NULL,
31
+ touched_at INTEGER NOT NULL DEFAULT 0
32
+ );
33
+ `);
34
+ this.migrateTouchedAt();
35
+ // Monotonic bump: a clock stepping back cannot make a re-cited blob look older.
36
+ this.insertStmt = this.db.prepare(`INSERT INTO content_blobs (address, bytes, touched_at) VALUES (?, ?, ?)
37
+ ON CONFLICT(address) DO UPDATE SET touched_at = max(touched_at, excluded.touched_at)`);
38
+ this.getStmt = this.db.prepare("SELECT bytes FROM content_blobs WHERE address = ?");
39
+ this.deleteStmt = this.db.prepare("DELETE FROM content_blobs WHERE address = ?");
40
+ this.deleteIfUntouchedStmt = this.db.prepare("DELETE FROM content_blobs WHERE address = ? AND touched_at < ?");
41
+ this.pageStmt = this.db.prepare(`SELECT address, touched_at AS touchedAt FROM content_blobs
42
+ WHERE address > ? ORDER BY address LIMIT ?`);
43
+ this.pageBeforeStmt = this.db.prepare(`SELECT address, touched_at AS touchedAt FROM content_blobs
44
+ WHERE address > ? AND touched_at < ? ORDER BY address LIMIT ?`);
45
+ }
46
+ catch (err) {
47
+ this.db.close(); // don't leak the file handle if setup fails on a bad/locked file
48
+ throw err;
49
+ }
50
+ }
51
+ async put(bytes) {
52
+ const address = `sha256:${(0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex")}`;
53
+ this.insertStmt.run(address, Buffer.from(bytes), this.now());
54
+ return { __loomContent: address, size: bytes.byteLength };
55
+ }
56
+ async putStream(stream) {
57
+ // ADR 0059 outcome contract: address of the full sequence, idempotent —
58
+ // hash while spooling, then a single idempotent insert.
59
+ const hash = (0, node_crypto_1.createHash)("sha256");
60
+ const chunks = [];
61
+ let size = 0;
62
+ for await (const chunk of stream) {
63
+ hash.update(chunk);
64
+ chunks.push(Buffer.from(chunk));
65
+ size += chunk.byteLength;
66
+ }
67
+ const address = `sha256:${hash.digest("hex")}`;
68
+ this.insertStmt.run(address, Buffer.concat(chunks, size), this.now());
69
+ return { __loomContent: address, size };
70
+ }
71
+ async get(ref) {
72
+ const row = this.getStmt.get(ref.__loomContent);
73
+ return row === undefined ? null : new Uint8Array(row.bytes);
74
+ }
75
+ async delete(ref) {
76
+ this.deleteStmt.run(ref.__loomContent);
77
+ }
78
+ async deleteIfUntouchedSince(ref, cutoffMs) {
79
+ return this.deleteIfUntouchedStmt.run(ref.__loomContent, cutoffMs).changes > 0;
80
+ }
81
+ async touchClock() {
82
+ return this.now();
83
+ }
84
+ /**
85
+ * SPI 1.4.0: bump `touched_at` for existing blobs without a put. One `UPDATE … WHERE address
86
+ * IN (…) RETURNING address` per chunk (bounded bind-variable count), all inside one
87
+ * transaction so a batch is all-or-nothing. `max()` keeps the time monotonic; an unknown
88
+ * address matches no row (no insert), and is reported back as missing.
89
+ */
90
+ /** One prepared `UPDATE` per distinct chunk size (at most {@link TOUCH_CHUNK} of them): a full chunk is prepared once, not per call. */
91
+ touchStatements = new Map();
92
+ touchStatement(size) {
93
+ let statement = this.touchStatements.get(size);
94
+ if (statement === undefined) {
95
+ statement = this.db.prepare(`UPDATE content_blobs SET touched_at = max(touched_at, ?)
96
+ WHERE address IN (${Array.from({ length: size }, () => "?").join(",")}) RETURNING address`);
97
+ this.touchStatements.set(size, statement);
98
+ }
99
+ return statement;
100
+ }
101
+ async touch(addresses) {
102
+ const unique = [...new Set(addresses)];
103
+ if (unique.length === 0)
104
+ return [];
105
+ const touchedAt = this.now();
106
+ const present = new Set();
107
+ this.db.transaction(() => {
108
+ for (let i = 0; i < unique.length; i += TOUCH_CHUNK) {
109
+ const chunk = unique.slice(i, i + TOUCH_CHUNK);
110
+ const rows = this.touchStatement(chunk.length).all(touchedAt, ...chunk);
111
+ for (const row of rows)
112
+ present.add(row.address);
113
+ }
114
+ })();
115
+ return unique.filter((a) => !present.has(a));
116
+ }
117
+ async listPage(opts = {}) {
118
+ const limit = (0, plugin_content_1.contentListPageLimit)(opts.limit);
119
+ const after = opts.cursor ?? "";
120
+ const entries = (opts.touchedBefore === undefined
121
+ ? this.pageStmt.all(after, limit)
122
+ : this.pageBeforeStmt.all(after, opts.touchedBefore, limit));
123
+ const last = entries[entries.length - 1];
124
+ return entries.length === limit && last !== undefined
125
+ ? { entries, nextCursor: last.address }
126
+ : { entries };
127
+ }
128
+ /**
129
+ * One-time upgrade of a pre-1.3.0 file. IMMEDIATE so two processes opening the same file
130
+ * cannot both add the column; the re-check inside the transaction makes it idempotent.
131
+ */
132
+ migrateTouchedAt() {
133
+ const hasColumn = () => this.db.prepare("PRAGMA table_info(content_blobs)").all().some((c) => c.name === "touched_at");
134
+ if (hasColumn())
135
+ return;
136
+ this.db
137
+ .transaction(() => {
138
+ if (hasColumn())
139
+ return;
140
+ this.db.exec("ALTER TABLE content_blobs ADD COLUMN touched_at INTEGER NOT NULL DEFAULT 0");
141
+ this.db.prepare("UPDATE content_blobs SET touched_at = ?").run(this.now());
142
+ })
143
+ .immediate();
144
+ }
145
+ async close() {
146
+ this.db.close();
147
+ }
148
+ }
149
+ exports.SqliteContentStore = SqliteContentStore;
150
+ //# sourceMappingURL=SqliteContentStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteContentStore.js","sourceRoot":"","sources":["../src/SqliteContentStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { DurableStateRecord, DurableStateStore } from "@loomwork/plugin-durable-state";
2
+ /**
3
+ * SQLite-backed {@link DurableStateStore} (ADR 0051 D2). Point it at the same
4
+ * DB file as the SqliteEventStore so one backup captures events + carry state.
5
+ * Own idempotent migration, same pattern as SqliteTimerStore. CAS rides
6
+ * single-statement atomicity: the guarded INSERT/UPDATE either lands the new
7
+ * revision or changes nothing, and a zero-change outcome reports the actual
8
+ * revision in a loud RevisionConflictError.
9
+ */
10
+ export declare class SqliteDurableStateStore implements DurableStateStore {
11
+ private readonly db;
12
+ private readonly loadStmt;
13
+ private readonly insertStmt;
14
+ private readonly updateStmt;
15
+ private readonly revisionStmt;
16
+ private readonly clearStmt;
17
+ constructor(dbPath: string);
18
+ load(key: string): Promise<DurableStateRecord | null>;
19
+ save(key: string, value: {
20
+ contractVersion: number;
21
+ payload: unknown;
22
+ }, expectedRevision: number): Promise<DurableStateRecord>;
23
+ clear(key: string): Promise<void>;
24
+ close(): Promise<void>;
25
+ }
26
+ //# sourceMappingURL=SqliteDurableStateStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteDurableStateStore.d.ts","sourceRoot":"","sources":["../src/SqliteDurableStateStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAU5F;;;;;;;GAOG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;gBAEnC,MAAM,EAAE,MAAM;IA8BpB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAUrD,IAAI,CACR,GAAG,EAAE,MAAM,EACX,KAAK,EAAE;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,EACpD,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,kBAAkB,CAAC;IAuBxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SqliteDurableStateStore = void 0;
7
+ const plugin_durable_state_1 = require("@loomwork/plugin-durable-state");
8
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
9
+ /**
10
+ * SQLite-backed {@link DurableStateStore} (ADR 0051 D2). Point it at the same
11
+ * DB file as the SqliteEventStore so one backup captures events + carry state.
12
+ * Own idempotent migration, same pattern as SqliteTimerStore. CAS rides
13
+ * single-statement atomicity: the guarded INSERT/UPDATE either lands the new
14
+ * revision or changes nothing, and a zero-change outcome reports the actual
15
+ * revision in a loud RevisionConflictError.
16
+ */
17
+ class SqliteDurableStateStore {
18
+ db;
19
+ loadStmt;
20
+ insertStmt;
21
+ updateStmt;
22
+ revisionStmt;
23
+ clearStmt;
24
+ constructor(dbPath) {
25
+ this.db = new better_sqlite3_1.default(dbPath);
26
+ try {
27
+ this.db.pragma("journal_mode = WAL");
28
+ this.db.pragma("synchronous = NORMAL");
29
+ this.db.exec(`
30
+ CREATE TABLE IF NOT EXISTS durable_state (
31
+ key TEXT PRIMARY KEY,
32
+ contract_version INTEGER NOT NULL,
33
+ revision INTEGER NOT NULL,
34
+ payload TEXT NOT NULL
35
+ );
36
+ `);
37
+ this.loadStmt = this.db.prepare("SELECT contract_version, revision, payload FROM durable_state WHERE key = ?");
38
+ this.insertStmt = this.db.prepare("INSERT OR IGNORE INTO durable_state (key, contract_version, revision, payload) VALUES (?, ?, 1, ?)");
39
+ this.updateStmt = this.db.prepare("UPDATE durable_state SET contract_version = ?, revision = ?, payload = ? WHERE key = ? AND revision = ?");
40
+ this.revisionStmt = this.db.prepare("SELECT revision FROM durable_state WHERE key = ?");
41
+ this.clearStmt = this.db.prepare("DELETE FROM durable_state WHERE key = ?");
42
+ }
43
+ catch (err) {
44
+ this.db.close(); // don't leak the file handle if setup fails on a bad/locked file
45
+ throw err;
46
+ }
47
+ }
48
+ async load(key) {
49
+ const row = this.loadStmt.get(key);
50
+ if (row === undefined)
51
+ return null;
52
+ return {
53
+ contractVersion: row.contract_version,
54
+ revision: row.revision,
55
+ payload: JSON.parse(row.payload),
56
+ };
57
+ }
58
+ async save(key, value, expectedRevision) {
59
+ const payload = JSON.stringify(value.payload);
60
+ const changes = expectedRevision === 0
61
+ ? this.insertStmt.run(key, value.contractVersion, payload).changes
62
+ : this.updateStmt.run(value.contractVersion, expectedRevision + 1, payload, key, expectedRevision).changes;
63
+ if (changes === 1) {
64
+ return {
65
+ contractVersion: value.contractVersion,
66
+ revision: expectedRevision + 1,
67
+ payload: value.payload,
68
+ };
69
+ }
70
+ const row = this.revisionStmt.get(key);
71
+ throw new plugin_durable_state_1.RevisionConflictError(key, expectedRevision, row?.revision ?? 0);
72
+ }
73
+ async clear(key) {
74
+ this.clearStmt.run(key);
75
+ }
76
+ async close() {
77
+ this.db.close();
78
+ }
79
+ }
80
+ exports.SqliteDurableStateStore = SqliteDurableStateStore;
81
+ //# sourceMappingURL=SqliteDurableStateStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteDurableStateStore.js","sourceRoot":"","sources":["../src/SqliteDurableStateStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { EventEnvelope } from "@loomwork/core";
2
+ import type { AppendIfUnchangedResult, EventStore, TipObservation } from "@loomwork/event-runtime";
3
+ export declare class SqliteEventStore implements EventStore {
4
+ private readonly db;
5
+ private pageFirstStmt;
6
+ private pageAfterStmt;
7
+ private readonly subscribers;
8
+ constructor(dbPath: string);
9
+ append(envelope: EventEnvelope): Promise<void>;
10
+ /**
11
+ * ADR 0074 D1 — bulk append in ONE transaction. better-sqlite3's `transaction()` wrapper
12
+ * is synchronous and rolls back on throw, which gives the required atomicity directly:
13
+ * a UNIQUE(execution_id, sequence) violation part-way aborts the whole statement group,
14
+ * so no execution is left with a hole.
15
+ *
16
+ * Subscribers fire only after the transaction commits — a subscriber must never observe
17
+ * a batch that could still roll back.
18
+ */
19
+ appendBatch(envelopes: readonly EventEnvelope[]): Promise<void>;
20
+ /**
21
+ * Which envelope in a rejected batch claimed something already taken — a
22
+ * `(executionId, sequence)` slot or an envelope `id`, either because the log already held
23
+ * it or because another envelope in the SAME batch did. Slot first, so a writer that lost
24
+ * a sequence race is told THAT rather than told about the id its own naming convention
25
+ * derived from the sequence it lost.
26
+ */
27
+ private duplicateIn;
28
+ /**
29
+ * SPI 2.2.0 (ADR 0116 amendment) — existence, head and the (always 0: SQLite does not fence)
30
+ * token are checked and the row inserted inside ONE `BEGIN IMMEDIATE` transaction, so a
31
+ * retention delete or another process's append cannot slip between the check and the write.
32
+ */
33
+ appendIfUnchanged(executionId: string, event: EventEnvelope | null, expect?: TipObservation): Promise<AppendIfUnchangedResult>;
34
+ read(executionId: string): Promise<EventEnvelope[]>;
35
+ readFrom(executionId: string, fromSequence: number): Promise<EventEnvelope[]>;
36
+ head(executionId: string): Promise<number | null>;
37
+ readRange(executionId: string, fromSequence: number, toSequence: number): Promise<EventEnvelope[]>;
38
+ copyPrefix(fromExecutionId: string, toExecutionId: string, throughSequence: number, metadata: Record<string, unknown>): Promise<void>;
39
+ subscribe(executionId: string, handler: (event: EventEnvelope) => void): Promise<() => void>;
40
+ deleteExecution(executionId: string): Promise<void>;
41
+ deleteThrough(executionId: string, through: number): Promise<void>;
42
+ truncatePrefix(executionId: string, throughSequence: number): Promise<void>;
43
+ truncatePrefixKeeping(executionId: string, throughSequence: number, keepEventTypes: readonly string[]): Promise<void>;
44
+ listExecutions(): Promise<string[]>;
45
+ listExecutionsPage(opts?: {
46
+ cursor?: string;
47
+ limit?: number;
48
+ }): Promise<{
49
+ executionIds: string[];
50
+ nextCursor?: string;
51
+ }>;
52
+ close(): Promise<void>;
53
+ private notify;
54
+ }
55
+ //# sourceMappingURL=SqliteEventStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteEventStore.d.ts","sourceRoot":"","sources":["../src/SqliteEventStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAgEnG,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;IACvC,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4B;gBAE5C,MAAM,EAAE,MAAM;IAYpB,MAAM,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAgCpD;;;;;;;;OAQG;IACG,WAAW,CAAC,SAAS,EAAE,SAAS,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCrE;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IAuBnB;;;;OAIG;IACG,iBAAiB,CACrB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,aAAa,GAAG,IAAI,EAC3B,MAAM,CAAC,EAAE,cAAc,GACtB,OAAO,CAAC,uBAAuB,CAAC;IAkE7B,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IASnD,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAS7E,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASjD,SAAS,CACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,EAAE,CAAC;IAUrB,UAAU,CACd,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC;IAoBV,SAAS,CACb,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GACtC,OAAO,CAAC,MAAM,IAAI,CAAC;IAIhB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOlE,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3E,qBAAqB,CACzB,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,SAAS,MAAM,EAAE,GAChC,OAAO,CAAC,IAAI,CAAC;IAUV,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAQnC,kBAAkB,CAAC,IAAI,CAAC,EAAE;QAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAsBtD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,CAAC,MAAM;CAGf"}