@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,297 @@
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.SqliteEventStore = void 0;
7
+ const event_runtime_1 = require("@loomwork/event-runtime");
8
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
9
+ const schema_1 = require("./schema");
10
+ /**
11
+ * #306 — normalize the `UNIQUE (execution_id, sequence)` violation to the portable
12
+ * {@link DuplicateSequenceError} every backend raises, so a caller need not know which
13
+ * store it is talking to (or match on driver text) to recognise a lost sequence race.
14
+ *
15
+ * Narrowed to THAT constraint by name: the table also has a PRIMARY KEY on `id`, and a
16
+ * duplicate id is a different bug (a re-used envelope id) that must not be reported as a
17
+ * sequence conflict. Anything else propagates untouched.
18
+ */
19
+ function isDuplicateSequenceViolation(err) {
20
+ if (!isConstraintViolation(err))
21
+ return false;
22
+ return err.message.includes("execution_id") && err.message.includes("sequence");
23
+ }
24
+ /**
25
+ * #309 — the table's OTHER key: `PRIMARY KEY (id)`. Distinguished from the sequence
26
+ * constraint by the column named in the violation, so each maps to its own error and
27
+ * neither is reported as the other.
28
+ */
29
+ function isDuplicateIdViolation(err) {
30
+ if (!isConstraintViolation(err))
31
+ return false;
32
+ return err.message.includes("events.id") && !err.message.includes("execution_id");
33
+ }
34
+ function isConstraintViolation(err) {
35
+ if (!(err instanceof Error))
36
+ return false;
37
+ const code = err.code;
38
+ return typeof code === "string" && code.startsWith("SQLITE_CONSTRAINT");
39
+ }
40
+ function rowToEnvelope(row) {
41
+ return {
42
+ id: row.id,
43
+ workflowId: row.workflow_id,
44
+ executionId: row.execution_id,
45
+ eventType: row.event_type,
46
+ sequence: row.sequence,
47
+ timestamp: row.timestamp,
48
+ payload: JSON.parse(row.payload),
49
+ ...(row.metadata !== null && { metadata: JSON.parse(row.metadata) }),
50
+ };
51
+ }
52
+ class SqliteEventStore {
53
+ db;
54
+ pageFirstStmt;
55
+ pageAfterStmt;
56
+ subscribers = new event_runtime_1.SubscriberRegistry();
57
+ constructor(dbPath) {
58
+ this.db = new better_sqlite3_1.default(dbPath);
59
+ try {
60
+ this.db.pragma("journal_mode = WAL");
61
+ this.db.pragma("synchronous = NORMAL");
62
+ (0, schema_1.migrate)(this.db);
63
+ }
64
+ catch (err) {
65
+ this.db.close(); // don't leak the file handle if setup fails on a bad/locked file
66
+ throw err;
67
+ }
68
+ }
69
+ async append(envelope) {
70
+ const stmt = this.db.prepare(`
71
+ INSERT INTO events (id, workflow_id, execution_id, event_type, sequence, timestamp, payload, metadata)
72
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
73
+ `);
74
+ try {
75
+ stmt.run(envelope.id, envelope.workflowId, envelope.executionId, envelope.eventType, envelope.sequence, envelope.timestamp, JSON.stringify(envelope.payload), envelope.metadata !== undefined ? JSON.stringify(envelope.metadata) : null);
76
+ }
77
+ catch (err) {
78
+ if (isDuplicateSequenceViolation(err)) {
79
+ throw new event_runtime_1.DuplicateSequenceError(envelope.executionId, envelope.sequence, err);
80
+ }
81
+ if (isDuplicateIdViolation(err)) {
82
+ throw new event_runtime_1.DuplicateEnvelopeIdError(envelope.id, envelope.executionId, err);
83
+ }
84
+ throw err;
85
+ }
86
+ this.notify(envelope.executionId, envelope);
87
+ }
88
+ /**
89
+ * ADR 0074 D1 — bulk append in ONE transaction. better-sqlite3's `transaction()` wrapper
90
+ * is synchronous and rolls back on throw, which gives the required atomicity directly:
91
+ * a UNIQUE(execution_id, sequence) violation part-way aborts the whole statement group,
92
+ * so no execution is left with a hole.
93
+ *
94
+ * Subscribers fire only after the transaction commits — a subscriber must never observe
95
+ * a batch that could still roll back.
96
+ */
97
+ async appendBatch(envelopes) {
98
+ if (envelopes.length === 0)
99
+ return;
100
+ const stmt = this.db.prepare(`
101
+ INSERT INTO events (id, workflow_id, execution_id, event_type, sequence, timestamp, payload, metadata)
102
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
103
+ `);
104
+ const insertAll = this.db.transaction((batch) => {
105
+ for (const envelope of batch) {
106
+ stmt.run(envelope.id, envelope.workflowId, envelope.executionId, envelope.eventType, envelope.sequence, envelope.timestamp, JSON.stringify(envelope.payload), envelope.metadata !== undefined ? JSON.stringify(envelope.metadata) : null);
107
+ }
108
+ });
109
+ try {
110
+ insertAll(envelopes);
111
+ }
112
+ catch (err) {
113
+ if (isDuplicateSequenceViolation(err) || isDuplicateIdViolation(err)) {
114
+ // SQLite names the constraint, not the row, so locate the offender ourselves —
115
+ // an error that cannot say WHICH slot was taken sends the reader back to the log.
116
+ // Error path only, so the extra query costs nothing on the hot path. The
117
+ // transaction has rolled back, so a slot found occupied now was occupied before.
118
+ throw this.duplicateIn(envelopes, err);
119
+ }
120
+ throw err;
121
+ }
122
+ for (const envelope of envelopes)
123
+ this.notify(envelope.executionId, envelope);
124
+ }
125
+ /**
126
+ * Which envelope in a rejected batch claimed something already taken — a
127
+ * `(executionId, sequence)` slot or an envelope `id`, either because the log already held
128
+ * it or because another envelope in the SAME batch did. Slot first, so a writer that lost
129
+ * a sequence race is told THAT rather than told about the id its own naming convention
130
+ * derived from the sequence it lost.
131
+ */
132
+ duplicateIn(envelopes, cause) {
133
+ const taken = this.db.prepare("SELECT 1 AS one FROM events WHERE execution_id = ? AND sequence = ?");
134
+ const idTaken = this.db.prepare("SELECT 1 AS one FROM events WHERE id = ?");
135
+ const claimedIds = new Set();
136
+ const claimed = new Set();
137
+ for (const e of envelopes) {
138
+ const key = `${e.executionId}\u0000${e.sequence}`;
139
+ if (claimed.has(key) || taken.get(e.executionId, e.sequence) !== undefined) {
140
+ return new event_runtime_1.DuplicateSequenceError(e.executionId, e.sequence, cause);
141
+ }
142
+ if (claimedIds.has(e.id) || idTaken.get(e.id) !== undefined) {
143
+ return new event_runtime_1.DuplicateEnvelopeIdError(e.id, e.executionId, cause);
144
+ }
145
+ claimed.add(key);
146
+ claimedIds.add(e.id);
147
+ }
148
+ return cause instanceof Error ? cause : new Error(String(cause));
149
+ }
150
+ /**
151
+ * SPI 2.2.0 (ADR 0116 amendment) — existence, head and the (always 0: SQLite does not fence)
152
+ * token are checked and the row inserted inside ONE `BEGIN IMMEDIATE` transaction, so a
153
+ * retention delete or another process's append cannot slip between the check and the write.
154
+ */
155
+ async appendIfUnchanged(executionId, event, expect) {
156
+ if (event !== null && (expect === undefined || event.executionId !== executionId)) {
157
+ throw new TypeError("appendIfUnchanged: an event needs `expect` and the matching executionId");
158
+ }
159
+ const run = this.db.transaction(() => {
160
+ const first = this.db
161
+ .prepare("SELECT id, timestamp, incarnation FROM events WHERE execution_id = ? AND sequence = 0")
162
+ .get(executionId);
163
+ if (first === undefined) {
164
+ return { appended: false, reason: "gone", head: null, fence: 0, anchor: null };
165
+ }
166
+ // ADR 0129: the store-assigned incarnation; a pre-migration log keeps the legacy marker.
167
+ const anchor = first.incarnation === null ? (0, event_runtime_1.creationMarker)(first) : String(first.incarnation);
168
+ const tip = this.db
169
+ .prepare("SELECT MAX(sequence) AS tip FROM events WHERE execution_id = ?")
170
+ .get(executionId);
171
+ const head = tip?.tip ?? 0;
172
+ if (event === null)
173
+ return { appended: false, reason: "observed", head, fence: 0, anchor };
174
+ const want = expect;
175
+ if (head !== want.head ||
176
+ want.fence !== 0 ||
177
+ anchor !== want.anchor ||
178
+ event.sequence !== head + 1) {
179
+ return { appended: false, reason: "moved", head, fence: 0, anchor };
180
+ }
181
+ this.db
182
+ .prepare(`INSERT INTO events (id, workflow_id, execution_id, event_type, sequence, timestamp, payload, metadata)
183
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
184
+ .run(event.id, event.workflowId, event.executionId, event.eventType, event.sequence, event.timestamp, JSON.stringify(event.payload), event.metadata !== undefined ? JSON.stringify(event.metadata) : null);
185
+ return { appended: true };
186
+ });
187
+ let result;
188
+ try {
189
+ result = run.immediate();
190
+ }
191
+ catch (err) {
192
+ if (isDuplicateSequenceViolation(err)) {
193
+ throw new event_runtime_1.DuplicateSequenceError(executionId, event?.sequence ?? -1, err);
194
+ }
195
+ if (isDuplicateIdViolation(err)) {
196
+ throw new event_runtime_1.DuplicateEnvelopeIdError(event?.id ?? "", executionId, err);
197
+ }
198
+ throw err;
199
+ }
200
+ if (result.appended && event !== null)
201
+ this.notify(executionId, event);
202
+ return result;
203
+ }
204
+ async read(executionId) {
205
+ const rows = this.db
206
+ .prepare("SELECT * FROM events WHERE execution_id = ? ORDER BY sequence ASC")
207
+ .all(executionId);
208
+ return rows.map(rowToEnvelope);
209
+ }
210
+ async readFrom(executionId, fromSequence) {
211
+ const rows = this.db
212
+ .prepare("SELECT * FROM events WHERE execution_id = ? AND sequence >= ? ORDER BY sequence ASC")
213
+ .all(executionId, fromSequence);
214
+ return rows.map(rowToEnvelope);
215
+ }
216
+ async head(executionId) {
217
+ const row = this.db
218
+ .prepare("SELECT MAX(sequence) AS tip FROM events WHERE execution_id = ?")
219
+ .get(executionId);
220
+ return row?.tip ?? null;
221
+ }
222
+ async readRange(executionId, fromSequence, toSequence) {
223
+ if (toSequence < fromSequence)
224
+ return [];
225
+ const rows = this.db
226
+ .prepare("SELECT * FROM events WHERE execution_id = ? AND sequence >= ? AND sequence <= ? ORDER BY sequence ASC")
227
+ .all(executionId, fromSequence, toSequence);
228
+ return rows.map(rowToEnvelope);
229
+ }
230
+ async copyPrefix(fromExecutionId, toExecutionId, throughSequence, metadata) {
231
+ // Copy the prefix entirely inside SQLite — payload stays as stored TEXT (no
232
+ // JS parse/stringify), id/execution_id are re-stamped, and provenance metadata
233
+ // is merged via json_patch (JSON1). O(P) rows, zero JS serialization.
234
+ this.db
235
+ .prepare(`INSERT INTO events (id, workflow_id, execution_id, event_type, sequence, timestamp, payload, metadata)
236
+ SELECT ? || '-' || sequence, workflow_id, ?, event_type, sequence, timestamp, payload,
237
+ json_patch(COALESCE(metadata, '{}'), json(?))
238
+ FROM events WHERE execution_id = ? AND sequence <= ?`)
239
+ .run(toExecutionId, toExecutionId, JSON.stringify(metadata), fromExecutionId, throughSequence);
240
+ }
241
+ async subscribe(executionId, handler) {
242
+ return this.subscribers.add(executionId, handler);
243
+ }
244
+ async deleteExecution(executionId) {
245
+ this.db.prepare("DELETE FROM events WHERE execution_id = ?").run(executionId);
246
+ }
247
+ async deleteThrough(executionId, through) {
248
+ // ADR 0139 D8 - one statement, sequence 0 included; events above `through` are untouched.
249
+ this.db
250
+ .prepare("DELETE FROM events WHERE execution_id = ? AND sequence <= ?")
251
+ .run(executionId, through);
252
+ }
253
+ async truncatePrefix(executionId, throughSequence) {
254
+ // Preserve sequence 0 (bootstrap anchor); drop (0..throughSequence].
255
+ this.db
256
+ .prepare("DELETE FROM events WHERE execution_id = ? AND sequence > 0 AND sequence <= ?")
257
+ .run(executionId, throughSequence);
258
+ }
259
+ async truncatePrefixKeeping(executionId, throughSequence, keepEventTypes) {
260
+ // ADR 0106 — one statement: sequence 0 and every kept-type event survive in place. The
261
+ // kept types ride as one JSON array parameter, so the statement text never varies.
262
+ this.db
263
+ .prepare("DELETE FROM events WHERE execution_id = ? AND sequence > 0 AND sequence <= ? AND event_type NOT IN (SELECT value FROM json_each(?))")
264
+ .run(executionId, throughSequence, JSON.stringify(keepEventTypes));
265
+ }
266
+ async listExecutions() {
267
+ // The (execution_id, sequence) index makes DISTINCT a cheap index-only scan.
268
+ const rows = this.db
269
+ .prepare("SELECT DISTINCT execution_id FROM events")
270
+ .all();
271
+ return rows.map((r) => r.execution_id);
272
+ }
273
+ async listExecutionsPage(opts) {
274
+ // SP-1 (SPI 1.2.0) — keyset over the same index: lexicographic, bounded memory.
275
+ // Statements prepared lazily once (audit: no per-page re-parse on big scans).
276
+ const limit = opts?.limit ?? 1000;
277
+ this.pageFirstStmt ??= this.db.prepare("SELECT DISTINCT execution_id FROM events ORDER BY execution_id LIMIT ?");
278
+ this.pageAfterStmt ??= this.db.prepare("SELECT DISTINCT execution_id FROM events WHERE execution_id > ? ORDER BY execution_id LIMIT ?");
279
+ const rows = opts?.cursor === undefined
280
+ ? this.pageFirstStmt.all(limit + 1)
281
+ : this.pageAfterStmt.all(opts.cursor, limit + 1);
282
+ const executionIds = rows.slice(0, limit).map((r) => r.execution_id);
283
+ const last = executionIds[executionIds.length - 1];
284
+ return {
285
+ executionIds,
286
+ ...(rows.length > limit && last !== undefined && { nextCursor: last }),
287
+ };
288
+ }
289
+ async close() {
290
+ this.db.close();
291
+ }
292
+ notify(_executionId, event) {
293
+ this.subscribers.emit(event);
294
+ }
295
+ }
296
+ exports.SqliteEventStore = SqliteEventStore;
297
+ //# sourceMappingURL=SqliteEventStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteEventStore.js","sourceRoot":"","sources":["../src/SqliteEventStore.ts"],"names":[],"mappings":";;;;;;AAEA,2DAKiC;AACjC,oEAAsC;AACtC,qCAAmC;AAEnC;;;;;;;;GAQG;AACH,SAAS,4BAA4B,CAAC,GAAY;IAChD,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AAClF,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAAC,GAAY;IAC1C,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;AACpF,CAAC;AAED,SAAS,qBAAqB,CAAC,GAAY;IACzC,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,MAAM,IAAI,GAAI,GAA0B,CAAC,IAAI,CAAC;IAC9C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;AAC1E,CAAC;AAaD,SAAS,aAAa,CAAC,GAAa;IAClC,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,UAAU,EAAE,GAAG,CAAC,WAAW;QAC3B,WAAW,EAAE,GAAG,CAAC,YAAY;QAC7B,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAY;QAC3C,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,IAAI,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAA4B,EAAE,CAAC;KAChG,CAAC;AACJ,CAAC;AAED,MAAa,gBAAgB;IACV,EAAE,CAAoB;IAC/B,aAAa,CAAiC;IAC9C,aAAa,CAAiC;IACrC,WAAW,GAAG,IAAI,kCAAkB,EAAE,CAAC;IAExD,YAAY,MAAc;QACxB,IAAI,CAAC,EAAE,GAAG,IAAI,wBAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;YACrC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;YACvC,IAAA,gBAAO,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,iEAAiE;YAClF,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAuB;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAE1B;;;KAGD,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,IAAI,CAAC,GAAG,CACN,QAAQ,CAAC,EAAE,EACX,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,WAAW,EACpB,QAAQ,CAAC,SAAS,EAClB,QAAQ,CAAC,QAAQ,EACjB,QAAQ,CAAC,SAAS,EAClB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAChC,QAAQ,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAC3E,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,4BAA4B,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtC,MAAM,IAAI,sCAAsB,CAAC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YACjF,CAAC;YACD,IAAI,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChC,MAAM,IAAI,wCAAwB,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;YAC7E,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,WAAW,CAAC,SAAmC;QACnD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAE1B;;;KAGD,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,KAA+B,EAAE,EAAE;YACxE,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;gBAC7B,IAAI,CAAC,GAAG,CACN,QAAQ,CAAC,EAAE,EACX,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,WAAW,EACpB,QAAQ,CAAC,SAAS,EAClB,QAAQ,CAAC,QAAQ,EACjB,QAAQ,CAAC,SAAS,EAClB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAChC,QAAQ,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAC3E,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,CAAC;YACH,SAAS,CAAC,SAAS,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,4BAA4B,CAAC,GAAG,CAAC,IAAI,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrE,+EAA+E;gBAC/E,kFAAkF;gBAClF,yEAAyE;gBACzE,iFAAiF;gBACjF,MAAM,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YACzC,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,SAAS;YAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAChF,CAAC;IAED;;;;;;OAMG;IACK,WAAW,CAAC,SAAmC,EAAE,KAAc;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC3B,qEAAqE,CACtE,CAAC;QACF,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC7B,0CAA0C,CAC3C,CAAC;QACF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,WAAW,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC;YAClD,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC3E,OAAO,IAAI,sCAAsB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YACtE,CAAC;YACD,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC5D,OAAO,IAAI,wCAAwB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YAClE,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,iBAAiB,CACrB,WAAmB,EACnB,KAA2B,EAC3B,MAAuB;QAEvB,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,WAAW,KAAK,WAAW,CAAC,EAAE,CAAC;YAClF,MAAM,IAAI,SAAS,CACjB,yEAAyE,CAC1E,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAA4B,EAAE;YAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE;iBAClB,OAAO,CACN,uFAAuF,CACxF;iBACA,GAAG,CAAC,WAAW,CAAC,CAAC;YACpB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACjF,CAAC;YACD,yFAAyF;YACzF,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,IAAA,8BAAc,EAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC9F,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;iBAChB,OAAO,CACN,gEAAgE,CACjE;iBACA,GAAG,CAAC,WAAW,CAAC,CAAC;YACpB,MAAM,IAAI,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YAC3B,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;YAC3F,MAAM,IAAI,GAAG,MAAwB,CAAC;YACtC,IACE,IAAI,KAAK,IAAI,CAAC,IAAI;gBAClB,IAAI,CAAC,KAAK,KAAK,CAAC;gBAChB,MAAM,KAAK,IAAI,CAAC,MAAM;gBACtB,KAAK,CAAC,QAAQ,KAAK,IAAI,GAAG,CAAC,EAC3B,CAAC;gBACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;YACtE,CAAC;YACD,IAAI,CAAC,EAAE;iBACJ,OAAO,CACN;2CACiC,CAClC;iBACA,GAAG,CACF,KAAK,CAAC,EAAE,EACR,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,WAAW,EACjB,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,SAAS,EACf,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAC7B,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CACrE,CAAC;YACJ,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,IAAI,MAA+B,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,4BAA4B,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtC,MAAM,IAAI,sCAAsB,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAC5E,CAAC;YACD,IAAI,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChC,MAAM,IAAI,wCAAwB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;YACxE,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,IAAI,KAAK,KAAK,IAAI;YAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QACvE,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,WAAmB;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CACN,mEAAmE,CACpE;aACA,GAAG,CAAC,WAAW,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,WAAmB,EAAE,YAAoB;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CACN,qFAAqF,CACtF;aACA,GAAG,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,WAAmB;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CACN,gEAAgE,CACjE;aACA,GAAG,CAAC,WAAW,CAAC,CAAC;QACpB,OAAO,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,SAAS,CACb,WAAmB,EACnB,YAAoB,EACpB,UAAkB;QAElB,IAAI,UAAU,GAAG,YAAY;YAAE,OAAO,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CACN,uGAAuG,CACxG;aACA,GAAG,CAAC,WAAW,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,UAAU,CACd,eAAuB,EACvB,aAAqB,EACrB,eAAuB,EACvB,QAAiC;QAEjC,4EAA4E;QAC5E,+EAA+E;QAC/E,sEAAsE;QACtE,IAAI,CAAC,EAAE;aACJ,OAAO,CACN;;;8DAGsD,CACvD;aACA,GAAG,CACF,aAAa,EACb,aAAa,EACb,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EACxB,eAAe,EACf,eAAe,CAChB,CAAC;IACN,CAAC;IAED,KAAK,CAAC,SAAS,CACb,WAAmB,EACnB,OAAuC;QAEvC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,WAAmB;QACvC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAW,2CAA2C,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,WAAmB,EAAE,OAAe;QACtD,0FAA0F;QAC1F,IAAI,CAAC,EAAE;aACJ,OAAO,CAAmB,6DAA6D,CAAC;aACxF,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,WAAmB,EAAE,eAAuB;QAC/D,qEAAqE;QACrE,IAAI,CAAC,EAAE;aACJ,OAAO,CACN,8EAA8E,CAC/E;aACA,GAAG,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,qBAAqB,CACzB,WAAmB,EACnB,eAAuB,EACvB,cAAiC;QAEjC,uFAAuF;QACvF,mFAAmF;QACnF,IAAI,CAAC,EAAE;aACJ,OAAO,CACN,qIAAqI,CACtI;aACA,GAAG,CAAC,WAAW,EAAE,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,6EAA6E;QAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CAA+B,0CAA0C,CAAC;aACjF,GAAG,EAAE,CAAC;QACT,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,IAGxB;QACC,gFAAgF;QAChF,8EAA8E;QAC9E,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC;QAClC,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC,EAAE,CAAC,OAAO,CACpC,wEAAwE,CACzE,CAAC;QACF,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC,EAAE,CAAC,OAAO,CACpC,+FAA+F,CAChG,CAAC;QACF,MAAM,IAAI,GACR,IAAI,EAAE,MAAM,KAAK,SAAS;YACxB,CAAC,CAAE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAgC;YACnE,CAAC,CAAE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,CAAC,CAAgC,CAAC;QACrF,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACrE,MAAM,IAAI,GAAG,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACnD,OAAO;YACL,YAAY;YACZ,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,IAAI,IAAI,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;SACvE,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;IAEO,MAAM,CAAC,YAAoB,EAAE,KAAoB;QACvD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;CACF;AA5VD,4CA4VC"}
@@ -0,0 +1,25 @@
1
+ import { type AcquireOptions, type Lease, type LeaseStore } from "@loomwork/event-runtime";
2
+ /**
3
+ * SQLite {@link LeaseStore} (#380). Single-host by nature — better-sqlite3 is synchronous and
4
+ * the file is the boundary — which is the right scope for it: leases answer "which of my nodes
5
+ * owns this", and a single-host deployment has one. Multi-node wants Postgres.
6
+ */
7
+ export declare class SqliteLeaseStore implements LeaseStore {
8
+ readonly scope: "host";
9
+ private readonly db;
10
+ constructor(dbPath: string);
11
+ acquire(executionId: string, owner: string, ttlMs: number, now?: number, options?: AcquireOptions): Promise<Lease | null>;
12
+ private acquireStmt;
13
+ private acquireOne;
14
+ /** Many leases in one transaction (SPI 2.2.0): per id exactly {@link acquire}, atomically. `steal` is rejected. */
15
+ acquireMany(executionIds: readonly string[], owner: string, ttlMs: number, now?: number, options?: AcquireOptions): Promise<Lease[]>;
16
+ /** The HELD set: ids whose lease `owner` still holds, extended. Never resurrects a missing row. */
17
+ renewMany(executionIds: readonly string[], owner: string, ttlMs: number, now?: number): Promise<string[]>;
18
+ releaseMany(executionIds: readonly string[], owner: string): Promise<void>;
19
+ renew(executionId: string, owner: string, ttlMs: number, now?: number): Promise<boolean>;
20
+ expired(now?: number, limit?: number): Promise<Lease[]>;
21
+ get(executionId: string): Promise<Lease | null>;
22
+ release(executionId: string, owner: string): Promise<void>;
23
+ close(): Promise<void>;
24
+ }
25
+ //# sourceMappingURL=SqliteLeaseStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteLeaseStore.d.ts","sourceRoot":"","sources":["../src/SqliteLeaseStore.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,KAAK,EACV,KAAK,UAAU,EAIhB,MAAM,yBAAyB,CAAC;AAgBjC;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IACjD,QAAQ,CAAC,KAAK,EAAG,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;gBAE3B,MAAM,EAAE,MAAM;IAOpB,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,SAAa,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IASxB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,UAAU;IAalB,mHAAmH;IAC7G,WAAW,CACf,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,SAAa,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,KAAK,EAAE,CAAC;IAenB,mGAAmG;IAC7F,SAAS,CACb,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,SAAa,GACf,OAAO,CAAC,MAAM,EAAE,CAAC;IAWd,WAAW,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU1E,KAAK,CACT,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,SAAa,GACf,OAAO,CAAC,OAAO,CAAC;IAUb,OAAO,CAAC,GAAG,SAAa,EAAE,KAAK,SAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IASxD,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAO/C,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
@@ -0,0 +1,107 @@
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.SqliteLeaseStore = void 0;
7
+ const event_runtime_1 = require("@loomwork/event-runtime");
8
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
9
+ const schema_1 = require("./schema");
10
+ const rowToLease = (r) => ({
11
+ executionId: r.execution_id,
12
+ owner: r.owner,
13
+ expiresAt: r.expires_at,
14
+ });
15
+ /**
16
+ * SQLite {@link LeaseStore} (#380). Single-host by nature — better-sqlite3 is synchronous and
17
+ * the file is the boundary — which is the right scope for it: leases answer "which of my nodes
18
+ * owns this", and a single-host deployment has one. Multi-node wants Postgres.
19
+ */
20
+ class SqliteLeaseStore {
21
+ scope = "host";
22
+ db;
23
+ constructor(dbPath) {
24
+ this.db = new better_sqlite3_1.default(dbPath);
25
+ this.db.pragma("journal_mode = WAL");
26
+ this.db.pragma("synchronous = NORMAL");
27
+ (0, schema_1.migrate)(this.db);
28
+ }
29
+ async acquire(executionId, owner, ttlMs, now = Date.now(), options) {
30
+ (0, event_runtime_1.assertTtl)(ttlMs);
31
+ const steal = (0, event_runtime_1.assertSteal)(options);
32
+ return this.acquireOne(this.acquireStmt(), executionId, owner, ttlMs, now, steal);
33
+ }
34
+ // One statement, so two processes on the same file cannot both win: the insert takes a
35
+ // free row, and the conflict clause takes it only when it is ours, already expired, or the
36
+ // caller holds a steal proof (validated by `acquire` before this runs; never in a batch).
37
+ acquireStmt() {
38
+ return this.db.prepare(`INSERT INTO leases (execution_id, owner, expires_at) VALUES (?, ?, ?)
39
+ ON CONFLICT(execution_id) DO UPDATE SET owner = excluded.owner, expires_at = excluded.expires_at
40
+ WHERE leases.owner = ? OR leases.expires_at <= ? OR ? = 1`);
41
+ }
42
+ acquireOne(stmt, executionId, owner, ttlMs, now, steal) {
43
+ const expiresAt = now + ttlMs;
44
+ const info = stmt.run(executionId, owner, expiresAt, owner, now, steal ? 1 : 0);
45
+ return info.changes > 0 ? { executionId, owner, expiresAt } : null;
46
+ }
47
+ /** Many leases in one transaction (SPI 2.2.0): per id exactly {@link acquire}, atomically. `steal` is rejected. */
48
+ async acquireMany(executionIds, owner, ttlMs, now = Date.now(), options) {
49
+ (0, event_runtime_1.assertTtl)(ttlMs);
50
+ (0, event_runtime_1.assertNoBatchSteal)(options);
51
+ const ids = [...new Set(executionIds)];
52
+ const stmt = this.acquireStmt();
53
+ return this.db.transaction(() => {
54
+ const out = [];
55
+ for (const id of ids) {
56
+ const lease = this.acquireOne(stmt, id, owner, ttlMs, now, false);
57
+ if (lease !== null)
58
+ out.push(lease);
59
+ }
60
+ return out;
61
+ })();
62
+ }
63
+ /** The HELD set: ids whose lease `owner` still holds, extended. Never resurrects a missing row. */
64
+ async renewMany(executionIds, owner, ttlMs, now = Date.now()) {
65
+ (0, event_runtime_1.assertTtl)(ttlMs);
66
+ const ids = [...new Set(executionIds)];
67
+ const stmt = this.db.prepare("UPDATE leases SET expires_at = ? WHERE execution_id = ? AND owner = ?");
68
+ return this.db.transaction(() => ids.filter((id) => stmt.run(now + ttlMs, id, owner).changes > 0))();
69
+ }
70
+ async releaseMany(executionIds, owner) {
71
+ const ids = [...new Set(executionIds)];
72
+ const stmt = this.db.prepare("DELETE FROM leases WHERE execution_id = ? AND owner = ?");
73
+ this.db.transaction(() => {
74
+ for (const id of ids)
75
+ stmt.run(id, owner);
76
+ })();
77
+ }
78
+ async renew(executionId, owner, ttlMs, now = Date.now()) {
79
+ (0, event_runtime_1.assertTtl)(ttlMs);
80
+ const info = this.db
81
+ .prepare("UPDATE leases SET expires_at = ? WHERE execution_id = ? AND owner = ?")
82
+ .run(now + ttlMs, executionId, owner);
83
+ return info.changes > 0;
84
+ }
85
+ async expired(now = Date.now(), limit = 100) {
86
+ const rows = this.db
87
+ .prepare("SELECT * FROM leases WHERE expires_at <= ? ORDER BY expires_at ASC LIMIT ?")
88
+ .all(now, limit);
89
+ return rows.map(rowToLease);
90
+ }
91
+ async get(executionId) {
92
+ const row = this.db
93
+ .prepare("SELECT * FROM leases WHERE execution_id = ?")
94
+ .get(executionId);
95
+ return row ? rowToLease(row) : null;
96
+ }
97
+ async release(executionId, owner) {
98
+ this.db
99
+ .prepare("DELETE FROM leases WHERE execution_id = ? AND owner = ?")
100
+ .run(executionId, owner);
101
+ }
102
+ async close() {
103
+ this.db.close();
104
+ }
105
+ }
106
+ exports.SqliteLeaseStore = SqliteLeaseStore;
107
+ //# sourceMappingURL=SqliteLeaseStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteLeaseStore.js","sourceRoot":"","sources":["../src/SqliteLeaseStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { MemoryEntry, MemorySearchOptions, MemoryStore } from "@loomwork/memory";
2
+ /**
3
+ * SQLite-backed {@link MemoryStore} — the zero-server durable agent memory. Point it
4
+ * at the same DB file as the SqliteEventStore so one backup captures events + memory.
5
+ * Own idempotent migration, same pattern as SqliteTimerStore/SqliteDurableStateStore.
6
+ *
7
+ * A key names an ordered list, so the table needs an insertion key of its own: entries
8
+ * routinely share a timestamp and `ORDER BY timestamp` would reshuffle a conversation.
9
+ * `AUTOINCREMENT` (not bare rowid) makes the monotonicity a schema-level guarantee
10
+ * rather than an argument about rowid reuse after a `clear`.
11
+ *
12
+ * `replace` is one transaction, which is the whole reason it is on the SPI: MemoryRuntime
13
+ * rewrites a list on every TTL/maxEntries eviction, and a crash between the delete and the
14
+ * re-insert would make routine eviction the thing that loses the scope.
15
+ *
16
+ * `remove` (ADR 0091) is one indexed `DELETE` matching `json_extract(entry, '$.id')` — the
17
+ * id is read from the stored JSON rather than duplicated into a column, so nothing can
18
+ * drift between what `read` returns and what a delete matches, and a database written
19
+ * before this shipped needs no migration.
20
+ *
21
+ * `search` rides an FTS5 index over `searchableText(entry)`, held in an EXTERNAL-CONTENT
22
+ * table so the text is indexed but not stored twice, and synchronised by triggers so a
23
+ * `clear`/`replace` cannot leave the index describing entries that no longer exist. FTS5's
24
+ * `rank` is a BM25 score where LOWER is better, hence `ORDER BY rank` ascending.
25
+ */
26
+ export declare class SqliteMemoryStore implements MemoryStore {
27
+ private readonly db;
28
+ private readonly appendStmt;
29
+ private readonly readStmt;
30
+ private readonly clearStmt;
31
+ private readonly removeStmt;
32
+ private readonly searchStmt;
33
+ private readonly replaceTxn;
34
+ constructor(dbPath: string);
35
+ append(key: string, entry: MemoryEntry): Promise<void>;
36
+ read(key: string): Promise<MemoryEntry[]>;
37
+ clear(key: string): Promise<void>;
38
+ replace(key: string, entries: readonly MemoryEntry[]): Promise<void>;
39
+ /**
40
+ * Delete by entry id. The FTS index needs no special handling: rows are deleted, so the
41
+ * existing AFTER DELETE trigger takes them out of `memory_fts` — which is the reason
42
+ * this is a row delete and not a rewrite of the `entry` JSON.
43
+ *
44
+ * An entry with no id has `json_extract(...) IS NULL`, and `NULL IN (...)` is NULL —
45
+ * never true — so anonymous entries cannot be removed by any list of ids, including one
46
+ * containing the strings "null" or "undefined".
47
+ */
48
+ remove(key: string, ids: readonly string[]): Promise<void>;
49
+ search(key: string, query: string, opts?: MemorySearchOptions): Promise<MemoryEntry[]>;
50
+ close(): Promise<void>;
51
+ }
52
+ //# sourceMappingURL=SqliteMemoryStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteMemoryStore.d.ts","sourceRoot":"","sources":["../src/SqliteMemoryStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQtF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,iBAAkB,YAAW,WAAW;IACnD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyD;gBAExE,MAAM,EAAE,MAAM;IAmEpB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAKzC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1E;;;;;;;;OAQG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1D,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAiB1F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}