@rotorsoft/act-tck 1.26.9 → 1.26.10
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/store-differential-tck.d.ts +57 -8
- package/dist/@types/store-differential-tck.d.ts.map +1 -1
- package/dist/@types/store-tck.d.ts.map +1 -1
- package/dist/index.cjs +377 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +377 -40
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -14,6 +14,32 @@ export type DifferentialStore = {
|
|
|
14
14
|
*/
|
|
15
15
|
readonly factory: () => Store | Promise<Store>;
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Known cross-adapter divergences the caller wants the differential to
|
|
19
|
+
* skip until the backing fix lands. Each flag suppresses exactly the
|
|
20
|
+
* assertions that a currently-filed bug makes red, so the differential
|
|
21
|
+
* stays green on master and the gate un-gates itself (delete the flag)
|
|
22
|
+
* when the fix merges. Every flag names its issue so the reason is
|
|
23
|
+
* auditable at the call site.
|
|
24
|
+
*/
|
|
25
|
+
export type DifferentialSkips = {
|
|
26
|
+
/**
|
|
27
|
+
* #1197 — SQLite `LIKE` is ASCII-case-insensitive, so a **mixed-case
|
|
28
|
+
* regex pattern** filter overmatches vs PG (`~`) / InMemory (`RegExp`),
|
|
29
|
+
* which are case-sensitive. Skips only the pattern-driven mixed-case
|
|
30
|
+
* assertions; exact-match (`stream_exact`) mixed-case still runs, since
|
|
31
|
+
* that path is case-exact on every adapter. Un-gate when #1197 lands.
|
|
32
|
+
*/
|
|
33
|
+
readonly caseInsensitivePatterns?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* #1199 — `names: []` and falsy-zero `before`/`after: 0` guards differ
|
|
36
|
+
* across adapters (`names: []` → PG returns all, InMemory/SQLite return
|
|
37
|
+
* none; `before: 0` / `after: 0` are dropped by some adapters' truthy
|
|
38
|
+
* guards). Skips only those specific edge-input query assertions.
|
|
39
|
+
* Un-gate when #1199 lands.
|
|
40
|
+
*/
|
|
41
|
+
readonly queryEdgeInputs?: boolean;
|
|
42
|
+
};
|
|
17
43
|
/**
|
|
18
44
|
* Options for {@link runStoreDifferentialTck}.
|
|
19
45
|
*/
|
|
@@ -46,15 +72,36 @@ export type StoreDifferentialTckOptions = {
|
|
|
46
72
|
* explores; fewer keep durable-adapter suites fast. Default `8`.
|
|
47
73
|
*/
|
|
48
74
|
readonly runs?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Whether the comparand adapters support the optional {@link Store.forget_pii}
|
|
77
|
+
* surface (and the `pii` field on commit). When `true`, the workload
|
|
78
|
+
* commits PII-bearing events and the differential asserts that
|
|
79
|
+
* `forget_pii` wipes them identically across adapters. All in-tree
|
|
80
|
+
* adapters implement it, so it defaults to `true`; a third-party store
|
|
81
|
+
* that opts out sets it `false`. Default `true`.
|
|
82
|
+
*/
|
|
83
|
+
readonly piiIsolation?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Known-divergence gates — see {@link DifferentialSkips}. Each flag
|
|
86
|
+
* suppresses the assertions a currently-filed bug makes red so the
|
|
87
|
+
* differential stays green until the fix merges. Default: nothing
|
|
88
|
+
* skipped.
|
|
89
|
+
*/
|
|
90
|
+
readonly skip?: DifferentialSkips;
|
|
49
91
|
};
|
|
50
92
|
/**
|
|
51
|
-
* Cross-adapter differential contract (#1030, fuzz workloads #1057
|
|
93
|
+
* Cross-adapter differential contract (#1030, fuzz workloads #1057,
|
|
94
|
+
* lease/truncate/pii/query fuzz #1200).
|
|
52
95
|
*
|
|
53
96
|
* Adapters can drift in ways per-adapter cases don't catch — event
|
|
54
97
|
* ordering, the `with_snaps` snapshot floor, the exact shape of
|
|
55
|
-
* `query_stats` / `query_streams` output
|
|
56
|
-
*
|
|
57
|
-
*
|
|
98
|
+
* `query_stats` / `query_streams` output, the lease lifecycle's effect on
|
|
99
|
+
* a stream's `retry`/`blocked`/`error`, windowed-truncate boundaries,
|
|
100
|
+
* `forget_pii` erasure, and query-option edge cases. This harness drives a
|
|
101
|
+
* **family of randomized, seeded workloads** — commits (some PII-bearing),
|
|
102
|
+
* inline + windowed truncates, subscriptions, and the full lease lifecycle
|
|
103
|
+
* (`claim` / `ack` / `defer` / `block` / `unblock` / `reset` /
|
|
104
|
+
* `prioritize`) — against every store in `options.stores`, then asserts
|
|
58
105
|
* their **normalized** outputs are byte-for-byte identical for every
|
|
59
106
|
* workload.
|
|
60
107
|
*
|
|
@@ -65,10 +112,12 @@ export type StoreDifferentialTckOptions = {
|
|
|
65
112
|
*
|
|
66
113
|
* Normalization drops only the fields that legitimately differ between
|
|
67
114
|
* stores (absolute event ids, `created` timestamps, correlation/causation
|
|
68
|
-
* uuids
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
115
|
+
* uuids, lease holder UUIDs, and wall-clock lease expiries) and keeps
|
|
116
|
+
* everything that defines correctness (stream, version, name, data,
|
|
117
|
+
* emission order, and the durable subscription state a lease op leaves
|
|
118
|
+
* behind: watermark, retry, blocked, error, priority, lane). Stream names
|
|
119
|
+
* carry mixed case so a case-insensitive pattern filter (#1197) surfaces as
|
|
120
|
+
* a diff.
|
|
72
121
|
*
|
|
73
122
|
* Wire it with the in-memory store as the reference and one or more
|
|
74
123
|
* durable adapters as comparands:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-differential-tck.d.ts","sourceRoot":"","sources":["../../src/store-differential-tck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"store-differential-tck.d.ts","sourceRoot":"","sources":["../../src/store-differential-tck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAKV,KAAK,EACN,MAAM,sBAAsB,CAAC;AAY9B;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;CAChD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IAClD;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAC;CACnC,CAAC;AAgfF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,uBAAuB,GAClC,SAAS,2BAA2B,KACnC,IA0RF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-tck.d.ts","sourceRoot":"","sources":["../../src/store-tck.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAQV,KAAK,EAEN,MAAM,sBAAsB,CAAC;AAc9B;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;OASG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,gCAAgC,CAAC,EAAE,OAAO,CAAC;CACrD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/C;;OAEG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAC3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,eAAe,KAAG,
|
|
1
|
+
{"version":3,"file":"store-tck.d.ts","sourceRoot":"","sources":["../../src/store-tck.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAQV,KAAK,EAEN,MAAM,sBAAsB,CAAC;AAc9B;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;OASG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,gCAAgC,CAAC,EAAE,OAAO,CAAC;CACrD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/C;;OAEG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAC3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,eAAe,KAAG,IAurHtD,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -448,20 +448,28 @@ var mulberry322 = (seed) => {
|
|
|
448
448
|
};
|
|
449
449
|
};
|
|
450
450
|
var normalize_event = (e) => ({
|
|
451
|
+
stream: e.stream,
|
|
452
|
+
version: e.version,
|
|
453
|
+
name: e.name,
|
|
454
|
+
data: e.data,
|
|
455
|
+
pii: e.pii != null
|
|
456
|
+
});
|
|
457
|
+
var normalize_identity = (e) => ({
|
|
451
458
|
stream: e.stream,
|
|
452
459
|
version: e.version,
|
|
453
460
|
name: e.name,
|
|
454
461
|
data: e.data
|
|
455
462
|
});
|
|
456
|
-
var build_plan = (seed, stream_count) => {
|
|
463
|
+
var build_plan = (seed, stream_count, pii_isolation) => {
|
|
457
464
|
const rng = mulberry322(seed);
|
|
458
465
|
const tag = uid();
|
|
459
|
-
const event_prefix = `
|
|
460
|
-
const sub_prefix = `
|
|
466
|
+
const event_prefix = `Diff-${tag}-Evt-`;
|
|
467
|
+
const sub_prefix = `Diff-${tag}-Sub-`;
|
|
461
468
|
const event_streams = Array.from(
|
|
462
469
|
{ length: stream_count },
|
|
463
|
-
(_, i) => `${event_prefix}${i}`
|
|
470
|
+
(_, i) => `${event_prefix}S${i}`
|
|
464
471
|
);
|
|
472
|
+
const correlation = `corr-${tag}`;
|
|
465
473
|
let type_cursor = 0;
|
|
466
474
|
const next_msg = () => {
|
|
467
475
|
const kind = type_cursor++ % 3;
|
|
@@ -470,65 +478,246 @@ var build_plan = (seed, stream_count) => {
|
|
|
470
478
|
};
|
|
471
479
|
const batch = () => Array.from({ length: 1 + Math.floor(rng() * 3) }, next_msg);
|
|
472
480
|
const pick_stream = () => event_streams[Math.floor(rng() * event_streams.length)];
|
|
473
|
-
const ops = [];
|
|
474
|
-
for (const stream of event_streams)
|
|
475
|
-
ops.push({ t: "commit", stream, msgs: batch() });
|
|
476
|
-
const middle = 8 + Math.floor(rng() * 16);
|
|
477
|
-
for (let i = 0; i < middle; i++) {
|
|
478
|
-
const stream = pick_stream();
|
|
479
|
-
const kind = Math.floor(rng() * 3);
|
|
480
|
-
if (kind === 0) ops.push({ t: "commit", stream, msgs: batch() });
|
|
481
|
-
else if (kind === 1)
|
|
482
|
-
ops.push({ t: "snapshot", stream, count: Math.floor(rng() * 1e3) });
|
|
483
|
-
else ops.push({ t: "truncate", stream, count: Math.floor(rng() * 1e3) });
|
|
484
|
-
}
|
|
485
|
-
for (const stream of event_streams)
|
|
486
|
-
ops.push({ t: "commit", stream, msgs: batch() });
|
|
487
481
|
const lanes = ["default", "slow", "fast"];
|
|
488
|
-
const
|
|
489
|
-
|
|
482
|
+
const lease_prefix = `Diff-${tag}-Src-`;
|
|
483
|
+
const lease_sources = event_streams.map((_, i) => `${lease_prefix}S${i}`);
|
|
484
|
+
const subs = lease_sources.map((source, i) => ({
|
|
485
|
+
stream: `${sub_prefix}S${i}`,
|
|
490
486
|
source,
|
|
491
487
|
lane: lanes[i % lanes.length],
|
|
492
488
|
priority: i % 4
|
|
493
489
|
}));
|
|
494
|
-
|
|
490
|
+
const pick_sub = () => Math.floor(rng() * subs.length);
|
|
491
|
+
const ops = [];
|
|
492
|
+
const pii_stream = event_streams[0];
|
|
493
|
+
event_streams.forEach((stream, i) => {
|
|
494
|
+
ops.push({
|
|
495
|
+
t: "commit",
|
|
496
|
+
stream,
|
|
497
|
+
msgs: batch(),
|
|
498
|
+
pii: pii_isolation && i === 0 ? { email: `u-${tag}@example.com`, name: "Ursula" } : void 0
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
for (const src of lease_sources)
|
|
502
|
+
ops.push({ t: "commit", stream: src, msgs: [inc(1)] });
|
|
503
|
+
const middle = 12 + Math.floor(rng() * 20);
|
|
504
|
+
for (let i = 0; i < middle; i++) {
|
|
505
|
+
const kind = Math.floor(rng() * 10);
|
|
506
|
+
if (kind === 0) {
|
|
507
|
+
ops.push({ t: "commit", stream: pick_stream(), msgs: batch() });
|
|
508
|
+
} else if (kind === 1) {
|
|
509
|
+
ops.push({
|
|
510
|
+
t: "snapshot",
|
|
511
|
+
stream: pick_stream(),
|
|
512
|
+
count: Math.floor(rng() * 1e3)
|
|
513
|
+
});
|
|
514
|
+
} else if (kind === 2) {
|
|
515
|
+
ops.push({
|
|
516
|
+
t: "truncate",
|
|
517
|
+
stream: pick_stream(),
|
|
518
|
+
count: Math.floor(rng() * 1e3)
|
|
519
|
+
});
|
|
520
|
+
} else if (kind === 3) {
|
|
521
|
+
ops.push({
|
|
522
|
+
t: "truncate_windowed",
|
|
523
|
+
stream: pick_stream(),
|
|
524
|
+
max_id: rng() < 0.5
|
|
525
|
+
});
|
|
526
|
+
} else if (kind === 4) {
|
|
527
|
+
ops.push({ t: "claim", millis: 1e5 });
|
|
528
|
+
} else if (kind === 5) {
|
|
529
|
+
ops.push({ t: "ack", sub: pick_sub() });
|
|
530
|
+
} else if (kind === 6) {
|
|
531
|
+
ops.push({
|
|
532
|
+
t: "defer",
|
|
533
|
+
sub: pick_sub(),
|
|
534
|
+
due: 41024448e5 + Math.floor(rng() * 1e3)
|
|
535
|
+
});
|
|
536
|
+
} else if (kind === 7) {
|
|
537
|
+
ops.push({
|
|
538
|
+
t: "block",
|
|
539
|
+
sub: pick_sub(),
|
|
540
|
+
error: `boom-${Math.floor(rng() * 100)}`
|
|
541
|
+
});
|
|
542
|
+
} else if (kind === 8) {
|
|
543
|
+
ops.push({ t: "unblock", sub: pick_sub() });
|
|
544
|
+
} else {
|
|
545
|
+
if (rng() < 0.5) ops.push({ t: "reset_sub", sub: pick_sub() });
|
|
546
|
+
else
|
|
547
|
+
ops.push({
|
|
548
|
+
t: "prioritize",
|
|
549
|
+
sub: pick_sub(),
|
|
550
|
+
priority: Math.floor(rng() * 10)
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const filter_name = "Incremented";
|
|
555
|
+
for (const stream of event_streams)
|
|
556
|
+
ops.push({ t: "commit", stream, msgs: [inc(1)] });
|
|
557
|
+
const query_cases = [
|
|
558
|
+
{
|
|
559
|
+
label: "names filter (single event name)",
|
|
560
|
+
query: { stream: `^${event_prefix}`, names: [filter_name] }
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
label: "correlation filter",
|
|
564
|
+
query: { stream: `^${event_prefix}`, correlation }
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
label: "limit bound",
|
|
568
|
+
query: { stream: `^${event_prefix}`, limit: 3 }
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
label: "backward + limit",
|
|
572
|
+
query: { stream: `^${event_prefix}`, backward: true, limit: 4 }
|
|
573
|
+
},
|
|
574
|
+
// Absolute `id` bounds (`after`/`before` with positive ids) are
|
|
575
|
+
// deliberately absent: event ids are the canonical "legitimately
|
|
576
|
+
// differs between adapters" field (see `normalize_event`), and full
|
|
577
|
+
// truncates renumber them differently per store, so a positive id
|
|
578
|
+
// bound selects a different absolute window on each adapter. The
|
|
579
|
+
// gated falsy-zero cases below compare only the *guard* behavior
|
|
580
|
+
// (filter applied → subset, vs filter dropped → all), which stays a
|
|
581
|
+
// well-defined divergence regardless of the concrete id values.
|
|
582
|
+
// #1199 edge inputs — opposite semantics across adapters today.
|
|
583
|
+
{
|
|
584
|
+
label: "empty names filter",
|
|
585
|
+
query: { stream: `^${event_prefix}`, names: [] },
|
|
586
|
+
gated: "queryEdgeInputs"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
label: "before: 0 (falsy-zero guard)",
|
|
590
|
+
query: { stream: `^${event_prefix}`, before: 0 },
|
|
591
|
+
gated: "queryEdgeInputs"
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
label: "backward + after: 0 (falsy-zero guard)",
|
|
595
|
+
query: { stream: `^${event_prefix}`, backward: true, after: 0 },
|
|
596
|
+
gated: "queryEdgeInputs"
|
|
597
|
+
}
|
|
598
|
+
];
|
|
599
|
+
return {
|
|
600
|
+
event_prefix,
|
|
601
|
+
event_streams,
|
|
602
|
+
sub_prefix,
|
|
603
|
+
ops,
|
|
604
|
+
subs,
|
|
605
|
+
correlation,
|
|
606
|
+
pii_stream,
|
|
607
|
+
query_cases,
|
|
608
|
+
filter_name
|
|
609
|
+
};
|
|
495
610
|
};
|
|
496
|
-
var apply_plan = async (store, plan) => {
|
|
611
|
+
var apply_plan = async (store, plan, pii_isolation) => {
|
|
612
|
+
await store.subscribe(
|
|
613
|
+
plan.subs.map((s) => ({
|
|
614
|
+
stream: s.stream,
|
|
615
|
+
source: s.source,
|
|
616
|
+
lane: s.lane,
|
|
617
|
+
priority: s.priority
|
|
618
|
+
}))
|
|
619
|
+
);
|
|
620
|
+
const held = /* @__PURE__ */ new Map();
|
|
621
|
+
const owned = new Set(plan.subs.map((s) => s.stream));
|
|
497
622
|
for (const op of plan.ops) {
|
|
498
623
|
if (op.t === "commit") {
|
|
624
|
+
const msgs = op.pii && pii_isolation ? op.msgs.map((m, i) => i === 0 ? { ...m, pii: op.pii } : m) : op.msgs;
|
|
499
625
|
await store.commit(
|
|
500
626
|
op.stream,
|
|
501
|
-
|
|
502
|
-
make_meta({ stream: op.stream })
|
|
627
|
+
msgs,
|
|
628
|
+
make_meta({ stream: op.stream, correlation: plan.correlation })
|
|
503
629
|
);
|
|
504
630
|
} else if (op.t === "snapshot") {
|
|
505
631
|
await store.commit(
|
|
506
632
|
op.stream,
|
|
507
633
|
[{ name: import_act.SNAP_EVENT, data: { count: op.count } }],
|
|
508
|
-
make_meta({ stream: op.stream })
|
|
634
|
+
make_meta({ stream: op.stream, correlation: plan.correlation })
|
|
509
635
|
);
|
|
510
|
-
} else {
|
|
636
|
+
} else if (op.t === "truncate") {
|
|
511
637
|
await store.truncate([
|
|
512
638
|
{ stream: op.stream, snapshot: { count: op.count } }
|
|
513
639
|
]);
|
|
640
|
+
} else if (op.t === "truncate_windowed") {
|
|
641
|
+
await store.commit(
|
|
642
|
+
op.stream,
|
|
643
|
+
[{ name: import_act.SNAP_EVENT, data: { count: 1 } }],
|
|
644
|
+
make_meta({ stream: op.stream, correlation: plan.correlation })
|
|
645
|
+
);
|
|
646
|
+
await store.truncate([
|
|
647
|
+
{
|
|
648
|
+
stream: op.stream,
|
|
649
|
+
before: new Date(Date.now() + 6e4),
|
|
650
|
+
// A wide cap that still fits a 32-bit event-id column, so it
|
|
651
|
+
// never excludes the boundary snapshot on any adapter.
|
|
652
|
+
...op.max_id ? { max_id: 2147483647 } : {}
|
|
653
|
+
}
|
|
654
|
+
]);
|
|
655
|
+
} else if (op.t === "claim") {
|
|
656
|
+
const by = uid();
|
|
657
|
+
const leased = await store.claim(1e5, 1e5, by, op.millis);
|
|
658
|
+
for (const l of leased) if (owned.has(l.stream)) held.set(l.stream, by);
|
|
659
|
+
} else if (op.t === "ack") {
|
|
660
|
+
const stream = plan.subs[op.sub].stream;
|
|
661
|
+
const by = held.get(stream);
|
|
662
|
+
if (by) {
|
|
663
|
+
await store.ack([{ stream, at: 0, by, retry: 0, lagging: true }]);
|
|
664
|
+
held.delete(stream);
|
|
665
|
+
}
|
|
666
|
+
} else if (op.t === "defer") {
|
|
667
|
+
const stream = plan.subs[op.sub].stream;
|
|
668
|
+
const by = held.get(stream);
|
|
669
|
+
if (by) {
|
|
670
|
+
const lease = {
|
|
671
|
+
stream,
|
|
672
|
+
at: 0,
|
|
673
|
+
by,
|
|
674
|
+
retry: 0,
|
|
675
|
+
lagging: true,
|
|
676
|
+
due: op.due
|
|
677
|
+
};
|
|
678
|
+
await store.ack([lease]);
|
|
679
|
+
held.delete(stream);
|
|
680
|
+
}
|
|
681
|
+
} else if (op.t === "block") {
|
|
682
|
+
const stream = plan.subs[op.sub].stream;
|
|
683
|
+
const by = held.get(stream);
|
|
684
|
+
if (by) {
|
|
685
|
+
const lease = {
|
|
686
|
+
stream,
|
|
687
|
+
at: 0,
|
|
688
|
+
by,
|
|
689
|
+
retry: 0,
|
|
690
|
+
lagging: true,
|
|
691
|
+
error: op.error
|
|
692
|
+
};
|
|
693
|
+
await store.block([lease]);
|
|
694
|
+
held.delete(stream);
|
|
695
|
+
}
|
|
696
|
+
} else if (op.t === "unblock") {
|
|
697
|
+
const stream = plan.subs[op.sub].stream;
|
|
698
|
+
await store.unblock([stream]);
|
|
699
|
+
held.delete(stream);
|
|
700
|
+
} else if (op.t === "reset_sub") {
|
|
701
|
+
const stream = plan.subs[op.sub].stream;
|
|
702
|
+
await store.reset([stream]);
|
|
703
|
+
held.delete(stream);
|
|
704
|
+
} else {
|
|
705
|
+
await store.prioritize(
|
|
706
|
+
{ stream: plan.subs[op.sub].stream, stream_exact: true },
|
|
707
|
+
op.priority
|
|
708
|
+
);
|
|
514
709
|
}
|
|
515
710
|
}
|
|
516
|
-
await store.subscribe(
|
|
517
|
-
plan.subs.map((s) => ({
|
|
518
|
-
stream: s.stream,
|
|
519
|
-
source: s.source,
|
|
520
|
-
lane: s.lane,
|
|
521
|
-
priority: s.priority
|
|
522
|
-
}))
|
|
523
|
-
);
|
|
524
711
|
};
|
|
525
712
|
var runStoreDifferentialTck = (options) => {
|
|
526
713
|
(0, import_vitest6.describe)(`TCK / Store differential / ${options.name}`, () => {
|
|
527
714
|
const base_seed = options.seed ?? 2759;
|
|
528
715
|
const stream_count = options.streams ?? 4;
|
|
716
|
+
const pii_isolation = options.piiIsolation ?? true;
|
|
717
|
+
const skip = { ...options.skip };
|
|
529
718
|
const plans = Array.from(
|
|
530
719
|
{ length: options.runs ?? 8 },
|
|
531
|
-
(_, r) => build_plan(base_seed + r, stream_count)
|
|
720
|
+
(_, r) => build_plan(base_seed + r, stream_count, pii_isolation)
|
|
532
721
|
);
|
|
533
722
|
const live = [];
|
|
534
723
|
(0, import_vitest6.beforeAll)(async () => {
|
|
@@ -536,7 +725,7 @@ var runStoreDifferentialTck = (options) => {
|
|
|
536
725
|
const store = await spec.factory();
|
|
537
726
|
await store.drop();
|
|
538
727
|
await store.seed();
|
|
539
|
-
for (const plan of plans) await apply_plan(store, plan);
|
|
728
|
+
for (const plan of plans) await apply_plan(store, plan, pii_isolation);
|
|
540
729
|
live.push({ name: spec.name, store });
|
|
541
730
|
}
|
|
542
731
|
});
|
|
@@ -600,6 +789,21 @@ var runStoreDifferentialTck = (options) => {
|
|
|
600
789
|
return by_stream;
|
|
601
790
|
});
|
|
602
791
|
});
|
|
792
|
+
plan.query_cases.forEach((qc) => {
|
|
793
|
+
const gated = qc.gated && skip[qc.gated];
|
|
794
|
+
import_vitest6.it.skipIf(gated)(
|
|
795
|
+
`yields identical events under ${qc.label}`,
|
|
796
|
+
async () => {
|
|
797
|
+
await assert_identical(`query / ${qc.label}`, async (store) => {
|
|
798
|
+
const out = [];
|
|
799
|
+
await store.query((e) => {
|
|
800
|
+
out.push(normalize_event(e));
|
|
801
|
+
}, qc.query);
|
|
802
|
+
return out;
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
);
|
|
806
|
+
});
|
|
603
807
|
(0, import_vitest6.it)("yields identical query_stats output (head/tail/count/names)", async () => {
|
|
604
808
|
await assert_identical("query_stats", async (store) => {
|
|
605
809
|
const stats = await store.query_stats(
|
|
@@ -610,8 +814,8 @@ var runStoreDifferentialTck = (options) => {
|
|
|
610
814
|
const content = {};
|
|
611
815
|
for (const [stream, s] of stats) {
|
|
612
816
|
content[stream] = {
|
|
613
|
-
head:
|
|
614
|
-
tail:
|
|
817
|
+
head: normalize_identity(s.head),
|
|
818
|
+
tail: normalize_identity(
|
|
615
819
|
s.tail
|
|
616
820
|
),
|
|
617
821
|
count: s.count,
|
|
@@ -621,7 +825,7 @@ var runStoreDifferentialTck = (options) => {
|
|
|
621
825
|
return { keys, content };
|
|
622
826
|
});
|
|
623
827
|
});
|
|
624
|
-
(0, import_vitest6.it)("yields identical query_streams output", async () => {
|
|
828
|
+
(0, import_vitest6.it)("yields identical query_streams output (incl. lease-lifecycle state)", async () => {
|
|
625
829
|
await assert_identical("query_streams", async (store) => {
|
|
626
830
|
const rows = [];
|
|
627
831
|
const { count } = await store.query_streams(
|
|
@@ -632,7 +836,10 @@ var runStoreDifferentialTck = (options) => {
|
|
|
632
836
|
at: p.at,
|
|
633
837
|
blocked: p.blocked,
|
|
634
838
|
priority: p.priority,
|
|
635
|
-
lane: p.lane
|
|
839
|
+
lane: p.lane,
|
|
840
|
+
retry: p.retry,
|
|
841
|
+
error: p.error,
|
|
842
|
+
leased: p.leased_by != null
|
|
636
843
|
});
|
|
637
844
|
},
|
|
638
845
|
{ stream: `^${plan.sub_prefix}`, limit: 1e3 }
|
|
@@ -641,6 +848,60 @@ var runStoreDifferentialTck = (options) => {
|
|
|
641
848
|
return { count, rows };
|
|
642
849
|
});
|
|
643
850
|
});
|
|
851
|
+
import_vitest6.it.skipIf(skip.caseInsensitivePatterns)(
|
|
852
|
+
"matches case-sensitively under a lower-cased pattern filter",
|
|
853
|
+
async () => {
|
|
854
|
+
await assert_identical(
|
|
855
|
+
"query_streams lower-cased pattern",
|
|
856
|
+
async (store) => {
|
|
857
|
+
const collect_streams = async (pattern) => {
|
|
858
|
+
const streams = [];
|
|
859
|
+
await store.query_streams(
|
|
860
|
+
(p) => {
|
|
861
|
+
streams.push(p.stream);
|
|
862
|
+
},
|
|
863
|
+
{ stream: pattern, limit: 1e3 }
|
|
864
|
+
);
|
|
865
|
+
streams.sort((a, b) => a.localeCompare(b));
|
|
866
|
+
return streams;
|
|
867
|
+
};
|
|
868
|
+
const matched = await collect_streams(`^${plan.sub_prefix}S`);
|
|
869
|
+
const lowered = await collect_streams(
|
|
870
|
+
`^${plan.sub_prefix.toLowerCase()}s`
|
|
871
|
+
);
|
|
872
|
+
return { matched, lowered };
|
|
873
|
+
}
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
);
|
|
877
|
+
(0, import_vitest6.it)("yields identical events under an exact mixed-case stream lookup", async () => {
|
|
878
|
+
await assert_identical("query exact mixed-case", async (store) => {
|
|
879
|
+
const out = [];
|
|
880
|
+
await store.query(
|
|
881
|
+
(e) => {
|
|
882
|
+
out.push(normalize_event(e));
|
|
883
|
+
},
|
|
884
|
+
{ stream: plan.event_streams[0], stream_exact: true }
|
|
885
|
+
);
|
|
886
|
+
return out;
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
import_vitest6.it.skipIf(!pii_isolation)(
|
|
890
|
+
"erases pii identically via forget_pii",
|
|
891
|
+
async () => {
|
|
892
|
+
await assert_identical("forget_pii erase", async (store) => {
|
|
893
|
+
const erased = await store.forget_pii(plan.pii_stream);
|
|
894
|
+
const after = [];
|
|
895
|
+
await store.query(
|
|
896
|
+
(e) => {
|
|
897
|
+
after.push(normalize_event(e));
|
|
898
|
+
},
|
|
899
|
+
{ stream: plan.pii_stream, stream_exact: true }
|
|
900
|
+
);
|
|
901
|
+
return { erased, after };
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
);
|
|
644
905
|
});
|
|
645
906
|
});
|
|
646
907
|
});
|
|
@@ -1054,6 +1315,54 @@ var runStoreTck = (options) => {
|
|
|
1054
1315
|
committed.slice(0, -1).map((c) => c.id)
|
|
1055
1316
|
);
|
|
1056
1317
|
});
|
|
1318
|
+
(0, import_vitest9.it)("names:[] matches no events", async () => {
|
|
1319
|
+
const s = `q-names-empty-${uid()}`;
|
|
1320
|
+
await store.commit(
|
|
1321
|
+
s,
|
|
1322
|
+
[inc(1), dec(1)],
|
|
1323
|
+
make_meta({ stream: s })
|
|
1324
|
+
);
|
|
1325
|
+
const none = await collect(store, {
|
|
1326
|
+
stream: s,
|
|
1327
|
+
stream_exact: true,
|
|
1328
|
+
names: []
|
|
1329
|
+
});
|
|
1330
|
+
(0, import_vitest9.expect)(none).toHaveLength(0);
|
|
1331
|
+
const all = await collect(store, { stream: s, stream_exact: true });
|
|
1332
|
+
(0, import_vitest9.expect)(all).toHaveLength(2);
|
|
1333
|
+
});
|
|
1334
|
+
(0, import_vitest9.it)("after:0 and before:0 are honored as id bounds", async () => {
|
|
1335
|
+
const s = `q-zero-bound-${uid()}`;
|
|
1336
|
+
await store.commit(
|
|
1337
|
+
s,
|
|
1338
|
+
[inc(1), inc(2), inc(3)],
|
|
1339
|
+
make_meta({ stream: s })
|
|
1340
|
+
);
|
|
1341
|
+
(0, import_vitest9.expect)(
|
|
1342
|
+
await collect(store, { stream: s, stream_exact: true, before: 0 })
|
|
1343
|
+
).toHaveLength(0);
|
|
1344
|
+
(0, import_vitest9.expect)(
|
|
1345
|
+
await collect(store, {
|
|
1346
|
+
stream: s,
|
|
1347
|
+
stream_exact: true,
|
|
1348
|
+
before: 0,
|
|
1349
|
+
backward: true
|
|
1350
|
+
})
|
|
1351
|
+
).toHaveLength(0);
|
|
1352
|
+
const forward = await collect(store, {
|
|
1353
|
+
stream: s,
|
|
1354
|
+
stream_exact: true,
|
|
1355
|
+
after: 0
|
|
1356
|
+
});
|
|
1357
|
+
(0, import_vitest9.expect)(forward.every((e) => e.id > 0)).toBe(true);
|
|
1358
|
+
const backward = await collect(store, {
|
|
1359
|
+
stream: s,
|
|
1360
|
+
stream_exact: true,
|
|
1361
|
+
after: 0,
|
|
1362
|
+
backward: true
|
|
1363
|
+
});
|
|
1364
|
+
(0, import_vitest9.expect)(backward.every((e) => e.id > 0)).toBe(true);
|
|
1365
|
+
});
|
|
1057
1366
|
(0, import_vitest9.it)("created_after/created_before filter by timestamp", async () => {
|
|
1058
1367
|
const s = `q-ts-${uid()}`;
|
|
1059
1368
|
const committed = await store.commit(
|
|
@@ -1292,6 +1601,34 @@ var runStoreTck = (options) => {
|
|
|
1292
1601
|
if (error !== void 0) (0, import_vitest9.expect)(error).toBeInstanceOf(import_act2.ValidationError);
|
|
1293
1602
|
else (0, import_vitest9.expect)(count).toBe(1);
|
|
1294
1603
|
});
|
|
1604
|
+
(0, import_vitest9.it)("stream filters match case-sensitively (query)", async () => {
|
|
1605
|
+
const tag = uid();
|
|
1606
|
+
const lower = `order-${tag}`;
|
|
1607
|
+
const upper = `Order-${tag}`;
|
|
1608
|
+
await seed_streams([lower, upper]);
|
|
1609
|
+
(0, import_vitest9.expect)(await streams_matching(`^order-${tag}`)).toEqual([lower]);
|
|
1610
|
+
(0, import_vitest9.expect)(await streams_matching(`^Order-${tag}$`)).toEqual([upper]);
|
|
1611
|
+
(0, import_vitest9.expect)(await streams_matching(`order-${tag}`)).toEqual([lower]);
|
|
1612
|
+
});
|
|
1613
|
+
(0, import_vitest9.it)("stream filters match case-sensitively (position filters)", async () => {
|
|
1614
|
+
const tag = uid();
|
|
1615
|
+
const lower = `csp-order-${tag}`;
|
|
1616
|
+
const upper = `csp-Order-${tag}`;
|
|
1617
|
+
await store.subscribe([{ stream: lower }, { stream: upper }]);
|
|
1618
|
+
const got = [];
|
|
1619
|
+
await store.query_streams((p) => got.push(p.stream), {
|
|
1620
|
+
stream: `^csp-order-${tag}`
|
|
1621
|
+
});
|
|
1622
|
+
(0, import_vitest9.expect)(got).toEqual([lower]);
|
|
1623
|
+
});
|
|
1624
|
+
(0, import_vitest9.it)("bulk stream ops match case-sensitively", async () => {
|
|
1625
|
+
const tag = uid();
|
|
1626
|
+
const lower = `bcs-order-${tag}`;
|
|
1627
|
+
const upper = `bcs-Order-${tag}`;
|
|
1628
|
+
await store.subscribe([{ stream: lower }, { stream: upper }]);
|
|
1629
|
+
const count = await store.reset({ stream: `^bcs-order-${tag}` });
|
|
1630
|
+
(0, import_vitest9.expect)(count).toBe(1);
|
|
1631
|
+
});
|
|
1295
1632
|
});
|
|
1296
1633
|
(0, import_vitest9.describe)("subscribe + claim + ack", () => {
|
|
1297
1634
|
(0, import_vitest9.it)("subscribes new streams and is idempotent on repeat", async () => {
|