@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.cjs
CHANGED
|
@@ -40,6 +40,8 @@ var import_client = require("@libsql/client");
|
|
|
40
40
|
var DEFAULT_CONFIG = {
|
|
41
41
|
url: "file::memory:"
|
|
42
42
|
};
|
|
43
|
+
var parse_pii = (raw) => raw == null ? null : JSON.parse(raw);
|
|
44
|
+
var stringify_pii = (pii) => pii == null ? null : JSON.stringify(pii);
|
|
43
45
|
function streamPatternToLike(input) {
|
|
44
46
|
let s = input;
|
|
45
47
|
const start = s.startsWith("^");
|
|
@@ -70,9 +72,14 @@ var SqliteStore = class {
|
|
|
70
72
|
data TEXT NOT NULL,
|
|
71
73
|
meta TEXT NOT NULL,
|
|
72
74
|
created TEXT NOT NULL,
|
|
75
|
+
pii TEXT,
|
|
73
76
|
UNIQUE(stream, version)
|
|
74
77
|
)
|
|
75
78
|
`);
|
|
79
|
+
try {
|
|
80
|
+
await this.client.execute("ALTER TABLE events ADD COLUMN pii TEXT");
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
76
83
|
await this.client.execute(
|
|
77
84
|
"CREATE INDEX IF NOT EXISTS idx_events_stream ON events(stream)"
|
|
78
85
|
);
|
|
@@ -124,33 +131,34 @@ var SqliteStore = class {
|
|
|
124
131
|
async commit(stream, msgs, meta, expectedVersion) {
|
|
125
132
|
const tx = await this.client.transaction("write");
|
|
126
133
|
try {
|
|
127
|
-
const
|
|
134
|
+
const version_row = await tx.execute({
|
|
128
135
|
sql: "SELECT COALESCE(MAX(version), -1) as v FROM events WHERE stream = ?",
|
|
129
136
|
args: [stream]
|
|
130
137
|
});
|
|
131
|
-
const
|
|
132
|
-
if (typeof expectedVersion === "number" &&
|
|
138
|
+
const current_version = Number(version_row.rows[0].v);
|
|
139
|
+
if (typeof expectedVersion === "number" && current_version !== expectedVersion) {
|
|
133
140
|
const { ConcurrencyError } = await import("@rotorsoft/act");
|
|
134
141
|
throw new ConcurrencyError(
|
|
135
142
|
stream,
|
|
136
|
-
|
|
143
|
+
current_version,
|
|
137
144
|
msgs,
|
|
138
145
|
expectedVersion
|
|
139
146
|
);
|
|
140
147
|
}
|
|
141
148
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
142
149
|
const committed = [];
|
|
143
|
-
let version =
|
|
144
|
-
for (const { name, data } of msgs) {
|
|
150
|
+
let version = current_version + 1;
|
|
151
|
+
for (const { name, data, pii } of msgs) {
|
|
145
152
|
const result = await tx.execute({
|
|
146
|
-
sql: "INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, ?, ?, ?, ?, ?)",
|
|
153
|
+
sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
147
154
|
args: [
|
|
148
155
|
stream,
|
|
149
156
|
version,
|
|
150
157
|
name,
|
|
151
158
|
JSON.stringify(data),
|
|
152
159
|
JSON.stringify(meta),
|
|
153
|
-
now
|
|
160
|
+
now,
|
|
161
|
+
stringify_pii(pii)
|
|
154
162
|
]
|
|
155
163
|
});
|
|
156
164
|
committed.push({
|
|
@@ -160,7 +168,8 @@ var SqliteStore = class {
|
|
|
160
168
|
created: new Date(now),
|
|
161
169
|
name,
|
|
162
170
|
data,
|
|
163
|
-
meta
|
|
171
|
+
meta,
|
|
172
|
+
...pii == null ? {} : { pii }
|
|
164
173
|
});
|
|
165
174
|
version++;
|
|
166
175
|
}
|
|
@@ -227,7 +236,8 @@ var SqliteStore = class {
|
|
|
227
236
|
created: new Date(row.created),
|
|
228
237
|
name: row.name,
|
|
229
238
|
data: JSON.parse(row.data),
|
|
230
|
-
meta: JSON.parse(row.meta)
|
|
239
|
+
meta: JSON.parse(row.meta),
|
|
240
|
+
pii: parse_pii(row.pii)
|
|
231
241
|
})
|
|
232
242
|
);
|
|
233
243
|
count++;
|
|
@@ -283,10 +293,10 @@ var SqliteStore = class {
|
|
|
283
293
|
const tx = await this.client.transaction("write");
|
|
284
294
|
try {
|
|
285
295
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
286
|
-
const
|
|
296
|
+
const lane_clause = lane !== void 0 ? " AND lane = ?" : "";
|
|
287
297
|
const result = await tx.execute({
|
|
288
298
|
sql: `SELECT stream, source, at, priority, lane FROM streams
|
|
289
|
-
WHERE blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)${
|
|
299
|
+
WHERE blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)${lane_clause}
|
|
290
300
|
ORDER BY priority DESC, at ASC`,
|
|
291
301
|
args: lane !== void 0 ? [now, lane] : [now]
|
|
292
302
|
});
|
|
@@ -295,21 +305,21 @@ var SqliteStore = class {
|
|
|
295
305
|
const stream = row.stream;
|
|
296
306
|
const source = row.source;
|
|
297
307
|
const at = Number(row.at);
|
|
298
|
-
let
|
|
308
|
+
let has_events;
|
|
299
309
|
if (source) {
|
|
300
310
|
const check = await tx.execute({
|
|
301
311
|
sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' AND stream LIKE ? LIMIT 1`,
|
|
302
312
|
args: [at, streamPatternToLike(source)]
|
|
303
313
|
});
|
|
304
|
-
|
|
314
|
+
has_events = check.rows.length > 0;
|
|
305
315
|
} else {
|
|
306
316
|
const check = await tx.execute({
|
|
307
317
|
sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' LIMIT 1`,
|
|
308
318
|
args: [at]
|
|
309
319
|
});
|
|
310
|
-
|
|
320
|
+
has_events = check.rows.length > 0;
|
|
311
321
|
}
|
|
312
|
-
if (
|
|
322
|
+
if (has_events) {
|
|
313
323
|
candidates.push({
|
|
314
324
|
stream,
|
|
315
325
|
source: source ?? void 0,
|
|
@@ -396,7 +406,7 @@ var SqliteStore = class {
|
|
|
396
406
|
* plus positional args. Returns `"1"` (always true) when empty so
|
|
397
407
|
* callers can compose it unconditionally.
|
|
398
408
|
*/
|
|
399
|
-
|
|
409
|
+
_filter_clause(filter) {
|
|
400
410
|
const conditions = [];
|
|
401
411
|
const args = [];
|
|
402
412
|
if (filter.stream !== void 0) {
|
|
@@ -430,7 +440,7 @@ var SqliteStore = class {
|
|
|
430
440
|
}
|
|
431
441
|
// --- reset: transactional, accepts names or filter ---
|
|
432
442
|
async reset(input) {
|
|
433
|
-
const
|
|
443
|
+
const set_clause = `SET at = -1, retry = 0, blocked = 0, error = '',
|
|
434
444
|
leased_by = NULL, leased_until = NULL`;
|
|
435
445
|
const tx = await this.client.transaction("write");
|
|
436
446
|
try {
|
|
@@ -438,15 +448,15 @@ var SqliteStore = class {
|
|
|
438
448
|
if (Array.isArray(input)) {
|
|
439
449
|
for (const stream of input) {
|
|
440
450
|
const r = await tx.execute({
|
|
441
|
-
sql: `UPDATE streams ${
|
|
451
|
+
sql: `UPDATE streams ${set_clause} WHERE stream = ?`,
|
|
442
452
|
args: [stream]
|
|
443
453
|
});
|
|
444
454
|
count += r.rowsAffected;
|
|
445
455
|
}
|
|
446
456
|
} else {
|
|
447
|
-
const { clause, args } = this.
|
|
457
|
+
const { clause, args } = this._filter_clause(input);
|
|
448
458
|
const r = await tx.execute({
|
|
449
|
-
sql: `UPDATE streams ${
|
|
459
|
+
sql: `UPDATE streams ${set_clause} WHERE ${clause}`,
|
|
450
460
|
args
|
|
451
461
|
});
|
|
452
462
|
count = r.rowsAffected;
|
|
@@ -462,7 +472,7 @@ var SqliteStore = class {
|
|
|
462
472
|
// `retry = -1` so claim's post-bump returns retry=0 (first attempt),
|
|
463
473
|
// matching the InMemoryStore convention.
|
|
464
474
|
async unblock(input) {
|
|
465
|
-
const
|
|
475
|
+
const set_clause = `SET retry = -1, blocked = 0, error = '',
|
|
466
476
|
leased_by = NULL, leased_until = NULL`;
|
|
467
477
|
const tx = await this.client.transaction("write");
|
|
468
478
|
try {
|
|
@@ -470,19 +480,19 @@ var SqliteStore = class {
|
|
|
470
480
|
if (Array.isArray(input)) {
|
|
471
481
|
for (const stream of input) {
|
|
472
482
|
const r = await tx.execute({
|
|
473
|
-
sql: `UPDATE streams ${
|
|
483
|
+
sql: `UPDATE streams ${set_clause}
|
|
474
484
|
WHERE stream = ? AND blocked = 1`,
|
|
475
485
|
args: [stream]
|
|
476
486
|
});
|
|
477
487
|
count += r.rowsAffected;
|
|
478
488
|
}
|
|
479
489
|
} else {
|
|
480
|
-
const { clause, args } = this.
|
|
490
|
+
const { clause, args } = this._filter_clause({
|
|
481
491
|
...input,
|
|
482
492
|
blocked: true
|
|
483
493
|
});
|
|
484
494
|
const r = await tx.execute({
|
|
485
|
-
sql: `UPDATE streams ${
|
|
495
|
+
sql: `UPDATE streams ${set_clause} WHERE ${clause}`,
|
|
486
496
|
args
|
|
487
497
|
});
|
|
488
498
|
count = r.rowsAffected;
|
|
@@ -581,11 +591,11 @@ var SqliteStore = class {
|
|
|
581
591
|
*/
|
|
582
592
|
async query_stats(input, options) {
|
|
583
593
|
const exclude = options?.exclude ?? [];
|
|
584
|
-
const
|
|
585
|
-
const
|
|
586
|
-
const
|
|
594
|
+
const want_tail = options?.tail ?? false;
|
|
595
|
+
const want_count = options?.count ?? false;
|
|
596
|
+
const want_names = options?.names ?? false;
|
|
587
597
|
const before = options?.before;
|
|
588
|
-
const
|
|
598
|
+
const full_scan = want_count || want_names;
|
|
589
599
|
if (Array.isArray(input) && input.length === 0) {
|
|
590
600
|
return /* @__PURE__ */ new Map();
|
|
591
601
|
}
|
|
@@ -613,55 +623,61 @@ var SqliteStore = class {
|
|
|
613
623
|
where.push(`e.id < ?`);
|
|
614
624
|
args.push(before);
|
|
615
625
|
}
|
|
616
|
-
const
|
|
617
|
-
const
|
|
618
|
-
return
|
|
619
|
-
|
|
620
|
-
|
|
626
|
+
const from_clause = `events e`;
|
|
627
|
+
const where_clause = `WHERE ${where.length ? where.join(" AND ") : "1=1"}`;
|
|
628
|
+
return full_scan ? this._query_stats_full_scan(
|
|
629
|
+
from_clause,
|
|
630
|
+
where_clause,
|
|
621
631
|
args,
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
) : this.
|
|
632
|
+
want_tail,
|
|
633
|
+
want_count,
|
|
634
|
+
want_names
|
|
635
|
+
) : this._query_stats_heads_only(
|
|
636
|
+
from_clause,
|
|
637
|
+
where_clause,
|
|
638
|
+
args,
|
|
639
|
+
want_tail
|
|
640
|
+
);
|
|
626
641
|
}
|
|
627
642
|
/**
|
|
628
643
|
* Cheap path — head (and optional tail) via ROW_NUMBER() over the
|
|
629
644
|
* `(stream, version)` unique index. Parallel queries when tail set.
|
|
630
645
|
*/
|
|
631
|
-
async
|
|
632
|
-
const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;
|
|
633
|
-
const
|
|
646
|
+
async _query_stats_heads_only(from_clause, where_clause, args, want_tail) {
|
|
647
|
+
const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta, e.pii`;
|
|
648
|
+
const head_sql = `SELECT * FROM (
|
|
634
649
|
SELECT ${cols}, ROW_NUMBER() OVER (PARTITION BY e.stream ORDER BY e.version DESC) AS rn
|
|
635
|
-
FROM ${
|
|
636
|
-
${
|
|
650
|
+
FROM ${from_clause}
|
|
651
|
+
${where_clause}
|
|
637
652
|
) WHERE rn = 1`;
|
|
638
|
-
const
|
|
653
|
+
const tail_sql = want_tail ? `SELECT * FROM (
|
|
639
654
|
SELECT ${cols}, ROW_NUMBER() OVER (PARTITION BY e.stream ORDER BY e.version ASC) AS rn
|
|
640
|
-
FROM ${
|
|
641
|
-
${
|
|
655
|
+
FROM ${from_clause}
|
|
656
|
+
${where_clause}
|
|
642
657
|
) WHERE rn = 1` : null;
|
|
643
658
|
const [headRes, tailRes] = await Promise.all([
|
|
644
|
-
this.client.execute({ sql:
|
|
645
|
-
|
|
659
|
+
this.client.execute({ sql: head_sql, args }),
|
|
660
|
+
tail_sql ? this.client.execute({ sql: tail_sql, args }) : null
|
|
646
661
|
]);
|
|
647
|
-
const
|
|
662
|
+
const to_committed = (row) => ({
|
|
648
663
|
id: Number(row.id),
|
|
649
664
|
stream: row.stream,
|
|
650
665
|
version: Number(row.version),
|
|
651
666
|
name: row.name,
|
|
652
667
|
data: JSON.parse(row.data),
|
|
653
668
|
meta: JSON.parse(row.meta),
|
|
654
|
-
created: new Date(row.created)
|
|
669
|
+
created: new Date(row.created),
|
|
670
|
+
pii: parse_pii(row.pii)
|
|
655
671
|
});
|
|
656
672
|
const out = /* @__PURE__ */ new Map();
|
|
657
673
|
for (const row of headRes.rows) {
|
|
658
674
|
out.set(row.stream, {
|
|
659
|
-
head:
|
|
675
|
+
head: to_committed(row)
|
|
660
676
|
});
|
|
661
677
|
}
|
|
662
678
|
if (tailRes) {
|
|
663
679
|
for (const row of tailRes.rows) {
|
|
664
|
-
out.get(row.stream).tail =
|
|
680
|
+
out.get(row.stream).tail = to_committed(row);
|
|
665
681
|
}
|
|
666
682
|
}
|
|
667
683
|
return out;
|
|
@@ -671,20 +687,20 @@ var SqliteStore = class {
|
|
|
671
687
|
* `json_group_object(name, n)`. Heads (and optional tails) ride free
|
|
672
688
|
* on the same scan.
|
|
673
689
|
*/
|
|
674
|
-
async
|
|
675
|
-
const
|
|
690
|
+
async _query_stats_full_scan(from_clause, where_clause, args, want_tail, want_count, want_names) {
|
|
691
|
+
const tail_cte = want_tail ? `, tails AS (
|
|
676
692
|
SELECT * FROM (
|
|
677
693
|
SELECT *, ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version ASC) AS rn FROM ef
|
|
678
694
|
) WHERE rn = 1
|
|
679
695
|
)` : "";
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta` : "";
|
|
696
|
+
const tail_join = want_tail ? `LEFT JOIN tails t ON t.stream = h.stream` : "";
|
|
697
|
+
const tail_cols = want_tail ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,
|
|
698
|
+
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` : "";
|
|
683
699
|
const sql = `
|
|
684
700
|
WITH ef AS (
|
|
685
|
-
SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta
|
|
686
|
-
FROM ${
|
|
687
|
-
${
|
|
701
|
+
SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta, e.pii
|
|
702
|
+
FROM ${from_clause}
|
|
703
|
+
${where_clause}
|
|
688
704
|
),
|
|
689
705
|
agg AS (
|
|
690
706
|
SELECT stream,
|
|
@@ -702,60 +718,63 @@ var SqliteStore = class {
|
|
|
702
718
|
SELECT *, ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version DESC) AS rn FROM ef
|
|
703
719
|
) WHERE rn = 1
|
|
704
720
|
)
|
|
705
|
-
${
|
|
721
|
+
${tail_cte}
|
|
706
722
|
SELECT
|
|
707
|
-
h.id, h.stream, h.version, h.name, h.data, h.created, h.meta,
|
|
723
|
+
h.id, h.stream, h.version, h.name, h.data, h.created, h.meta, h.pii,
|
|
708
724
|
a.cnt AS agg_count,
|
|
709
725
|
a.names AS agg_names
|
|
710
|
-
${
|
|
726
|
+
${tail_cols}
|
|
711
727
|
FROM heads h
|
|
712
728
|
LEFT JOIN agg a ON a.stream = h.stream
|
|
713
|
-
${
|
|
729
|
+
${tail_join}
|
|
714
730
|
`;
|
|
715
731
|
const res = await this.client.execute({ sql, args });
|
|
716
|
-
const
|
|
732
|
+
const to_committed = (id, stream, version, name, data, meta, created, pii) => ({
|
|
717
733
|
id: Number(id),
|
|
718
734
|
stream,
|
|
719
735
|
version: Number(version),
|
|
720
736
|
name,
|
|
721
737
|
data: JSON.parse(data),
|
|
722
738
|
meta: JSON.parse(meta),
|
|
723
|
-
created: new Date(created)
|
|
739
|
+
created: new Date(created),
|
|
740
|
+
pii: parse_pii(pii)
|
|
724
741
|
});
|
|
725
742
|
const out = /* @__PURE__ */ new Map();
|
|
726
743
|
for (const row of res.rows) {
|
|
727
744
|
const r = row;
|
|
728
745
|
const stats = {
|
|
729
|
-
head:
|
|
746
|
+
head: to_committed(
|
|
730
747
|
r.id,
|
|
731
748
|
r.stream,
|
|
732
749
|
r.version,
|
|
733
750
|
r.name,
|
|
734
751
|
r.data,
|
|
735
752
|
r.meta,
|
|
736
|
-
r.created
|
|
753
|
+
r.created,
|
|
754
|
+
r.pii
|
|
737
755
|
)
|
|
738
756
|
};
|
|
739
|
-
if (
|
|
740
|
-
stats.tail =
|
|
757
|
+
if (want_tail && r.t_id !== null && r.t_id !== void 0) {
|
|
758
|
+
stats.tail = to_committed(
|
|
741
759
|
r.t_id,
|
|
742
760
|
r.t_stream,
|
|
743
761
|
r.t_version,
|
|
744
762
|
r.t_name,
|
|
745
763
|
r.t_data,
|
|
746
764
|
r.t_meta,
|
|
747
|
-
r.t_created
|
|
765
|
+
r.t_created,
|
|
766
|
+
r.t_pii
|
|
748
767
|
);
|
|
749
768
|
}
|
|
750
|
-
if (
|
|
751
|
-
if (
|
|
769
|
+
if (want_count) stats.count = Number(r.agg_count);
|
|
770
|
+
if (want_names) stats.names = JSON.parse(r.agg_names);
|
|
752
771
|
out.set(r.stream, stats);
|
|
753
772
|
}
|
|
754
773
|
return out;
|
|
755
774
|
}
|
|
756
775
|
// --- prioritize: bulk priority update with filter (ACT-102) ---
|
|
757
776
|
async prioritize(filter, priority) {
|
|
758
|
-
const { clause, args: filterArgs } = this.
|
|
777
|
+
const { clause, args: filterArgs } = this._filter_clause(filter);
|
|
759
778
|
const sql = `UPDATE streams SET priority = ?
|
|
760
779
|
WHERE priority <> ? AND ${clause}`;
|
|
761
780
|
const result = await this.client.execute({
|
|
@@ -770,11 +789,11 @@ var SqliteStore = class {
|
|
|
770
789
|
const tx = await this.client.transaction("write");
|
|
771
790
|
try {
|
|
772
791
|
for (const { stream, snapshot, meta } of targets) {
|
|
773
|
-
const
|
|
792
|
+
const count_row = await tx.execute({
|
|
774
793
|
sql: "SELECT COUNT(*) as c FROM events WHERE stream = ?",
|
|
775
794
|
args: [stream]
|
|
776
795
|
});
|
|
777
|
-
const deleted = Number(
|
|
796
|
+
const deleted = Number(count_row.rows[0].c);
|
|
778
797
|
await tx.execute({
|
|
779
798
|
sql: "DELETE FROM events WHERE stream = ?",
|
|
780
799
|
args: [stream]
|
|
@@ -783,16 +802,16 @@ var SqliteStore = class {
|
|
|
783
802
|
sql: "DELETE FROM streams WHERE stream = ?",
|
|
784
803
|
args: [stream]
|
|
785
804
|
});
|
|
786
|
-
const
|
|
787
|
-
const
|
|
805
|
+
const event_name = snapshot !== void 0 ? "__snapshot__" : "__tombstone__";
|
|
806
|
+
const event_meta = meta ?? { correlation: "", causation: {} };
|
|
788
807
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
789
808
|
const ins = await tx.execute({
|
|
790
809
|
sql: "INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, 0, ?, ?, ?, ?)",
|
|
791
810
|
args: [
|
|
792
811
|
stream,
|
|
793
|
-
|
|
812
|
+
event_name,
|
|
794
813
|
JSON.stringify(snapshot ?? {}),
|
|
795
|
-
JSON.stringify(
|
|
814
|
+
JSON.stringify(event_meta),
|
|
796
815
|
now
|
|
797
816
|
]
|
|
798
817
|
});
|
|
@@ -803,9 +822,9 @@ var SqliteStore = class {
|
|
|
803
822
|
stream,
|
|
804
823
|
version: 0,
|
|
805
824
|
created: new Date(now),
|
|
806
|
-
name:
|
|
825
|
+
name: event_name,
|
|
807
826
|
data: snapshot ?? {},
|
|
808
|
-
meta:
|
|
827
|
+
meta: event_meta
|
|
809
828
|
}
|
|
810
829
|
});
|
|
811
830
|
}
|
|
@@ -835,14 +854,15 @@ var SqliteStore = class {
|
|
|
835
854
|
await tx.execute("DELETE FROM sqlite_sequence WHERE name = 'events'");
|
|
836
855
|
await driver(async (event) => {
|
|
837
856
|
const ins = await tx.execute({
|
|
838
|
-
sql: "INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, ?, ?, ?, ?, ?)",
|
|
857
|
+
sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
839
858
|
args: [
|
|
840
859
|
event.stream,
|
|
841
860
|
event.version,
|
|
842
861
|
event.name,
|
|
843
862
|
JSON.stringify(event.data),
|
|
844
863
|
JSON.stringify(event.meta),
|
|
845
|
-
event.created.toISOString()
|
|
864
|
+
event.created.toISOString(),
|
|
865
|
+
stringify_pii(event.pii)
|
|
846
866
|
]
|
|
847
867
|
});
|
|
848
868
|
return Number(ins.lastInsertRowid);
|
|
@@ -853,6 +873,30 @@ var SqliteStore = class {
|
|
|
853
873
|
throw error;
|
|
854
874
|
}
|
|
855
875
|
}
|
|
876
|
+
/**
|
|
877
|
+
* Wipe the sensitive-data payload for every event on the stream — the
|
|
878
|
+
* physical-erasure side of the sensitive-data epic (#566). Sets
|
|
879
|
+
* `events.pii` to `NULL` for the stream's events; `events.data` and
|
|
880
|
+
* the rest of the row are never touched.
|
|
881
|
+
*
|
|
882
|
+
* Single `UPDATE` under SQLite's writer lock, bounded by events-per-
|
|
883
|
+
* stream. Idempotent — the `pii IS NOT NULL` predicate filters out
|
|
884
|
+
* already-wiped rows so a second call returns `0`.
|
|
885
|
+
*
|
|
886
|
+
* SQLite doesn't auto-reclaim space; freed pages stay in the file
|
|
887
|
+
* until an operator-scheduled `PRAGMA incremental_vacuum` or a full
|
|
888
|
+
* `VACUUM`. The production checklist documents the cadence.
|
|
889
|
+
*
|
|
890
|
+
* @param stream Target stream
|
|
891
|
+
* @returns Count of events whose `pii` was set to `NULL`
|
|
892
|
+
*/
|
|
893
|
+
async forget_pii(stream) {
|
|
894
|
+
const r = await this.client.execute({
|
|
895
|
+
sql: "UPDATE events SET pii = NULL WHERE stream = ? AND pii IS NOT NULL",
|
|
896
|
+
args: [stream]
|
|
897
|
+
});
|
|
898
|
+
return r.rowsAffected ?? 0;
|
|
899
|
+
}
|
|
856
900
|
};
|
|
857
901
|
// Annotate the CommonJS export names for ESM import in node:
|
|
858
902
|
0 && (module.exports = {
|