@rotorsoft/act-sqlite 1.22.4 → 1.22.6

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.
@@ -102,11 +102,14 @@ export declare function streamPatternToGlob(input: string): string;
102
102
  * Provides persistent event storage using SQLite via `@libsql/client`.
103
103
  * All write operations use transactions for ACID guarantees.
104
104
  *
105
- * SQLite is a single-writer store: concurrent write transactions raise
106
- * `SQLITE_BUSY` rather than serializing transparently, so it does not
107
- * support competing consumers the way PostgreSQL's `FOR UPDATE SKIP LOCKED`
108
- * does (the TCK leaves `concurrent_claim` off for this reason). The intended
109
- * model is a single drain worker per database file.
105
+ * SQLite is a single-writer store. The store runs its statements and
106
+ * transactions one at a time on its one connection, so concurrent calls
107
+ * from one process wait their turn. A second connection to the same file
108
+ * (another process, another `SqliteStore`) still fails with `SQLITE_BUSY`,
109
+ * so it does not support competing consumers the way PostgreSQL's
110
+ * `FOR UPDATE SKIP LOCKED` does (the TCK leaves `concurrent_claim` off for
111
+ * this reason). The intended model is one store, and one drain worker, per
112
+ * database file.
110
113
  *
111
114
  * **`Store.notify` is intentionally not implemented.** The notify hook is
112
115
  * a cross-process wake-up signal that lets a horizontally-scaled Act
@@ -132,6 +135,10 @@ export declare class SqliteStore implements Store {
132
135
  private _parse_pii_from_read;
133
136
  seed(): Promise<void>;
134
137
  drop(): Promise<void>;
138
+ private _turn;
139
+ private _serial;
140
+ private _exec;
141
+ private _begin;
135
142
  private _tx;
136
143
  dispose(): Promise<void>;
137
144
  commit<E extends Schemas>(stream: string, msgs: Message<E, keyof E>[], meta: EventMeta, expectedVersion?: number): Promise<Committed<E, keyof E>[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/sqlite-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/sqlite-store.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -135,8 +135,8 @@ var SqliteStore = class {
135
135
  return parsed;
136
136
  }
137
137
  async seed() {
138
- await this.client.execute("PRAGMA journal_mode=WAL");
139
- await this.client.execute(`
138
+ await this._exec("PRAGMA journal_mode=WAL");
139
+ await this._exec(`
140
140
  CREATE TABLE IF NOT EXISTS events (
141
141
  id INTEGER PRIMARY KEY AUTOINCREMENT,
142
142
  stream TEXT NOT NULL,
@@ -150,22 +150,22 @@ var SqliteStore = class {
150
150
  )
151
151
  `);
152
152
  try {
153
- await this.client.execute("ALTER TABLE events ADD COLUMN pii TEXT");
153
+ await this._exec("ALTER TABLE events ADD COLUMN pii TEXT");
154
154
  } catch {
155
155
  }
156
- await this.client.execute(
156
+ await this._exec(
157
157
  "CREATE INDEX IF NOT EXISTS idx_events_stream ON events(stream)"
158
158
  );
159
- await this.client.execute(
159
+ await this._exec(
160
160
  "CREATE INDEX IF NOT EXISTS idx_events_name ON events(name)"
161
161
  );
162
- await this.client.execute(
162
+ await this._exec(
163
163
  "CREATE INDEX IF NOT EXISTS idx_events_snapshot ON events(stream, id) WHERE name = '__snapshot__'"
164
164
  );
165
- await this.client.execute(
165
+ await this._exec(
166
166
  "CREATE INDEX IF NOT EXISTS idx_events_stream_id ON events(stream, id) WHERE name <> '__snapshot__'"
167
167
  );
168
- await this.client.execute(`
168
+ await this._exec(`
169
169
  CREATE TABLE IF NOT EXISTS streams (
170
170
  stream TEXT PRIMARY KEY,
171
171
  source TEXT,
@@ -182,40 +182,36 @@ var SqliteStore = class {
182
182
  )
183
183
  `);
184
184
  try {
185
- await this.client.execute(
185
+ await this._exec(
186
186
  "ALTER TABLE streams ADD COLUMN priority INTEGER NOT NULL DEFAULT 0"
187
187
  );
188
188
  } catch {
189
189
  }
190
190
  try {
191
- await this.client.execute(
192
- "ALTER TABLE streams ADD COLUMN deferred_at TEXT"
193
- );
191
+ await this._exec("ALTER TABLE streams ADD COLUMN deferred_at TEXT");
194
192
  } catch {
195
193
  }
196
194
  try {
197
- await this.client.execute(
195
+ await this._exec(
198
196
  "ALTER TABLE streams ADD COLUMN lane TEXT NOT NULL DEFAULT 'default'"
199
197
  );
200
198
  } catch {
201
199
  }
202
200
  try {
203
- await this.client.execute(
204
- "ALTER TABLE streams ADD COLUMN correlated_at INTEGER"
205
- );
201
+ await this._exec("ALTER TABLE streams ADD COLUMN correlated_at INTEGER");
206
202
  } catch {
207
203
  }
208
- await this.client.execute(
204
+ await this._exec(
209
205
  `UPDATE streams
210
206
  SET correlated_at = (SELECT COALESCE(MAX(id), -1) FROM events)
211
207
  WHERE correlated_at IS NULL`
212
208
  );
213
- const correlated_columns = await this.client.execute(
209
+ const correlated_columns = await this._exec(
214
210
  "PRAGMA table_info(correlated)"
215
211
  );
216
212
  const legacy = correlated_columns.rows.some((r) => r.name === "id");
217
213
  if (legacy) {
218
- await this.client.execute(`
214
+ await this._exec(`
219
215
  CREATE TABLE IF NOT EXISTS correlated_keyed (
220
216
  key TEXT PRIMARY KEY,
221
217
  at INTEGER NOT NULL DEFAULT -1,
@@ -223,15 +219,13 @@ var SqliteStore = class {
223
219
  leased_until INTEGER
224
220
  )
225
221
  `);
226
- await this.client.execute(
222
+ await this._exec(
227
223
  "INSERT OR IGNORE INTO correlated_keyed (key, at) SELECT '', at FROM correlated WHERE id = 0"
228
224
  );
229
- await this.client.execute("DROP TABLE correlated");
230
- await this.client.execute(
231
- "ALTER TABLE correlated_keyed RENAME TO correlated"
232
- );
225
+ await this._exec("DROP TABLE correlated");
226
+ await this._exec("ALTER TABLE correlated_keyed RENAME TO correlated");
233
227
  } else {
234
- await this.client.execute(`
228
+ await this._exec(`
235
229
  CREATE TABLE IF NOT EXISTS correlated (
236
230
  key TEXT PRIMARY KEY,
237
231
  at INTEGER NOT NULL DEFAULT -1,
@@ -240,104 +234,127 @@ var SqliteStore = class {
240
234
  )
241
235
  `);
242
236
  }
243
- await this.client.execute(
244
- "INSERT OR IGNORE INTO correlated (key) VALUES ('')"
245
- );
246
- await this.client.execute(
237
+ await this._exec("INSERT OR IGNORE INTO correlated (key) VALUES ('')");
238
+ await this._exec(
247
239
  "CREATE INDEX IF NOT EXISTS idx_streams_claim ON streams(blocked, priority DESC, at)"
248
240
  );
249
- await this.client.execute(
241
+ await this._exec(
250
242
  "CREATE INDEX IF NOT EXISTS idx_streams_correlated_at ON streams(lane, priority DESC, at) WHERE blocked = 0 AND at < correlated_at"
251
243
  );
252
- await this.client.execute(
244
+ await this._exec(
253
245
  "CREATE INDEX IF NOT EXISTS idx_streams_correlated_at_any_lane ON streams(priority DESC, at) WHERE blocked = 0 AND at < correlated_at"
254
246
  );
255
- await this.client.execute(
247
+ await this._exec(
256
248
  "CREATE INDEX IF NOT EXISTS idx_streams_lane ON streams(lane)"
257
249
  );
258
250
  }
259
251
  async drop() {
260
- await this.client.execute("DROP TABLE IF EXISTS events");
261
- await this.client.execute("DROP TABLE IF EXISTS streams");
262
- await this.client.execute("DROP TABLE IF EXISTS correlated");
252
+ await this._exec("DROP TABLE IF EXISTS events");
253
+ await this._exec("DROP TABLE IF EXISTS streams");
254
+ await this._exec("DROP TABLE IF EXISTS correlated");
263
255
  }
264
- // One write transaction: any failure rolls back and throws a {@link StoreError} naming the operation.
265
- async _tx(operation, work) {
266
- const tx = await this.client.transaction("write");
267
- try {
268
- const result = await work(tx);
269
- await tx.commit();
270
- return result;
271
- } catch (e) {
272
- await tx.rollback();
256
+ // One connection serves the whole store, and it can't run a statement
257
+ // while a write transaction is open on it: a second one fails at once
258
+ // (`TRANSACTION_ACTIVE` in memory, `SQLITE_BUSY` on a file) instead of
259
+ // waiting. So every statement and every transaction takes its turn here.
260
+ // A turn covers only the database work, never a caller's callback, so a
261
+ // callback that writes to the store can't wait on itself.
262
+ _turn = Promise.resolve();
263
+ _serial(work) {
264
+ const run = this._turn.then(work);
265
+ this._turn = run.catch(() => void 0);
266
+ return run;
267
+ }
268
+ // One statement, in its turn.
269
+ _exec(stmt) {
270
+ return this._serial(() => this.client.execute(stmt));
271
+ }
272
+ // Opens a write transaction: a BEGIN that fails is a {@link StoreError} too.
273
+ _begin(operation) {
274
+ return this.client.transaction("write").catch((e) => {
273
275
  throw new import_act.StoreError(operation, { cause: e });
274
- }
276
+ });
277
+ }
278
+ // One write transaction, in its turn: any failure rolls back and throws a {@link StoreError} naming the operation.
279
+ _tx(operation, work) {
280
+ return this._serial(async () => {
281
+ const tx = await this._begin(operation);
282
+ try {
283
+ const result = await work(tx);
284
+ await tx.commit();
285
+ return result;
286
+ } catch (e) {
287
+ await tx.rollback();
288
+ throw new import_act.StoreError(operation, { cause: e });
289
+ }
290
+ });
275
291
  }
276
292
  async dispose() {
277
- await Promise.resolve();
278
- this.client.close();
293
+ await this._serial(async () => this.client.close());
279
294
  }
280
295
  // --- commit: transaction + optimistic concurrency ---
281
296
  async commit(stream, msgs, meta, expectedVersion) {
282
- const tx = await this.client.transaction("write");
283
- let current_version = -1;
284
- try {
285
- const version_row = await tx.execute({
286
- sql: "SELECT COALESCE(MAX(version), -1) as v FROM events WHERE stream = ?",
287
- args: [stream]
288
- });
289
- current_version = Number(version_row.rows[0].v);
290
- if (typeof expectedVersion === "number" && current_version !== expectedVersion) {
291
- throw new import_act.ConcurrencyError(
292
- stream,
293
- current_version,
294
- msgs,
295
- expectedVersion
296
- );
297
- }
298
- const now = (/* @__PURE__ */ new Date()).toISOString();
299
- const committed = [];
300
- let version = current_version + 1;
301
- for (const { name, data, pii } of msgs) {
302
- const pii_for_write = await this._stringify_pii_for_write(pii);
303
- const result = await tx.execute({
304
- sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
305
- args: [
297
+ return this._serial(async () => {
298
+ const tx = await this._begin("commit");
299
+ let current_version = -1;
300
+ try {
301
+ const version_row = await tx.execute({
302
+ sql: "SELECT COALESCE(MAX(version), -1) as v FROM events WHERE stream = ?",
303
+ args: [stream]
304
+ });
305
+ current_version = Number(version_row.rows[0].v);
306
+ if (typeof expectedVersion === "number" && current_version !== expectedVersion) {
307
+ throw new import_act.ConcurrencyError(
308
+ stream,
309
+ current_version,
310
+ msgs,
311
+ expectedVersion
312
+ );
313
+ }
314
+ const now = (/* @__PURE__ */ new Date()).toISOString();
315
+ const committed = [];
316
+ let version = current_version + 1;
317
+ for (const { name, data, pii } of msgs) {
318
+ const pii_for_write = await this._stringify_pii_for_write(pii);
319
+ const result = await tx.execute({
320
+ sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
321
+ args: [
322
+ stream,
323
+ version,
324
+ name,
325
+ JSON.stringify(data),
326
+ JSON.stringify(meta),
327
+ now,
328
+ pii_for_write
329
+ ]
330
+ });
331
+ committed.push({
332
+ id: Number(result.lastInsertRowid),
306
333
  stream,
307
334
  version,
335
+ created: new Date(now),
308
336
  name,
309
- JSON.stringify(data),
310
- JSON.stringify(meta),
311
- now,
312
- pii_for_write
313
- ]
314
- });
315
- committed.push({
316
- id: Number(result.lastInsertRowid),
317
- stream,
318
- version,
319
- created: new Date(now),
320
- name,
321
- data,
322
- meta,
323
- ...pii == null ? {} : { pii }
324
- });
325
- version++;
337
+ data,
338
+ meta,
339
+ ...pii == null ? {} : { pii }
340
+ });
341
+ version++;
342
+ }
343
+ await tx.commit();
344
+ return committed;
345
+ } catch (e) {
346
+ await tx.rollback();
347
+ if (e instanceof import_act.ConcurrencyError) throw e;
348
+ if (is_unique_violation(e))
349
+ throw new import_act.ConcurrencyError(
350
+ stream,
351
+ current_version,
352
+ msgs,
353
+ expectedVersion ?? -1
354
+ );
355
+ throw new import_act.StoreError("commit", { cause: e });
326
356
  }
327
- await tx.commit();
328
- return committed;
329
- } catch (e) {
330
- await tx.rollback();
331
- if (e instanceof import_act.ConcurrencyError) throw e;
332
- if (is_unique_violation(e))
333
- throw new import_act.ConcurrencyError(
334
- stream,
335
- current_version,
336
- msgs,
337
- expectedVersion ?? -1
338
- );
339
- throw new import_act.StoreError("commit", { cause: e });
340
- }
357
+ });
341
358
  }
342
359
  // --- query: read-only, no transaction needed ---
343
360
  async query(callback, query) {
@@ -387,7 +404,7 @@ var SqliteStore = class {
387
404
  sql += " LIMIT ?";
388
405
  args.push(query.limit);
389
406
  }
390
- const result = await this.client.execute({ sql, args });
407
+ const result = await this._exec({ sql, args });
391
408
  let count = 0;
392
409
  for (const row of result.rows) {
393
410
  const pii_value = query?.with_pii === false ? null : await this._parse_pii_from_read(row.pii);
@@ -758,8 +775,8 @@ var SqliteStore = class {
758
775
  sql += " ORDER BY stream LIMIT ?";
759
776
  args.push(limit);
760
777
  const [streamsResult, maxResult] = await Promise.all([
761
- this.client.execute({ sql, args }),
762
- this.client.execute("SELECT COALESCE(MAX(id), -1) AS m FROM events")
778
+ this._exec({ sql, args }),
779
+ this._exec("SELECT COALESCE(MAX(id), -1) AS m FROM events")
763
780
  ]);
764
781
  let count = 0;
765
782
  for (const row of streamsResult.rows) {
@@ -891,8 +908,8 @@ var SqliteStore = class {
891
908
  )` : null;
892
909
  const tail_args = limit !== void 0 ? [...args, ...args, limit] : [...args, ...args];
893
910
  const [headRes, tailRes] = await Promise.all([
894
- this.client.execute({ sql: head_sql, args: head_args }),
895
- tail_sql ? this.client.execute({ sql: tail_sql, args: tail_args }) : null
911
+ this._exec({ sql: head_sql, args: head_args }),
912
+ tail_sql ? this._exec({ sql: tail_sql, args: tail_args }) : null
896
913
  ]);
897
914
  const out = /* @__PURE__ */ new Map();
898
915
  for (const row of headRes.rows) {
@@ -954,7 +971,7 @@ var SqliteStore = class {
954
971
  ORDER BY h.stream
955
972
  `;
956
973
  const full_args = limit !== void 0 ? [...args, limit] : args;
957
- const res = await this.client.execute({ sql, args: full_args });
974
+ const res = await this._exec({ sql, args: full_args });
958
975
  const out = /* @__PURE__ */ new Map();
959
976
  for (const row of res.rows) {
960
977
  const r = row;
@@ -1064,33 +1081,35 @@ var SqliteStore = class {
1064
1081
  * from the source.
1065
1082
  */
1066
1083
  async restore(driver) {
1067
- const tx = await this.client.transaction("write");
1068
- try {
1069
- await tx.execute("DELETE FROM events");
1070
- await tx.execute("DELETE FROM streams");
1071
- await tx.execute("UPDATE correlated SET at = -1");
1072
- await tx.execute("DELETE FROM sqlite_sequence WHERE name = 'events'");
1073
- await driver(async (event) => {
1074
- const pii_for_write = await this._stringify_pii_for_write(event.pii);
1075
- const ins = await tx.execute({
1076
- sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
1077
- args: [
1078
- event.stream,
1079
- event.version,
1080
- event.name,
1081
- JSON.stringify(event.data),
1082
- JSON.stringify(event.meta),
1083
- event.created.toISOString(),
1084
- pii_for_write
1085
- ]
1084
+ return this._serial(async () => {
1085
+ const tx = await this._begin("restore");
1086
+ try {
1087
+ await tx.execute("DELETE FROM events");
1088
+ await tx.execute("DELETE FROM streams");
1089
+ await tx.execute("UPDATE correlated SET at = -1");
1090
+ await tx.execute("DELETE FROM sqlite_sequence WHERE name = 'events'");
1091
+ await driver(async (event) => {
1092
+ const pii_for_write = await this._stringify_pii_for_write(event.pii);
1093
+ const ins = await tx.execute({
1094
+ sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
1095
+ args: [
1096
+ event.stream,
1097
+ event.version,
1098
+ event.name,
1099
+ JSON.stringify(event.data),
1100
+ JSON.stringify(event.meta),
1101
+ event.created.toISOString(),
1102
+ pii_for_write
1103
+ ]
1104
+ });
1105
+ return Number(ins.lastInsertRowid);
1086
1106
  });
1087
- return Number(ins.lastInsertRowid);
1088
- });
1089
- await tx.commit();
1090
- } catch (error) {
1091
- await tx.rollback();
1092
- throw error;
1093
- }
1107
+ await tx.commit();
1108
+ } catch (error) {
1109
+ await tx.rollback();
1110
+ throw error;
1111
+ }
1112
+ });
1094
1113
  }
1095
1114
  /**
1096
1115
  * Wipe the sensitive-data payload for every event on the stream — the
@@ -1110,7 +1129,7 @@ var SqliteStore = class {
1110
1129
  * @returns Count of events whose `pii` was set to `NULL`
1111
1130
  */
1112
1131
  async forget_pii(stream) {
1113
- const r = await this.client.execute({
1132
+ const r = await this._exec({
1114
1133
  sql: "UPDATE events SET pii = NULL WHERE stream = ? AND pii IS NOT NULL",
1115
1134
  args: [stream]
1116
1135
  });