@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.
- package/README.md +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/sqlite-store.d.ts +12 -5
- package/dist/@types/sqlite-store.d.ts.map +1 -1
- package/dist/index.cjs +154 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +157 -136
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// src/sqlite-store.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createClient
|
|
4
|
+
} from "@libsql/client";
|
|
3
5
|
import {
|
|
4
6
|
ConcurrencyError,
|
|
5
7
|
is_literal_source,
|
|
@@ -117,8 +119,8 @@ var SqliteStore = class {
|
|
|
117
119
|
return parsed;
|
|
118
120
|
}
|
|
119
121
|
async seed() {
|
|
120
|
-
await this.
|
|
121
|
-
await this.
|
|
122
|
+
await this._exec("PRAGMA journal_mode=WAL");
|
|
123
|
+
await this._exec(`
|
|
122
124
|
CREATE TABLE IF NOT EXISTS events (
|
|
123
125
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
124
126
|
stream TEXT NOT NULL,
|
|
@@ -132,22 +134,22 @@ var SqliteStore = class {
|
|
|
132
134
|
)
|
|
133
135
|
`);
|
|
134
136
|
try {
|
|
135
|
-
await this.
|
|
137
|
+
await this._exec("ALTER TABLE events ADD COLUMN pii TEXT");
|
|
136
138
|
} catch {
|
|
137
139
|
}
|
|
138
|
-
await this.
|
|
140
|
+
await this._exec(
|
|
139
141
|
"CREATE INDEX IF NOT EXISTS idx_events_stream ON events(stream)"
|
|
140
142
|
);
|
|
141
|
-
await this.
|
|
143
|
+
await this._exec(
|
|
142
144
|
"CREATE INDEX IF NOT EXISTS idx_events_name ON events(name)"
|
|
143
145
|
);
|
|
144
|
-
await this.
|
|
146
|
+
await this._exec(
|
|
145
147
|
"CREATE INDEX IF NOT EXISTS idx_events_snapshot ON events(stream, id) WHERE name = '__snapshot__'"
|
|
146
148
|
);
|
|
147
|
-
await this.
|
|
149
|
+
await this._exec(
|
|
148
150
|
"CREATE INDEX IF NOT EXISTS idx_events_stream_id ON events(stream, id) WHERE name <> '__snapshot__'"
|
|
149
151
|
);
|
|
150
|
-
await this.
|
|
152
|
+
await this._exec(`
|
|
151
153
|
CREATE TABLE IF NOT EXISTS streams (
|
|
152
154
|
stream TEXT PRIMARY KEY,
|
|
153
155
|
source TEXT,
|
|
@@ -164,40 +166,36 @@ var SqliteStore = class {
|
|
|
164
166
|
)
|
|
165
167
|
`);
|
|
166
168
|
try {
|
|
167
|
-
await this.
|
|
169
|
+
await this._exec(
|
|
168
170
|
"ALTER TABLE streams ADD COLUMN priority INTEGER NOT NULL DEFAULT 0"
|
|
169
171
|
);
|
|
170
172
|
} catch {
|
|
171
173
|
}
|
|
172
174
|
try {
|
|
173
|
-
await this.
|
|
174
|
-
"ALTER TABLE streams ADD COLUMN deferred_at TEXT"
|
|
175
|
-
);
|
|
175
|
+
await this._exec("ALTER TABLE streams ADD COLUMN deferred_at TEXT");
|
|
176
176
|
} catch {
|
|
177
177
|
}
|
|
178
178
|
try {
|
|
179
|
-
await this.
|
|
179
|
+
await this._exec(
|
|
180
180
|
"ALTER TABLE streams ADD COLUMN lane TEXT NOT NULL DEFAULT 'default'"
|
|
181
181
|
);
|
|
182
182
|
} catch {
|
|
183
183
|
}
|
|
184
184
|
try {
|
|
185
|
-
await this.
|
|
186
|
-
"ALTER TABLE streams ADD COLUMN correlated_at INTEGER"
|
|
187
|
-
);
|
|
185
|
+
await this._exec("ALTER TABLE streams ADD COLUMN correlated_at INTEGER");
|
|
188
186
|
} catch {
|
|
189
187
|
}
|
|
190
|
-
await this.
|
|
188
|
+
await this._exec(
|
|
191
189
|
`UPDATE streams
|
|
192
190
|
SET correlated_at = (SELECT COALESCE(MAX(id), -1) FROM events)
|
|
193
191
|
WHERE correlated_at IS NULL`
|
|
194
192
|
);
|
|
195
|
-
const correlated_columns = await this.
|
|
193
|
+
const correlated_columns = await this._exec(
|
|
196
194
|
"PRAGMA table_info(correlated)"
|
|
197
195
|
);
|
|
198
196
|
const legacy = correlated_columns.rows.some((r) => r.name === "id");
|
|
199
197
|
if (legacy) {
|
|
200
|
-
await this.
|
|
198
|
+
await this._exec(`
|
|
201
199
|
CREATE TABLE IF NOT EXISTS correlated_keyed (
|
|
202
200
|
key TEXT PRIMARY KEY,
|
|
203
201
|
at INTEGER NOT NULL DEFAULT -1,
|
|
@@ -205,15 +203,13 @@ var SqliteStore = class {
|
|
|
205
203
|
leased_until INTEGER
|
|
206
204
|
)
|
|
207
205
|
`);
|
|
208
|
-
await this.
|
|
206
|
+
await this._exec(
|
|
209
207
|
"INSERT OR IGNORE INTO correlated_keyed (key, at) SELECT '', at FROM correlated WHERE id = 0"
|
|
210
208
|
);
|
|
211
|
-
await this.
|
|
212
|
-
await this.
|
|
213
|
-
"ALTER TABLE correlated_keyed RENAME TO correlated"
|
|
214
|
-
);
|
|
209
|
+
await this._exec("DROP TABLE correlated");
|
|
210
|
+
await this._exec("ALTER TABLE correlated_keyed RENAME TO correlated");
|
|
215
211
|
} else {
|
|
216
|
-
await this.
|
|
212
|
+
await this._exec(`
|
|
217
213
|
CREATE TABLE IF NOT EXISTS correlated (
|
|
218
214
|
key TEXT PRIMARY KEY,
|
|
219
215
|
at INTEGER NOT NULL DEFAULT -1,
|
|
@@ -222,104 +218,127 @@ var SqliteStore = class {
|
|
|
222
218
|
)
|
|
223
219
|
`);
|
|
224
220
|
}
|
|
225
|
-
await this.
|
|
226
|
-
|
|
227
|
-
);
|
|
228
|
-
await this.client.execute(
|
|
221
|
+
await this._exec("INSERT OR IGNORE INTO correlated (key) VALUES ('')");
|
|
222
|
+
await this._exec(
|
|
229
223
|
"CREATE INDEX IF NOT EXISTS idx_streams_claim ON streams(blocked, priority DESC, at)"
|
|
230
224
|
);
|
|
231
|
-
await this.
|
|
225
|
+
await this._exec(
|
|
232
226
|
"CREATE INDEX IF NOT EXISTS idx_streams_correlated_at ON streams(lane, priority DESC, at) WHERE blocked = 0 AND at < correlated_at"
|
|
233
227
|
);
|
|
234
|
-
await this.
|
|
228
|
+
await this._exec(
|
|
235
229
|
"CREATE INDEX IF NOT EXISTS idx_streams_correlated_at_any_lane ON streams(priority DESC, at) WHERE blocked = 0 AND at < correlated_at"
|
|
236
230
|
);
|
|
237
|
-
await this.
|
|
231
|
+
await this._exec(
|
|
238
232
|
"CREATE INDEX IF NOT EXISTS idx_streams_lane ON streams(lane)"
|
|
239
233
|
);
|
|
240
234
|
}
|
|
241
235
|
async drop() {
|
|
242
|
-
await this.
|
|
243
|
-
await this.
|
|
244
|
-
await this.
|
|
236
|
+
await this._exec("DROP TABLE IF EXISTS events");
|
|
237
|
+
await this._exec("DROP TABLE IF EXISTS streams");
|
|
238
|
+
await this._exec("DROP TABLE IF EXISTS correlated");
|
|
245
239
|
}
|
|
246
|
-
// One
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
240
|
+
// One connection serves the whole store, and it can't run a statement
|
|
241
|
+
// while a write transaction is open on it: a second one fails at once
|
|
242
|
+
// (`TRANSACTION_ACTIVE` in memory, `SQLITE_BUSY` on a file) instead of
|
|
243
|
+
// waiting. So every statement and every transaction takes its turn here.
|
|
244
|
+
// A turn covers only the database work, never a caller's callback, so a
|
|
245
|
+
// callback that writes to the store can't wait on itself.
|
|
246
|
+
_turn = Promise.resolve();
|
|
247
|
+
_serial(work) {
|
|
248
|
+
const run = this._turn.then(work);
|
|
249
|
+
this._turn = run.catch(() => void 0);
|
|
250
|
+
return run;
|
|
251
|
+
}
|
|
252
|
+
// One statement, in its turn.
|
|
253
|
+
_exec(stmt) {
|
|
254
|
+
return this._serial(() => this.client.execute(stmt));
|
|
255
|
+
}
|
|
256
|
+
// Opens a write transaction: a BEGIN that fails is a {@link StoreError} too.
|
|
257
|
+
_begin(operation) {
|
|
258
|
+
return this.client.transaction("write").catch((e) => {
|
|
255
259
|
throw new StoreError(operation, { cause: e });
|
|
256
|
-
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// One write transaction, in its turn: any failure rolls back and throws a {@link StoreError} naming the operation.
|
|
263
|
+
_tx(operation, work) {
|
|
264
|
+
return this._serial(async () => {
|
|
265
|
+
const tx = await this._begin(operation);
|
|
266
|
+
try {
|
|
267
|
+
const result = await work(tx);
|
|
268
|
+
await tx.commit();
|
|
269
|
+
return result;
|
|
270
|
+
} catch (e) {
|
|
271
|
+
await tx.rollback();
|
|
272
|
+
throw new StoreError(operation, { cause: e });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
257
275
|
}
|
|
258
276
|
async dispose() {
|
|
259
|
-
await
|
|
260
|
-
this.client.close();
|
|
277
|
+
await this._serial(async () => this.client.close());
|
|
261
278
|
}
|
|
262
279
|
// --- commit: transaction + optimistic concurrency ---
|
|
263
280
|
async commit(stream, msgs, meta, expectedVersion) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
return this._serial(async () => {
|
|
282
|
+
const tx = await this._begin("commit");
|
|
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 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: [
|
|
306
|
+
stream,
|
|
307
|
+
version,
|
|
308
|
+
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),
|
|
288
317
|
stream,
|
|
289
318
|
version,
|
|
319
|
+
created: new Date(now),
|
|
290
320
|
name,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
321
|
+
data,
|
|
322
|
+
meta,
|
|
323
|
+
...pii == null ? {} : { pii }
|
|
324
|
+
});
|
|
325
|
+
version++;
|
|
326
|
+
}
|
|
327
|
+
await tx.commit();
|
|
328
|
+
return committed;
|
|
329
|
+
} catch (e) {
|
|
330
|
+
await tx.rollback();
|
|
331
|
+
if (e instanceof ConcurrencyError) throw e;
|
|
332
|
+
if (is_unique_violation(e))
|
|
333
|
+
throw new ConcurrencyError(
|
|
334
|
+
stream,
|
|
335
|
+
current_version,
|
|
336
|
+
msgs,
|
|
337
|
+
expectedVersion ?? -1
|
|
338
|
+
);
|
|
339
|
+
throw new StoreError("commit", { cause: e });
|
|
308
340
|
}
|
|
309
|
-
|
|
310
|
-
return committed;
|
|
311
|
-
} catch (e) {
|
|
312
|
-
await tx.rollback();
|
|
313
|
-
if (e instanceof ConcurrencyError) throw e;
|
|
314
|
-
if (is_unique_violation(e))
|
|
315
|
-
throw new ConcurrencyError(
|
|
316
|
-
stream,
|
|
317
|
-
current_version,
|
|
318
|
-
msgs,
|
|
319
|
-
expectedVersion ?? -1
|
|
320
|
-
);
|
|
321
|
-
throw new StoreError("commit", { cause: e });
|
|
322
|
-
}
|
|
341
|
+
});
|
|
323
342
|
}
|
|
324
343
|
// --- query: read-only, no transaction needed ---
|
|
325
344
|
async query(callback, query) {
|
|
@@ -369,7 +388,7 @@ var SqliteStore = class {
|
|
|
369
388
|
sql += " LIMIT ?";
|
|
370
389
|
args.push(query.limit);
|
|
371
390
|
}
|
|
372
|
-
const result = await this.
|
|
391
|
+
const result = await this._exec({ sql, args });
|
|
373
392
|
let count = 0;
|
|
374
393
|
for (const row of result.rows) {
|
|
375
394
|
const pii_value = query?.with_pii === false ? null : await this._parse_pii_from_read(row.pii);
|
|
@@ -740,8 +759,8 @@ var SqliteStore = class {
|
|
|
740
759
|
sql += " ORDER BY stream LIMIT ?";
|
|
741
760
|
args.push(limit);
|
|
742
761
|
const [streamsResult, maxResult] = await Promise.all([
|
|
743
|
-
this.
|
|
744
|
-
this.
|
|
762
|
+
this._exec({ sql, args }),
|
|
763
|
+
this._exec("SELECT COALESCE(MAX(id), -1) AS m FROM events")
|
|
745
764
|
]);
|
|
746
765
|
let count = 0;
|
|
747
766
|
for (const row of streamsResult.rows) {
|
|
@@ -873,8 +892,8 @@ var SqliteStore = class {
|
|
|
873
892
|
)` : null;
|
|
874
893
|
const tail_args = limit !== void 0 ? [...args, ...args, limit] : [...args, ...args];
|
|
875
894
|
const [headRes, tailRes] = await Promise.all([
|
|
876
|
-
this.
|
|
877
|
-
tail_sql ? this.
|
|
895
|
+
this._exec({ sql: head_sql, args: head_args }),
|
|
896
|
+
tail_sql ? this._exec({ sql: tail_sql, args: tail_args }) : null
|
|
878
897
|
]);
|
|
879
898
|
const out = /* @__PURE__ */ new Map();
|
|
880
899
|
for (const row of headRes.rows) {
|
|
@@ -936,7 +955,7 @@ var SqliteStore = class {
|
|
|
936
955
|
ORDER BY h.stream
|
|
937
956
|
`;
|
|
938
957
|
const full_args = limit !== void 0 ? [...args, limit] : args;
|
|
939
|
-
const res = await this.
|
|
958
|
+
const res = await this._exec({ sql, args: full_args });
|
|
940
959
|
const out = /* @__PURE__ */ new Map();
|
|
941
960
|
for (const row of res.rows) {
|
|
942
961
|
const r = row;
|
|
@@ -1046,33 +1065,35 @@ var SqliteStore = class {
|
|
|
1046
1065
|
* from the source.
|
|
1047
1066
|
*/
|
|
1048
1067
|
async restore(driver) {
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
+
return this._serial(async () => {
|
|
1069
|
+
const tx = await this._begin("restore");
|
|
1070
|
+
try {
|
|
1071
|
+
await tx.execute("DELETE FROM events");
|
|
1072
|
+
await tx.execute("DELETE FROM streams");
|
|
1073
|
+
await tx.execute("UPDATE correlated SET at = -1");
|
|
1074
|
+
await tx.execute("DELETE FROM sqlite_sequence WHERE name = 'events'");
|
|
1075
|
+
await driver(async (event) => {
|
|
1076
|
+
const pii_for_write = await this._stringify_pii_for_write(event.pii);
|
|
1077
|
+
const ins = await tx.execute({
|
|
1078
|
+
sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
1079
|
+
args: [
|
|
1080
|
+
event.stream,
|
|
1081
|
+
event.version,
|
|
1082
|
+
event.name,
|
|
1083
|
+
JSON.stringify(event.data),
|
|
1084
|
+
JSON.stringify(event.meta),
|
|
1085
|
+
event.created.toISOString(),
|
|
1086
|
+
pii_for_write
|
|
1087
|
+
]
|
|
1088
|
+
});
|
|
1089
|
+
return Number(ins.lastInsertRowid);
|
|
1068
1090
|
});
|
|
1069
|
-
|
|
1070
|
-
})
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1091
|
+
await tx.commit();
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
await tx.rollback();
|
|
1094
|
+
throw error;
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1076
1097
|
}
|
|
1077
1098
|
/**
|
|
1078
1099
|
* Wipe the sensitive-data payload for every event on the stream — the
|
|
@@ -1092,7 +1113,7 @@ var SqliteStore = class {
|
|
|
1092
1113
|
* @returns Count of events whose `pii` was set to `NULL`
|
|
1093
1114
|
*/
|
|
1094
1115
|
async forget_pii(stream) {
|
|
1095
|
-
const r = await this.
|
|
1116
|
+
const r = await this._exec({
|
|
1096
1117
|
sql: "UPDATE events SET pii = NULL WHERE stream = ? AND pii IS NOT NULL",
|
|
1097
1118
|
args: [stream]
|
|
1098
1119
|
});
|