@rotorsoft/act-sqlite 1.12.2 → 1.12.4
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/dist/.tsbuildinfo +1 -1
- package/dist/@types/sqlite-store.d.ts +22 -10
- package/dist/@types/sqlite-store.d.ts.map +1 -1
- package/dist/index.cjs +66 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +70 -41
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// src/sqlite-store.ts
|
|
2
2
|
import { createClient } from "@libsql/client";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ConcurrencyError,
|
|
5
|
+
is_literal_source,
|
|
6
|
+
StoreError,
|
|
7
|
+
ValidationError
|
|
8
|
+
} from "@rotorsoft/act";
|
|
4
9
|
import {
|
|
5
10
|
decrypt,
|
|
6
11
|
encrypt,
|
|
@@ -9,8 +14,16 @@ import {
|
|
|
9
14
|
var DEFAULT_CONFIG = {
|
|
10
15
|
url: "file::memory:"
|
|
11
16
|
};
|
|
17
|
+
var ISO_8601 = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(\.\d+)?(Z|[+-][0-2][0-9]:[0-5][0-9])?$/;
|
|
18
|
+
var date_reviver = (_key, value) => typeof value === "string" && ISO_8601.test(value) ? new Date(value) : value;
|
|
19
|
+
var parse_json = (raw) => JSON.parse(raw, date_reviver);
|
|
20
|
+
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
21
|
+
var is_unique_violation = (e) => {
|
|
22
|
+
const err = e;
|
|
23
|
+
return err?.rawCode === SQLITE_CONSTRAINT_UNIQUE || typeof err?.message === "string" && err.message.includes("UNIQUE constraint failed");
|
|
24
|
+
};
|
|
12
25
|
var PORTABLE_BODY = /^(?:\.\*|\.|[^\\^$.()[\]{}|+?*])*$/;
|
|
13
|
-
function
|
|
26
|
+
function streamPatternToGlob(input) {
|
|
14
27
|
let s = input;
|
|
15
28
|
const start = s.startsWith("^");
|
|
16
29
|
const end = s.endsWith("$");
|
|
@@ -18,27 +31,26 @@ function streamPatternToLike(input) {
|
|
|
18
31
|
if (end) s = s.slice(0, -1);
|
|
19
32
|
if (!PORTABLE_BODY.test(s))
|
|
20
33
|
throw new ValidationError(
|
|
21
|
-
`stream filter pattern "${input}" \u2014 the SQLite adapter supports only the portable subset: "^" and "$" anchors, "." (any single character), ".*" (any run), and literal characters. Escapes, character classes, grouping, alternation, and quantifiers cannot be converted to
|
|
34
|
+
`stream filter pattern "${input}" \u2014 the SQLite adapter supports only the portable subset: "^" and "$" anchors, "." (any single character), ".*" (any run), and literal characters. Escapes, character classes, grouping, alternation, and quantifiers cannot be converted to GLOB; use stream_exact/source_exact for literal names`,
|
|
22
35
|
input,
|
|
23
36
|
"non-portable stream filter pattern"
|
|
24
37
|
);
|
|
25
38
|
const tokens = [];
|
|
26
|
-
if (!start) tokens.push("
|
|
39
|
+
if (!start) tokens.push("*");
|
|
27
40
|
for (let i = 0; i < s.length; i++) {
|
|
28
41
|
const c = s[i];
|
|
29
42
|
if (c === ".") {
|
|
30
43
|
if (s[i + 1] === "*") {
|
|
31
|
-
tokens.push("
|
|
44
|
+
tokens.push("*");
|
|
32
45
|
i++;
|
|
33
|
-
} else tokens.push("
|
|
34
|
-
} else
|
|
35
|
-
else tokens.push(c);
|
|
46
|
+
} else tokens.push("?");
|
|
47
|
+
} else tokens.push(c);
|
|
36
48
|
}
|
|
37
|
-
if (!end) tokens.push("
|
|
49
|
+
if (!end) tokens.push("*");
|
|
38
50
|
let out = "";
|
|
39
51
|
let prev = "";
|
|
40
52
|
for (const t of tokens) {
|
|
41
|
-
if (t === "
|
|
53
|
+
if (t === "*" && prev === "*") continue;
|
|
42
54
|
out += t;
|
|
43
55
|
prev = t;
|
|
44
56
|
}
|
|
@@ -173,14 +185,14 @@ var SqliteStore = class {
|
|
|
173
185
|
// --- commit: transaction + optimistic concurrency ---
|
|
174
186
|
async commit(stream, msgs, meta, expectedVersion) {
|
|
175
187
|
const tx = await this.client.transaction("write");
|
|
188
|
+
let current_version = -1;
|
|
176
189
|
try {
|
|
177
190
|
const version_row = await tx.execute({
|
|
178
191
|
sql: "SELECT COALESCE(MAX(version), -1) as v FROM events WHERE stream = ?",
|
|
179
192
|
args: [stream]
|
|
180
193
|
});
|
|
181
|
-
|
|
194
|
+
current_version = Number(version_row.rows[0].v);
|
|
182
195
|
if (typeof expectedVersion === "number" && current_version !== expectedVersion) {
|
|
183
|
-
const { ConcurrencyError } = await import("@rotorsoft/act");
|
|
184
196
|
throw new ConcurrencyError(
|
|
185
197
|
stream,
|
|
186
198
|
current_version,
|
|
@@ -221,7 +233,15 @@ var SqliteStore = class {
|
|
|
221
233
|
return committed;
|
|
222
234
|
} catch (e) {
|
|
223
235
|
await tx.rollback();
|
|
224
|
-
throw e;
|
|
236
|
+
if (e instanceof ConcurrencyError) throw e;
|
|
237
|
+
if (is_unique_violation(e))
|
|
238
|
+
throw new ConcurrencyError(
|
|
239
|
+
stream,
|
|
240
|
+
current_version,
|
|
241
|
+
msgs,
|
|
242
|
+
expectedVersion ?? -1
|
|
243
|
+
);
|
|
244
|
+
throw new StoreError("commit", { cause: e });
|
|
225
245
|
}
|
|
226
246
|
}
|
|
227
247
|
// --- query: read-only, no transaction needed ---
|
|
@@ -233,11 +253,11 @@ var SqliteStore = class {
|
|
|
233
253
|
sql += " AND stream = ?";
|
|
234
254
|
args.push(query.stream);
|
|
235
255
|
} else {
|
|
236
|
-
sql += " AND stream
|
|
237
|
-
args.push(
|
|
256
|
+
sql += " AND stream GLOB ?";
|
|
257
|
+
args.push(streamPatternToGlob(query.stream));
|
|
238
258
|
}
|
|
239
259
|
}
|
|
240
|
-
if (query?.names) {
|
|
260
|
+
if (query?.names !== void 0) {
|
|
241
261
|
sql += ` AND name IN (${query.names.map(() => "?").join(",")})`;
|
|
242
262
|
args.push(...query.names);
|
|
243
263
|
}
|
|
@@ -283,8 +303,8 @@ var SqliteStore = class {
|
|
|
283
303
|
version: Number(row.version),
|
|
284
304
|
created: new Date(row.created),
|
|
285
305
|
name: row.name,
|
|
286
|
-
data:
|
|
287
|
-
meta:
|
|
306
|
+
data: parse_json(row.data),
|
|
307
|
+
meta: parse_json(row.meta),
|
|
288
308
|
pii: pii_value
|
|
289
309
|
})
|
|
290
310
|
);
|
|
@@ -299,7 +319,7 @@ var SqliteStore = class {
|
|
|
299
319
|
async subscribe(streams) {
|
|
300
320
|
for (const { source } of streams) {
|
|
301
321
|
if (source !== void 0 && !is_literal_source(source))
|
|
302
|
-
|
|
322
|
+
streamPatternToGlob(source);
|
|
303
323
|
}
|
|
304
324
|
const tx = await this.client.transaction("write");
|
|
305
325
|
try {
|
|
@@ -365,8 +385,8 @@ var SqliteStore = class {
|
|
|
365
385
|
sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' AND stream = ? LIMIT 1`,
|
|
366
386
|
args: [at, source]
|
|
367
387
|
}) : await tx.execute({
|
|
368
|
-
sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' AND stream
|
|
369
|
-
args: [at,
|
|
388
|
+
sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' AND stream GLOB ? LIMIT 1`,
|
|
389
|
+
args: [at, streamPatternToGlob(source)]
|
|
370
390
|
});
|
|
371
391
|
has_events = check.rows.length > 0;
|
|
372
392
|
} else {
|
|
@@ -386,7 +406,14 @@ var SqliteStore = class {
|
|
|
386
406
|
});
|
|
387
407
|
}
|
|
388
408
|
}
|
|
389
|
-
const
|
|
409
|
+
const fair = lagging >= 2 ? Math.max(1, Math.floor(lagging / 4)) : 0;
|
|
410
|
+
const priority_slice = candidates.slice(0, lagging - fair);
|
|
411
|
+
const picked = new Set(priority_slice.map((c) => c.stream));
|
|
412
|
+
const fair_slice = [...candidates].sort((a, b) => a.at - b.at).filter((c) => !picked.has(c.stream)).slice(0, fair);
|
|
413
|
+
const lag = [...priority_slice, ...fair_slice].map((c) => ({
|
|
414
|
+
...c,
|
|
415
|
+
lagging: true
|
|
416
|
+
}));
|
|
390
417
|
const lead = [...candidates].sort((a, b) => b.at - a.at).slice(0, leading).map((c) => ({ ...c, lagging: false }));
|
|
391
418
|
const seen = /* @__PURE__ */ new Set();
|
|
392
419
|
const combined = [...lag, ...lead].filter((p) => {
|
|
@@ -507,8 +534,8 @@ var SqliteStore = class {
|
|
|
507
534
|
conditions.push("stream = ?");
|
|
508
535
|
args.push(filter.stream);
|
|
509
536
|
} else {
|
|
510
|
-
conditions.push("stream
|
|
511
|
-
args.push(
|
|
537
|
+
conditions.push("stream GLOB ?");
|
|
538
|
+
args.push(streamPatternToGlob(filter.stream));
|
|
512
539
|
}
|
|
513
540
|
}
|
|
514
541
|
if (filter.source !== void 0) {
|
|
@@ -517,8 +544,8 @@ var SqliteStore = class {
|
|
|
517
544
|
conditions.push("source = ?");
|
|
518
545
|
args.push(filter.source);
|
|
519
546
|
} else {
|
|
520
|
-
conditions.push("source
|
|
521
|
-
args.push(
|
|
547
|
+
conditions.push("source GLOB ?");
|
|
548
|
+
args.push(streamPatternToGlob(filter.source));
|
|
522
549
|
}
|
|
523
550
|
}
|
|
524
551
|
if (filter.blocked !== void 0) {
|
|
@@ -600,15 +627,15 @@ var SqliteStore = class {
|
|
|
600
627
|
// --- query_streams: read-only introspection with filters ---
|
|
601
628
|
async query_streams(callback, query) {
|
|
602
629
|
const limit = query?.limit ?? 100;
|
|
603
|
-
let sql = "SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority, lane FROM streams WHERE 1=1";
|
|
630
|
+
let sql = "SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority, lane, deferred_at FROM streams WHERE 1=1";
|
|
604
631
|
const args = [];
|
|
605
632
|
if (query?.stream !== void 0) {
|
|
606
633
|
if (query.stream_exact) {
|
|
607
634
|
sql += " AND stream = ?";
|
|
608
635
|
args.push(query.stream);
|
|
609
636
|
} else {
|
|
610
|
-
sql += " AND stream
|
|
611
|
-
args.push(
|
|
637
|
+
sql += " AND stream GLOB ?";
|
|
638
|
+
args.push(streamPatternToGlob(query.stream));
|
|
612
639
|
}
|
|
613
640
|
}
|
|
614
641
|
if (query?.source !== void 0) {
|
|
@@ -617,8 +644,8 @@ var SqliteStore = class {
|
|
|
617
644
|
sql += " AND source = ?";
|
|
618
645
|
args.push(query.source);
|
|
619
646
|
} else {
|
|
620
|
-
sql += " AND source
|
|
621
|
-
args.push(
|
|
647
|
+
sql += " AND source GLOB ?";
|
|
648
|
+
args.push(streamPatternToGlob(query.source));
|
|
622
649
|
}
|
|
623
650
|
}
|
|
624
651
|
if (query?.blocked !== void 0) {
|
|
@@ -642,6 +669,7 @@ var SqliteStore = class {
|
|
|
642
669
|
let count = 0;
|
|
643
670
|
for (const row of streamsResult.rows) {
|
|
644
671
|
const leased_until = row.leased_until;
|
|
672
|
+
const deferred_at = row.deferred_at;
|
|
645
673
|
callback({
|
|
646
674
|
stream: row.stream,
|
|
647
675
|
source: row.source ?? void 0,
|
|
@@ -652,7 +680,8 @@ var SqliteStore = class {
|
|
|
652
680
|
priority: Number(row.priority),
|
|
653
681
|
leased_by: row.leased_by ?? void 0,
|
|
654
682
|
leased_until: leased_until ? new Date(leased_until) : void 0,
|
|
655
|
-
lane: row.lane
|
|
683
|
+
lane: row.lane,
|
|
684
|
+
deferred_at: deferred_at ? new Date(deferred_at).getTime() : void 0
|
|
656
685
|
});
|
|
657
686
|
count++;
|
|
658
687
|
}
|
|
@@ -705,8 +734,8 @@ var SqliteStore = class {
|
|
|
705
734
|
where.push(`e.stream = ?`);
|
|
706
735
|
args.push(input.stream);
|
|
707
736
|
} else {
|
|
708
|
-
where.push(`e.stream
|
|
709
|
-
args.push(
|
|
737
|
+
where.push(`e.stream GLOB ?`);
|
|
738
|
+
args.push(streamPatternToGlob(input.stream));
|
|
710
739
|
}
|
|
711
740
|
}
|
|
712
741
|
if (exclude.length) {
|
|
@@ -775,8 +804,8 @@ var SqliteStore = class {
|
|
|
775
804
|
stream: row.stream,
|
|
776
805
|
version: Number(row.version),
|
|
777
806
|
name: row.name,
|
|
778
|
-
data:
|
|
779
|
-
meta:
|
|
807
|
+
data: parse_json(row.data),
|
|
808
|
+
meta: parse_json(row.meta),
|
|
780
809
|
created: new Date(row.created),
|
|
781
810
|
pii: await this._parse_pii_from_read(row.pii)
|
|
782
811
|
});
|
|
@@ -848,8 +877,8 @@ var SqliteStore = class {
|
|
|
848
877
|
stream,
|
|
849
878
|
version: Number(version),
|
|
850
879
|
name,
|
|
851
|
-
data:
|
|
852
|
-
meta:
|
|
880
|
+
data: parse_json(data),
|
|
881
|
+
meta: parse_json(meta),
|
|
853
882
|
created: new Date(created),
|
|
854
883
|
pii: await this._parse_pii_from_read(pii)
|
|
855
884
|
});
|
|
@@ -930,8 +959,8 @@ var SqliteStore = class {
|
|
|
930
959
|
version: Number(row.version),
|
|
931
960
|
created: new Date(row.created),
|
|
932
961
|
name: row.name,
|
|
933
|
-
data:
|
|
934
|
-
meta:
|
|
962
|
+
data: parse_json(row.data),
|
|
963
|
+
meta: parse_json(row.meta)
|
|
935
964
|
},
|
|
936
965
|
before
|
|
937
966
|
});
|
|
@@ -1050,6 +1079,6 @@ var SqliteStore = class {
|
|
|
1050
1079
|
};
|
|
1051
1080
|
export {
|
|
1052
1081
|
SqliteStore,
|
|
1053
|
-
|
|
1082
|
+
streamPatternToGlob
|
|
1054
1083
|
};
|
|
1055
1084
|
//# sourceMappingURL=index.js.map
|