@rotorsoft/act-sqlite 1.4.0 → 1.4.2
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 +21 -3
- package/dist/@types/sqlite-store.d.ts.map +1 -1
- package/dist/index.cjs +128 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +128 -84
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import { createClient } from "@libsql/client";
|
|
|
3
3
|
var DEFAULT_CONFIG = {
|
|
4
4
|
url: "file::memory:"
|
|
5
5
|
};
|
|
6
|
+
var parse_pii = (raw) => raw == null ? null : JSON.parse(raw);
|
|
7
|
+
var stringify_pii = (pii) => pii == null ? null : JSON.stringify(pii);
|
|
6
8
|
function streamPatternToLike(input) {
|
|
7
9
|
let s = input;
|
|
8
10
|
const start = s.startsWith("^");
|
|
@@ -33,9 +35,14 @@ var SqliteStore = class {
|
|
|
33
35
|
data TEXT NOT NULL,
|
|
34
36
|
meta TEXT NOT NULL,
|
|
35
37
|
created TEXT NOT NULL,
|
|
38
|
+
pii TEXT,
|
|
36
39
|
UNIQUE(stream, version)
|
|
37
40
|
)
|
|
38
41
|
`);
|
|
42
|
+
try {
|
|
43
|
+
await this.client.execute("ALTER TABLE events ADD COLUMN pii TEXT");
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
39
46
|
await this.client.execute(
|
|
40
47
|
"CREATE INDEX IF NOT EXISTS idx_events_stream ON events(stream)"
|
|
41
48
|
);
|
|
@@ -87,33 +94,34 @@ var SqliteStore = class {
|
|
|
87
94
|
async commit(stream, msgs, meta, expectedVersion) {
|
|
88
95
|
const tx = await this.client.transaction("write");
|
|
89
96
|
try {
|
|
90
|
-
const
|
|
97
|
+
const version_row = await tx.execute({
|
|
91
98
|
sql: "SELECT COALESCE(MAX(version), -1) as v FROM events WHERE stream = ?",
|
|
92
99
|
args: [stream]
|
|
93
100
|
});
|
|
94
|
-
const
|
|
95
|
-
if (typeof expectedVersion === "number" &&
|
|
101
|
+
const current_version = Number(version_row.rows[0].v);
|
|
102
|
+
if (typeof expectedVersion === "number" && current_version !== expectedVersion) {
|
|
96
103
|
const { ConcurrencyError } = await import("@rotorsoft/act");
|
|
97
104
|
throw new ConcurrencyError(
|
|
98
105
|
stream,
|
|
99
|
-
|
|
106
|
+
current_version,
|
|
100
107
|
msgs,
|
|
101
108
|
expectedVersion
|
|
102
109
|
);
|
|
103
110
|
}
|
|
104
111
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
105
112
|
const committed = [];
|
|
106
|
-
let version =
|
|
107
|
-
for (const { name, data } of msgs) {
|
|
113
|
+
let version = current_version + 1;
|
|
114
|
+
for (const { name, data, pii } of msgs) {
|
|
108
115
|
const result = await tx.execute({
|
|
109
|
-
sql: "INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, ?, ?, ?, ?, ?)",
|
|
116
|
+
sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
110
117
|
args: [
|
|
111
118
|
stream,
|
|
112
119
|
version,
|
|
113
120
|
name,
|
|
114
121
|
JSON.stringify(data),
|
|
115
122
|
JSON.stringify(meta),
|
|
116
|
-
now
|
|
123
|
+
now,
|
|
124
|
+
stringify_pii(pii)
|
|
117
125
|
]
|
|
118
126
|
});
|
|
119
127
|
committed.push({
|
|
@@ -123,7 +131,8 @@ var SqliteStore = class {
|
|
|
123
131
|
created: new Date(now),
|
|
124
132
|
name,
|
|
125
133
|
data,
|
|
126
|
-
meta
|
|
134
|
+
meta,
|
|
135
|
+
...pii == null ? {} : { pii }
|
|
127
136
|
});
|
|
128
137
|
version++;
|
|
129
138
|
}
|
|
@@ -190,7 +199,8 @@ var SqliteStore = class {
|
|
|
190
199
|
created: new Date(row.created),
|
|
191
200
|
name: row.name,
|
|
192
201
|
data: JSON.parse(row.data),
|
|
193
|
-
meta: JSON.parse(row.meta)
|
|
202
|
+
meta: JSON.parse(row.meta),
|
|
203
|
+
pii: parse_pii(row.pii)
|
|
194
204
|
})
|
|
195
205
|
);
|
|
196
206
|
count++;
|
|
@@ -246,10 +256,10 @@ var SqliteStore = class {
|
|
|
246
256
|
const tx = await this.client.transaction("write");
|
|
247
257
|
try {
|
|
248
258
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
249
|
-
const
|
|
259
|
+
const lane_clause = lane !== void 0 ? " AND lane = ?" : "";
|
|
250
260
|
const result = await tx.execute({
|
|
251
261
|
sql: `SELECT stream, source, at, priority, lane FROM streams
|
|
252
|
-
WHERE blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)${
|
|
262
|
+
WHERE blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)${lane_clause}
|
|
253
263
|
ORDER BY priority DESC, at ASC`,
|
|
254
264
|
args: lane !== void 0 ? [now, lane] : [now]
|
|
255
265
|
});
|
|
@@ -258,21 +268,21 @@ var SqliteStore = class {
|
|
|
258
268
|
const stream = row.stream;
|
|
259
269
|
const source = row.source;
|
|
260
270
|
const at = Number(row.at);
|
|
261
|
-
let
|
|
271
|
+
let has_events;
|
|
262
272
|
if (source) {
|
|
263
273
|
const check = await tx.execute({
|
|
264
274
|
sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' AND stream LIKE ? LIMIT 1`,
|
|
265
275
|
args: [at, streamPatternToLike(source)]
|
|
266
276
|
});
|
|
267
|
-
|
|
277
|
+
has_events = check.rows.length > 0;
|
|
268
278
|
} else {
|
|
269
279
|
const check = await tx.execute({
|
|
270
280
|
sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' LIMIT 1`,
|
|
271
281
|
args: [at]
|
|
272
282
|
});
|
|
273
|
-
|
|
283
|
+
has_events = check.rows.length > 0;
|
|
274
284
|
}
|
|
275
|
-
if (
|
|
285
|
+
if (has_events) {
|
|
276
286
|
candidates.push({
|
|
277
287
|
stream,
|
|
278
288
|
source: source ?? void 0,
|
|
@@ -359,7 +369,7 @@ var SqliteStore = class {
|
|
|
359
369
|
* plus positional args. Returns `"1"` (always true) when empty so
|
|
360
370
|
* callers can compose it unconditionally.
|
|
361
371
|
*/
|
|
362
|
-
|
|
372
|
+
_filter_clause(filter) {
|
|
363
373
|
const conditions = [];
|
|
364
374
|
const args = [];
|
|
365
375
|
if (filter.stream !== void 0) {
|
|
@@ -393,7 +403,7 @@ var SqliteStore = class {
|
|
|
393
403
|
}
|
|
394
404
|
// --- reset: transactional, accepts names or filter ---
|
|
395
405
|
async reset(input) {
|
|
396
|
-
const
|
|
406
|
+
const set_clause = `SET at = -1, retry = 0, blocked = 0, error = '',
|
|
397
407
|
leased_by = NULL, leased_until = NULL`;
|
|
398
408
|
const tx = await this.client.transaction("write");
|
|
399
409
|
try {
|
|
@@ -401,15 +411,15 @@ var SqliteStore = class {
|
|
|
401
411
|
if (Array.isArray(input)) {
|
|
402
412
|
for (const stream of input) {
|
|
403
413
|
const r = await tx.execute({
|
|
404
|
-
sql: `UPDATE streams ${
|
|
414
|
+
sql: `UPDATE streams ${set_clause} WHERE stream = ?`,
|
|
405
415
|
args: [stream]
|
|
406
416
|
});
|
|
407
417
|
count += r.rowsAffected;
|
|
408
418
|
}
|
|
409
419
|
} else {
|
|
410
|
-
const { clause, args } = this.
|
|
420
|
+
const { clause, args } = this._filter_clause(input);
|
|
411
421
|
const r = await tx.execute({
|
|
412
|
-
sql: `UPDATE streams ${
|
|
422
|
+
sql: `UPDATE streams ${set_clause} WHERE ${clause}`,
|
|
413
423
|
args
|
|
414
424
|
});
|
|
415
425
|
count = r.rowsAffected;
|
|
@@ -425,7 +435,7 @@ var SqliteStore = class {
|
|
|
425
435
|
// `retry = -1` so claim's post-bump returns retry=0 (first attempt),
|
|
426
436
|
// matching the InMemoryStore convention.
|
|
427
437
|
async unblock(input) {
|
|
428
|
-
const
|
|
438
|
+
const set_clause = `SET retry = -1, blocked = 0, error = '',
|
|
429
439
|
leased_by = NULL, leased_until = NULL`;
|
|
430
440
|
const tx = await this.client.transaction("write");
|
|
431
441
|
try {
|
|
@@ -433,19 +443,19 @@ var SqliteStore = class {
|
|
|
433
443
|
if (Array.isArray(input)) {
|
|
434
444
|
for (const stream of input) {
|
|
435
445
|
const r = await tx.execute({
|
|
436
|
-
sql: `UPDATE streams ${
|
|
446
|
+
sql: `UPDATE streams ${set_clause}
|
|
437
447
|
WHERE stream = ? AND blocked = 1`,
|
|
438
448
|
args: [stream]
|
|
439
449
|
});
|
|
440
450
|
count += r.rowsAffected;
|
|
441
451
|
}
|
|
442
452
|
} else {
|
|
443
|
-
const { clause, args } = this.
|
|
453
|
+
const { clause, args } = this._filter_clause({
|
|
444
454
|
...input,
|
|
445
455
|
blocked: true
|
|
446
456
|
});
|
|
447
457
|
const r = await tx.execute({
|
|
448
|
-
sql: `UPDATE streams ${
|
|
458
|
+
sql: `UPDATE streams ${set_clause} WHERE ${clause}`,
|
|
449
459
|
args
|
|
450
460
|
});
|
|
451
461
|
count = r.rowsAffected;
|
|
@@ -544,11 +554,11 @@ var SqliteStore = class {
|
|
|
544
554
|
*/
|
|
545
555
|
async query_stats(input, options) {
|
|
546
556
|
const exclude = options?.exclude ?? [];
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
const
|
|
557
|
+
const want_tail = options?.tail ?? false;
|
|
558
|
+
const want_count = options?.count ?? false;
|
|
559
|
+
const want_names = options?.names ?? false;
|
|
550
560
|
const before = options?.before;
|
|
551
|
-
const
|
|
561
|
+
const full_scan = want_count || want_names;
|
|
552
562
|
if (Array.isArray(input) && input.length === 0) {
|
|
553
563
|
return /* @__PURE__ */ new Map();
|
|
554
564
|
}
|
|
@@ -576,55 +586,61 @@ var SqliteStore = class {
|
|
|
576
586
|
where.push(`e.id < ?`);
|
|
577
587
|
args.push(before);
|
|
578
588
|
}
|
|
579
|
-
const
|
|
580
|
-
const
|
|
581
|
-
return
|
|
582
|
-
|
|
583
|
-
|
|
589
|
+
const from_clause = `events e`;
|
|
590
|
+
const where_clause = `WHERE ${where.length ? where.join(" AND ") : "1=1"}`;
|
|
591
|
+
return full_scan ? this._query_stats_full_scan(
|
|
592
|
+
from_clause,
|
|
593
|
+
where_clause,
|
|
584
594
|
args,
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
) : this.
|
|
595
|
+
want_tail,
|
|
596
|
+
want_count,
|
|
597
|
+
want_names
|
|
598
|
+
) : this._query_stats_heads_only(
|
|
599
|
+
from_clause,
|
|
600
|
+
where_clause,
|
|
601
|
+
args,
|
|
602
|
+
want_tail
|
|
603
|
+
);
|
|
589
604
|
}
|
|
590
605
|
/**
|
|
591
606
|
* Cheap path — head (and optional tail) via ROW_NUMBER() over the
|
|
592
607
|
* `(stream, version)` unique index. Parallel queries when tail set.
|
|
593
608
|
*/
|
|
594
|
-
async
|
|
595
|
-
const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;
|
|
596
|
-
const
|
|
609
|
+
async _query_stats_heads_only(from_clause, where_clause, args, want_tail) {
|
|
610
|
+
const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta, e.pii`;
|
|
611
|
+
const head_sql = `SELECT * FROM (
|
|
597
612
|
SELECT ${cols}, ROW_NUMBER() OVER (PARTITION BY e.stream ORDER BY e.version DESC) AS rn
|
|
598
|
-
FROM ${
|
|
599
|
-
${
|
|
613
|
+
FROM ${from_clause}
|
|
614
|
+
${where_clause}
|
|
600
615
|
) WHERE rn = 1`;
|
|
601
|
-
const
|
|
616
|
+
const tail_sql = want_tail ? `SELECT * FROM (
|
|
602
617
|
SELECT ${cols}, ROW_NUMBER() OVER (PARTITION BY e.stream ORDER BY e.version ASC) AS rn
|
|
603
|
-
FROM ${
|
|
604
|
-
${
|
|
618
|
+
FROM ${from_clause}
|
|
619
|
+
${where_clause}
|
|
605
620
|
) WHERE rn = 1` : null;
|
|
606
621
|
const [headRes, tailRes] = await Promise.all([
|
|
607
|
-
this.client.execute({ sql:
|
|
608
|
-
|
|
622
|
+
this.client.execute({ sql: head_sql, args }),
|
|
623
|
+
tail_sql ? this.client.execute({ sql: tail_sql, args }) : null
|
|
609
624
|
]);
|
|
610
|
-
const
|
|
625
|
+
const to_committed = (row) => ({
|
|
611
626
|
id: Number(row.id),
|
|
612
627
|
stream: row.stream,
|
|
613
628
|
version: Number(row.version),
|
|
614
629
|
name: row.name,
|
|
615
630
|
data: JSON.parse(row.data),
|
|
616
631
|
meta: JSON.parse(row.meta),
|
|
617
|
-
created: new Date(row.created)
|
|
632
|
+
created: new Date(row.created),
|
|
633
|
+
pii: parse_pii(row.pii)
|
|
618
634
|
});
|
|
619
635
|
const out = /* @__PURE__ */ new Map();
|
|
620
636
|
for (const row of headRes.rows) {
|
|
621
637
|
out.set(row.stream, {
|
|
622
|
-
head:
|
|
638
|
+
head: to_committed(row)
|
|
623
639
|
});
|
|
624
640
|
}
|
|
625
641
|
if (tailRes) {
|
|
626
642
|
for (const row of tailRes.rows) {
|
|
627
|
-
out.get(row.stream).tail =
|
|
643
|
+
out.get(row.stream).tail = to_committed(row);
|
|
628
644
|
}
|
|
629
645
|
}
|
|
630
646
|
return out;
|
|
@@ -634,20 +650,20 @@ var SqliteStore = class {
|
|
|
634
650
|
* `json_group_object(name, n)`. Heads (and optional tails) ride free
|
|
635
651
|
* on the same scan.
|
|
636
652
|
*/
|
|
637
|
-
async
|
|
638
|
-
const
|
|
653
|
+
async _query_stats_full_scan(from_clause, where_clause, args, want_tail, want_count, want_names) {
|
|
654
|
+
const tail_cte = want_tail ? `, tails AS (
|
|
639
655
|
SELECT * FROM (
|
|
640
656
|
SELECT *, ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version ASC) AS rn FROM ef
|
|
641
657
|
) WHERE rn = 1
|
|
642
658
|
)` : "";
|
|
643
|
-
const
|
|
644
|
-
const
|
|
645
|
-
t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta` : "";
|
|
659
|
+
const tail_join = want_tail ? `LEFT JOIN tails t ON t.stream = h.stream` : "";
|
|
660
|
+
const tail_cols = want_tail ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,
|
|
661
|
+
t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta, t.pii AS t_pii` : "";
|
|
646
662
|
const sql = `
|
|
647
663
|
WITH ef AS (
|
|
648
|
-
SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta
|
|
649
|
-
FROM ${
|
|
650
|
-
${
|
|
664
|
+
SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta, e.pii
|
|
665
|
+
FROM ${from_clause}
|
|
666
|
+
${where_clause}
|
|
651
667
|
),
|
|
652
668
|
agg AS (
|
|
653
669
|
SELECT stream,
|
|
@@ -665,60 +681,63 @@ var SqliteStore = class {
|
|
|
665
681
|
SELECT *, ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version DESC) AS rn FROM ef
|
|
666
682
|
) WHERE rn = 1
|
|
667
683
|
)
|
|
668
|
-
${
|
|
684
|
+
${tail_cte}
|
|
669
685
|
SELECT
|
|
670
|
-
h.id, h.stream, h.version, h.name, h.data, h.created, h.meta,
|
|
686
|
+
h.id, h.stream, h.version, h.name, h.data, h.created, h.meta, h.pii,
|
|
671
687
|
a.cnt AS agg_count,
|
|
672
688
|
a.names AS agg_names
|
|
673
|
-
${
|
|
689
|
+
${tail_cols}
|
|
674
690
|
FROM heads h
|
|
675
691
|
LEFT JOIN agg a ON a.stream = h.stream
|
|
676
|
-
${
|
|
692
|
+
${tail_join}
|
|
677
693
|
`;
|
|
678
694
|
const res = await this.client.execute({ sql, args });
|
|
679
|
-
const
|
|
695
|
+
const to_committed = (id, stream, version, name, data, meta, created, pii) => ({
|
|
680
696
|
id: Number(id),
|
|
681
697
|
stream,
|
|
682
698
|
version: Number(version),
|
|
683
699
|
name,
|
|
684
700
|
data: JSON.parse(data),
|
|
685
701
|
meta: JSON.parse(meta),
|
|
686
|
-
created: new Date(created)
|
|
702
|
+
created: new Date(created),
|
|
703
|
+
pii: parse_pii(pii)
|
|
687
704
|
});
|
|
688
705
|
const out = /* @__PURE__ */ new Map();
|
|
689
706
|
for (const row of res.rows) {
|
|
690
707
|
const r = row;
|
|
691
708
|
const stats = {
|
|
692
|
-
head:
|
|
709
|
+
head: to_committed(
|
|
693
710
|
r.id,
|
|
694
711
|
r.stream,
|
|
695
712
|
r.version,
|
|
696
713
|
r.name,
|
|
697
714
|
r.data,
|
|
698
715
|
r.meta,
|
|
699
|
-
r.created
|
|
716
|
+
r.created,
|
|
717
|
+
r.pii
|
|
700
718
|
)
|
|
701
719
|
};
|
|
702
|
-
if (
|
|
703
|
-
stats.tail =
|
|
720
|
+
if (want_tail && r.t_id !== null && r.t_id !== void 0) {
|
|
721
|
+
stats.tail = to_committed(
|
|
704
722
|
r.t_id,
|
|
705
723
|
r.t_stream,
|
|
706
724
|
r.t_version,
|
|
707
725
|
r.t_name,
|
|
708
726
|
r.t_data,
|
|
709
727
|
r.t_meta,
|
|
710
|
-
r.t_created
|
|
728
|
+
r.t_created,
|
|
729
|
+
r.t_pii
|
|
711
730
|
);
|
|
712
731
|
}
|
|
713
|
-
if (
|
|
714
|
-
if (
|
|
732
|
+
if (want_count) stats.count = Number(r.agg_count);
|
|
733
|
+
if (want_names) stats.names = JSON.parse(r.agg_names);
|
|
715
734
|
out.set(r.stream, stats);
|
|
716
735
|
}
|
|
717
736
|
return out;
|
|
718
737
|
}
|
|
719
738
|
// --- prioritize: bulk priority update with filter (ACT-102) ---
|
|
720
739
|
async prioritize(filter, priority) {
|
|
721
|
-
const { clause, args: filterArgs } = this.
|
|
740
|
+
const { clause, args: filterArgs } = this._filter_clause(filter);
|
|
722
741
|
const sql = `UPDATE streams SET priority = ?
|
|
723
742
|
WHERE priority <> ? AND ${clause}`;
|
|
724
743
|
const result = await this.client.execute({
|
|
@@ -733,11 +752,11 @@ var SqliteStore = class {
|
|
|
733
752
|
const tx = await this.client.transaction("write");
|
|
734
753
|
try {
|
|
735
754
|
for (const { stream, snapshot, meta } of targets) {
|
|
736
|
-
const
|
|
755
|
+
const count_row = await tx.execute({
|
|
737
756
|
sql: "SELECT COUNT(*) as c FROM events WHERE stream = ?",
|
|
738
757
|
args: [stream]
|
|
739
758
|
});
|
|
740
|
-
const deleted = Number(
|
|
759
|
+
const deleted = Number(count_row.rows[0].c);
|
|
741
760
|
await tx.execute({
|
|
742
761
|
sql: "DELETE FROM events WHERE stream = ?",
|
|
743
762
|
args: [stream]
|
|
@@ -746,16 +765,16 @@ var SqliteStore = class {
|
|
|
746
765
|
sql: "DELETE FROM streams WHERE stream = ?",
|
|
747
766
|
args: [stream]
|
|
748
767
|
});
|
|
749
|
-
const
|
|
750
|
-
const
|
|
768
|
+
const event_name = snapshot !== void 0 ? "__snapshot__" : "__tombstone__";
|
|
769
|
+
const event_meta = meta ?? { correlation: "", causation: {} };
|
|
751
770
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
752
771
|
const ins = await tx.execute({
|
|
753
772
|
sql: "INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, 0, ?, ?, ?, ?)",
|
|
754
773
|
args: [
|
|
755
774
|
stream,
|
|
756
|
-
|
|
775
|
+
event_name,
|
|
757
776
|
JSON.stringify(snapshot ?? {}),
|
|
758
|
-
JSON.stringify(
|
|
777
|
+
JSON.stringify(event_meta),
|
|
759
778
|
now
|
|
760
779
|
]
|
|
761
780
|
});
|
|
@@ -766,9 +785,9 @@ var SqliteStore = class {
|
|
|
766
785
|
stream,
|
|
767
786
|
version: 0,
|
|
768
787
|
created: new Date(now),
|
|
769
|
-
name:
|
|
788
|
+
name: event_name,
|
|
770
789
|
data: snapshot ?? {},
|
|
771
|
-
meta:
|
|
790
|
+
meta: event_meta
|
|
772
791
|
}
|
|
773
792
|
});
|
|
774
793
|
}
|
|
@@ -798,14 +817,15 @@ var SqliteStore = class {
|
|
|
798
817
|
await tx.execute("DELETE FROM sqlite_sequence WHERE name = 'events'");
|
|
799
818
|
await driver(async (event) => {
|
|
800
819
|
const ins = await tx.execute({
|
|
801
|
-
sql: "INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, ?, ?, ?, ?, ?)",
|
|
820
|
+
sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
802
821
|
args: [
|
|
803
822
|
event.stream,
|
|
804
823
|
event.version,
|
|
805
824
|
event.name,
|
|
806
825
|
JSON.stringify(event.data),
|
|
807
826
|
JSON.stringify(event.meta),
|
|
808
|
-
event.created.toISOString()
|
|
827
|
+
event.created.toISOString(),
|
|
828
|
+
stringify_pii(event.pii)
|
|
809
829
|
]
|
|
810
830
|
});
|
|
811
831
|
return Number(ins.lastInsertRowid);
|
|
@@ -816,6 +836,30 @@ var SqliteStore = class {
|
|
|
816
836
|
throw error;
|
|
817
837
|
}
|
|
818
838
|
}
|
|
839
|
+
/**
|
|
840
|
+
* Wipe the sensitive-data payload for every event on the stream — the
|
|
841
|
+
* physical-erasure side of the sensitive-data epic (#566). Sets
|
|
842
|
+
* `events.pii` to `NULL` for the stream's events; `events.data` and
|
|
843
|
+
* the rest of the row are never touched.
|
|
844
|
+
*
|
|
845
|
+
* Single `UPDATE` under SQLite's writer lock, bounded by events-per-
|
|
846
|
+
* stream. Idempotent — the `pii IS NOT NULL` predicate filters out
|
|
847
|
+
* already-wiped rows so a second call returns `0`.
|
|
848
|
+
*
|
|
849
|
+
* SQLite doesn't auto-reclaim space; freed pages stay in the file
|
|
850
|
+
* until an operator-scheduled `PRAGMA incremental_vacuum` or a full
|
|
851
|
+
* `VACUUM`. The production checklist documents the cadence.
|
|
852
|
+
*
|
|
853
|
+
* @param stream Target stream
|
|
854
|
+
* @returns Count of events whose `pii` was set to `NULL`
|
|
855
|
+
*/
|
|
856
|
+
async forget_pii(stream) {
|
|
857
|
+
const r = await this.client.execute({
|
|
858
|
+
sql: "UPDATE events SET pii = NULL WHERE stream = ? AND pii IS NOT NULL",
|
|
859
|
+
args: [stream]
|
|
860
|
+
});
|
|
861
|
+
return r.rowsAffected ?? 0;
|
|
862
|
+
}
|
|
819
863
|
};
|
|
820
864
|
export {
|
|
821
865
|
SqliteStore,
|