@rotorsoft/act-tck 1.17.0 → 1.18.0
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 +66 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/cache-differential-tck.d.ts +80 -0
- package/dist/@types/cache-differential-tck.d.ts.map +1 -0
- package/dist/@types/index.d.ts +6 -0
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/@types/logger-differential-tck.d.ts +61 -0
- package/dist/@types/logger-differential-tck.d.ts.map +1 -0
- package/dist/@types/store-differential-tck.d.ts +93 -0
- package/dist/@types/store-differential-tck.d.ts.map +1 -0
- package/dist/@types/store-tck.d.ts.map +1 -1
- package/dist/index.cjs +1001 -514
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +974 -490
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -1,5 +1,139 @@
|
|
|
1
|
+
// src/cache-differential-tck.ts
|
|
2
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
// src/fixtures/helpers.ts
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
var uid = () => randomUUID().slice(0, 8);
|
|
7
|
+
var actor = (name = "tester") => ({ id: randomUUID(), name });
|
|
8
|
+
var make_meta = (opts = {}) => ({
|
|
9
|
+
correlation: opts.correlation ?? randomUUID(),
|
|
10
|
+
causation: opts.stream ? {
|
|
11
|
+
action: {
|
|
12
|
+
name: opts.action ?? "Test",
|
|
13
|
+
stream: opts.stream,
|
|
14
|
+
actor: actor()
|
|
15
|
+
}
|
|
16
|
+
} : {}
|
|
17
|
+
});
|
|
18
|
+
var inc = (amount = 1) => ({
|
|
19
|
+
name: "Incremented",
|
|
20
|
+
data: { amount }
|
|
21
|
+
});
|
|
22
|
+
var dec = (amount = 1) => ({
|
|
23
|
+
name: "Decremented",
|
|
24
|
+
data: { amount }
|
|
25
|
+
});
|
|
26
|
+
var reset = () => ({ name: "Reset", data: {} });
|
|
27
|
+
var seed_stream = async (store, stream, count, correlation) => {
|
|
28
|
+
const out = [];
|
|
29
|
+
for (let i = 0; i < count; i++) {
|
|
30
|
+
const committed = await store.commit(
|
|
31
|
+
stream,
|
|
32
|
+
[inc(1)],
|
|
33
|
+
make_meta({ correlation, stream })
|
|
34
|
+
);
|
|
35
|
+
out.push(...committed);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
};
|
|
39
|
+
var collect = async (store, query) => {
|
|
40
|
+
const out = [];
|
|
41
|
+
await store.query((e) => {
|
|
42
|
+
out.push(e);
|
|
43
|
+
}, query);
|
|
44
|
+
return out;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/cache-differential-tck.ts
|
|
48
|
+
var mulberry32 = (seed) => {
|
|
49
|
+
let a = seed >>> 0;
|
|
50
|
+
return () => {
|
|
51
|
+
a = a + 1831565813 | 0;
|
|
52
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
53
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
54
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
var build_cache_plan = (seed, stream_count) => {
|
|
58
|
+
const rng = mulberry32(seed);
|
|
59
|
+
const prefix = `cdiff-${uid()}-`;
|
|
60
|
+
const streams = Array.from(
|
|
61
|
+
{ length: stream_count },
|
|
62
|
+
(_, i) => `${prefix}${i}`
|
|
63
|
+
);
|
|
64
|
+
const make_entry = () => {
|
|
65
|
+
const v = Math.floor(rng() * 1e3);
|
|
66
|
+
return {
|
|
67
|
+
state: { count: v, label: `k${v % 7}` },
|
|
68
|
+
version: v,
|
|
69
|
+
event_id: v,
|
|
70
|
+
patches: 1 + Math.floor(rng() * 5),
|
|
71
|
+
snaps: Math.floor(rng() * 3)
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const pick_stream = () => streams[Math.floor(rng() * streams.length)];
|
|
75
|
+
const ops = [];
|
|
76
|
+
for (const stream of streams)
|
|
77
|
+
ops.push({ t: "set", stream, entry: make_entry() });
|
|
78
|
+
const middle = 6 + Math.floor(rng() * 14);
|
|
79
|
+
for (let i = 0; i < middle; i++) {
|
|
80
|
+
const kind = Math.floor(rng() * 3);
|
|
81
|
+
if (kind === 0)
|
|
82
|
+
ops.push({ t: "set", stream: pick_stream(), entry: make_entry() });
|
|
83
|
+
else if (kind === 1) ops.push({ t: "invalidate", stream: pick_stream() });
|
|
84
|
+
else ops.push({ t: "clear" });
|
|
85
|
+
}
|
|
86
|
+
return { streams, ops };
|
|
87
|
+
};
|
|
88
|
+
var apply_op = async (cache, op) => {
|
|
89
|
+
if (op.t === "set") await cache.set(op.stream, op.entry);
|
|
90
|
+
else if (op.t === "invalidate") await cache.invalidate(op.stream);
|
|
91
|
+
else await cache.clear();
|
|
92
|
+
};
|
|
93
|
+
var snapshot = async (cache, streams) => {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const stream of streams)
|
|
96
|
+
out[stream] = await cache.get(stream);
|
|
97
|
+
return out;
|
|
98
|
+
};
|
|
99
|
+
var runCacheDifferentialTck = (options) => {
|
|
100
|
+
describe(`TCK / Cache differential / ${options.name}`, () => {
|
|
101
|
+
const base_seed = options.seed ?? 3244;
|
|
102
|
+
const stream_count = options.streams ?? 6;
|
|
103
|
+
const plans = Array.from(
|
|
104
|
+
{ length: options.runs ?? 8 },
|
|
105
|
+
(_, r) => build_cache_plan(base_seed + r, stream_count)
|
|
106
|
+
);
|
|
107
|
+
const live = [];
|
|
108
|
+
beforeAll(async () => {
|
|
109
|
+
for (const spec of options.caches) {
|
|
110
|
+
live.push({ name: spec.name, cache: await spec.factory() });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
afterAll(async () => {
|
|
114
|
+
for (const { cache } of live) await cache.dispose();
|
|
115
|
+
});
|
|
116
|
+
plans.forEach((plan, run) => {
|
|
117
|
+
const seed_hex = `0x${(base_seed + run).toString(16)}`;
|
|
118
|
+
it(`agrees on get() after every op (workload ${run}, seed ${seed_hex})`, async () => {
|
|
119
|
+
for (const op of plan.ops) {
|
|
120
|
+
for (const { cache } of live) await apply_op(cache, op);
|
|
121
|
+
const reference = await snapshot(live[0].cache, plan.streams);
|
|
122
|
+
for (let i = 1; i < live.length; i++) {
|
|
123
|
+
const actual = await snapshot(live[i].cache, plan.streams);
|
|
124
|
+
expect(
|
|
125
|
+
actual,
|
|
126
|
+
`${live[i].name} diverged from ${live[0].name} after ${op.t}`
|
|
127
|
+
).toEqual(reference);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
1
135
|
// src/cache-tck.ts
|
|
2
|
-
import { afterEach, beforeEach as beforeEach2, describe, expect, it } from "vitest";
|
|
136
|
+
import { afterEach, beforeEach as beforeEach2, describe as describe2, expect as expect2, it as it2 } from "vitest";
|
|
3
137
|
var entry = (event_id, state = {}) => ({
|
|
4
138
|
state,
|
|
5
139
|
version: event_id,
|
|
@@ -8,7 +142,7 @@ var entry = (event_id, state = {}) => ({
|
|
|
8
142
|
snaps: 0
|
|
9
143
|
});
|
|
10
144
|
var runCacheTck = (options) => {
|
|
11
|
-
|
|
145
|
+
describe2(`TCK / Cache / ${options.name}`, () => {
|
|
12
146
|
let cache;
|
|
13
147
|
beforeEach2(() => {
|
|
14
148
|
cache = options.factory();
|
|
@@ -16,55 +150,55 @@ var runCacheTck = (options) => {
|
|
|
16
150
|
afterEach(async () => {
|
|
17
151
|
await cache.dispose();
|
|
18
152
|
});
|
|
19
|
-
|
|
20
|
-
|
|
153
|
+
it2("returns undefined for an unset stream", async () => {
|
|
154
|
+
expect2(await cache.get("missing")).toBeUndefined();
|
|
21
155
|
});
|
|
22
|
-
|
|
156
|
+
it2("set then get round-trips an entry", async () => {
|
|
23
157
|
const e = entry(1, { count: 7 });
|
|
24
158
|
await cache.set("s1", e);
|
|
25
|
-
|
|
159
|
+
expect2(await cache.get("s1")).toEqual(e);
|
|
26
160
|
});
|
|
27
|
-
|
|
161
|
+
it2("set overwrites a prior entry on the same stream", async () => {
|
|
28
162
|
await cache.set("s1", entry(1, { count: 1 }));
|
|
29
163
|
await cache.set("s1", entry(2, { count: 2 }));
|
|
30
164
|
const got = await cache.get("s1");
|
|
31
|
-
|
|
32
|
-
|
|
165
|
+
expect2(got?.event_id).toBe(2);
|
|
166
|
+
expect2(got?.state).toEqual({ count: 2 });
|
|
33
167
|
});
|
|
34
|
-
|
|
168
|
+
it2("invalidate removes one stream and leaves others", async () => {
|
|
35
169
|
await cache.set("a", entry(1));
|
|
36
170
|
await cache.set("b", entry(2));
|
|
37
171
|
await cache.invalidate("a");
|
|
38
|
-
|
|
39
|
-
|
|
172
|
+
expect2(await cache.get("a")).toBeUndefined();
|
|
173
|
+
expect2(await cache.get("b")).toBeDefined();
|
|
40
174
|
});
|
|
41
|
-
|
|
42
|
-
await
|
|
175
|
+
it2("invalidate on an unknown stream is a no-op", async () => {
|
|
176
|
+
await expect2(cache.invalidate("never-set")).resolves.toBeUndefined();
|
|
43
177
|
});
|
|
44
|
-
|
|
178
|
+
it2("clear empties every stream", async () => {
|
|
45
179
|
await cache.set("a", entry(1));
|
|
46
180
|
await cache.set("b", entry(2));
|
|
47
181
|
await cache.set("c", entry(3));
|
|
48
182
|
await cache.clear();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
183
|
+
expect2(await cache.get("a")).toBeUndefined();
|
|
184
|
+
expect2(await cache.get("b")).toBeUndefined();
|
|
185
|
+
expect2(await cache.get("c")).toBeUndefined();
|
|
52
186
|
});
|
|
53
|
-
|
|
54
|
-
await
|
|
187
|
+
it2("clear on an empty cache is a no-op", async () => {
|
|
188
|
+
await expect2(cache.clear()).resolves.toBeUndefined();
|
|
55
189
|
});
|
|
56
|
-
|
|
190
|
+
it2("entries are isolated per stream", async () => {
|
|
57
191
|
const ea = entry(1, { id: "a" });
|
|
58
192
|
const eb = entry(2, { id: "b" });
|
|
59
193
|
await cache.set("a", ea);
|
|
60
194
|
await cache.set("b", eb);
|
|
61
|
-
|
|
62
|
-
|
|
195
|
+
expect2(await cache.get("a")).toEqual(ea);
|
|
196
|
+
expect2(await cache.get("b")).toEqual(eb);
|
|
63
197
|
});
|
|
64
|
-
|
|
198
|
+
it2("dispose is idempotent", async () => {
|
|
65
199
|
await cache.set("a", entry(1));
|
|
66
200
|
await cache.dispose();
|
|
67
|
-
await
|
|
201
|
+
await expect2(cache.dispose()).resolves.toBeUndefined();
|
|
68
202
|
});
|
|
69
203
|
});
|
|
70
204
|
};
|
|
@@ -85,105 +219,122 @@ var COUNTER_EVENT_NAMES = [
|
|
|
85
219
|
"Reset"
|
|
86
220
|
];
|
|
87
221
|
|
|
88
|
-
// src/
|
|
89
|
-
import {
|
|
90
|
-
var
|
|
91
|
-
var
|
|
92
|
-
var make_meta = (opts = {}) => ({
|
|
93
|
-
correlation: opts.correlation ?? randomUUID(),
|
|
94
|
-
causation: opts.stream ? {
|
|
95
|
-
action: {
|
|
96
|
-
name: opts.action ?? "Test",
|
|
97
|
-
stream: opts.stream,
|
|
98
|
-
actor: actor()
|
|
99
|
-
}
|
|
100
|
-
} : {}
|
|
101
|
-
});
|
|
102
|
-
var inc = (amount = 1) => ({
|
|
103
|
-
name: "Incremented",
|
|
104
|
-
data: { amount }
|
|
105
|
-
});
|
|
106
|
-
var dec = (amount = 1) => ({
|
|
107
|
-
name: "Decremented",
|
|
108
|
-
data: { amount }
|
|
109
|
-
});
|
|
110
|
-
var reset = () => ({ name: "Reset", data: {} });
|
|
111
|
-
var seed_stream = async (store, stream, count, correlation) => {
|
|
222
|
+
// src/logger-differential-tck.ts
|
|
223
|
+
import { afterEach as afterEach2, beforeEach as beforeEach3, describe as describe3, expect as expect3, it as it3 } from "vitest";
|
|
224
|
+
var LEVELS = ["fatal", "error", "warn", "info", "debug", "trace"];
|
|
225
|
+
var drive = (logger) => {
|
|
112
226
|
const out = [];
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
227
|
+
const ok = (fn) => {
|
|
228
|
+
try {
|
|
229
|
+
fn();
|
|
230
|
+
out.push(true);
|
|
231
|
+
} catch {
|
|
232
|
+
out.push(false);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
out.push(typeof logger.level === "string" && logger.level.length > 0);
|
|
236
|
+
for (const level of LEVELS) {
|
|
237
|
+
ok(() => logger[level]("message"));
|
|
238
|
+
ok(() => logger[level]({ k: "v", n: 1 }));
|
|
239
|
+
ok(() => logger[level]({ k: "v" }, "context"));
|
|
120
240
|
}
|
|
241
|
+
ok(() => logger.info(null, "null payload"));
|
|
242
|
+
ok(() => {
|
|
243
|
+
const cyclic = { name: "loop" };
|
|
244
|
+
cyclic.self = cyclic;
|
|
245
|
+
logger.info(cyclic, "cycle");
|
|
246
|
+
});
|
|
247
|
+
const child = logger.child({ request_id: "abc" });
|
|
248
|
+
out.push(typeof child.level === "string" && child.level.length > 0);
|
|
249
|
+
out.push(LEVELS.every((level) => typeof child[level] === "function"));
|
|
250
|
+
out.push(typeof child.child === "function");
|
|
251
|
+
ok(() => child.child({ nested: true }).info("nested"));
|
|
121
252
|
return out;
|
|
122
253
|
};
|
|
123
|
-
var
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
254
|
+
var runLoggerDifferentialTck = (options) => {
|
|
255
|
+
describe3(`TCK / Logger differential / ${options.name}`, () => {
|
|
256
|
+
let live = [];
|
|
257
|
+
let original_stdout;
|
|
258
|
+
beforeEach3(() => {
|
|
259
|
+
live = options.loggers.map((spec) => ({
|
|
260
|
+
name: spec.name,
|
|
261
|
+
logger: spec.factory()
|
|
262
|
+
}));
|
|
263
|
+
original_stdout = process.stdout.write.bind(process.stdout);
|
|
264
|
+
process.stdout.write = (() => true);
|
|
265
|
+
});
|
|
266
|
+
afterEach2(async () => {
|
|
267
|
+
process.stdout.write = original_stdout;
|
|
268
|
+
for (const { logger } of live) await logger.dispose();
|
|
269
|
+
});
|
|
270
|
+
it3("agrees on robustness and structural parity across the call surface", () => {
|
|
271
|
+
const reference = drive(live[0].logger);
|
|
272
|
+
for (let i = 1; i < live.length; i++) {
|
|
273
|
+
const actual = drive(live[i].logger);
|
|
274
|
+
expect3(actual, `${live[i].name} diverged from ${live[0].name}`).toEqual(
|
|
275
|
+
reference
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
});
|
|
129
280
|
};
|
|
130
281
|
|
|
131
282
|
// src/logger-tck.ts
|
|
132
|
-
import { afterEach as
|
|
133
|
-
var
|
|
283
|
+
import { afterEach as afterEach3, beforeEach as beforeEach4, describe as describe4, expect as expect4, it as it4 } from "vitest";
|
|
284
|
+
var LEVELS2 = ["fatal", "error", "warn", "info", "debug", "trace"];
|
|
134
285
|
var runLoggerTck = (options) => {
|
|
135
|
-
|
|
286
|
+
describe4(`TCK / Logger / ${options.name}`, () => {
|
|
136
287
|
let logger;
|
|
137
288
|
let original_stdout;
|
|
138
|
-
|
|
289
|
+
beforeEach4(() => {
|
|
139
290
|
logger = options.factory();
|
|
140
291
|
original_stdout = process.stdout.write.bind(process.stdout);
|
|
141
292
|
process.stdout.write = (() => true);
|
|
142
293
|
});
|
|
143
|
-
|
|
294
|
+
afterEach3(async () => {
|
|
144
295
|
process.stdout.write = original_stdout;
|
|
145
296
|
await logger.dispose();
|
|
146
297
|
});
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
298
|
+
it4("exposes a non-empty `level` string", () => {
|
|
299
|
+
expect4(typeof logger.level).toBe("string");
|
|
300
|
+
expect4(logger.level.length).toBeGreaterThan(0);
|
|
150
301
|
});
|
|
151
|
-
for (const level of
|
|
152
|
-
|
|
153
|
-
|
|
302
|
+
for (const level of LEVELS2) {
|
|
303
|
+
it4(`${level}(msg) does not throw`, () => {
|
|
304
|
+
expect4(() => logger[level]("hello")).not.toThrow();
|
|
154
305
|
});
|
|
155
|
-
|
|
156
|
-
|
|
306
|
+
it4(`${level}(obj) does not throw`, () => {
|
|
307
|
+
expect4(() => logger[level]({ k: "v" })).not.toThrow();
|
|
157
308
|
});
|
|
158
|
-
|
|
159
|
-
|
|
309
|
+
it4(`${level}(obj, msg) does not throw`, () => {
|
|
310
|
+
expect4(() => logger[level]({ k: "v" }, "context")).not.toThrow();
|
|
160
311
|
});
|
|
161
312
|
}
|
|
162
|
-
|
|
163
|
-
|
|
313
|
+
it4("accepts a null payload", () => {
|
|
314
|
+
expect4(() => logger.info(null, "null payload")).not.toThrow();
|
|
164
315
|
});
|
|
165
|
-
|
|
316
|
+
it4("accepts a cyclic payload without throwing", () => {
|
|
166
317
|
const cyclic = { name: "loop" };
|
|
167
318
|
cyclic.self = cyclic;
|
|
168
|
-
|
|
319
|
+
expect4(() => logger.info(cyclic, "cycle")).not.toThrow();
|
|
169
320
|
});
|
|
170
|
-
|
|
321
|
+
it4("child(bindings) returns a Logger satisfying the same contract", () => {
|
|
171
322
|
const child = logger.child({ request_id: "abc" });
|
|
172
|
-
|
|
173
|
-
for (const level of
|
|
174
|
-
|
|
323
|
+
expect4(typeof child.level).toBe("string");
|
|
324
|
+
for (const level of LEVELS2) {
|
|
325
|
+
expect4(typeof child[level]).toBe("function");
|
|
175
326
|
}
|
|
176
|
-
|
|
177
|
-
|
|
327
|
+
expect4(typeof child.child).toBe("function");
|
|
328
|
+
expect4(typeof child.dispose).toBe("function");
|
|
178
329
|
});
|
|
179
|
-
|
|
330
|
+
it4("child loggers can themselves spawn children", () => {
|
|
180
331
|
const c1 = logger.child({ a: 1 });
|
|
181
332
|
const c2 = c1.child({ b: 2 });
|
|
182
|
-
|
|
333
|
+
expect4(() => c2.info("nested")).not.toThrow();
|
|
183
334
|
});
|
|
184
|
-
|
|
185
|
-
await
|
|
186
|
-
await
|
|
335
|
+
it4("dispose is idempotent and awaitable", async () => {
|
|
336
|
+
await expect4(logger.dispose()).resolves.toBeUndefined();
|
|
337
|
+
await expect4(logger.dispose()).resolves.toBeUndefined();
|
|
187
338
|
});
|
|
188
339
|
});
|
|
189
340
|
};
|
|
@@ -191,14 +342,14 @@ var runLoggerTck = (options) => {
|
|
|
191
342
|
// src/stability-tck.ts
|
|
192
343
|
import { promises as fs } from "fs";
|
|
193
344
|
import path from "path";
|
|
194
|
-
import { describe as
|
|
345
|
+
import { describe as describe5, expect as expect5, it as it5 } from "vitest";
|
|
195
346
|
function runStabilityTck(options) {
|
|
196
|
-
|
|
347
|
+
describe5(`${options.name} \u2014 public API stability`, () => {
|
|
197
348
|
for (const [subpath, entry2] of Object.entries(options.entryPoints)) {
|
|
198
349
|
const label = subpath || "(root)";
|
|
199
|
-
|
|
350
|
+
it5(`stable public surface for ${label}`, async () => {
|
|
200
351
|
const surface = await load_surface(entry2);
|
|
201
|
-
|
|
352
|
+
expect5(surface).toMatchSnapshot();
|
|
202
353
|
});
|
|
203
354
|
}
|
|
204
355
|
});
|
|
@@ -228,9 +379,220 @@ ${content}`;
|
|
|
228
379
|
}).join("\n");
|
|
229
380
|
}
|
|
230
381
|
|
|
382
|
+
// src/store-differential-tck.ts
|
|
383
|
+
import { SNAP_EVENT } from "@rotorsoft/act";
|
|
384
|
+
import { afterAll as afterAll2, beforeAll as beforeAll2, describe as describe6, expect as expect6, it as it6 } from "vitest";
|
|
385
|
+
var mulberry322 = (seed) => {
|
|
386
|
+
let a = seed >>> 0;
|
|
387
|
+
return () => {
|
|
388
|
+
a = a + 1831565813 | 0;
|
|
389
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
390
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
391
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
392
|
+
};
|
|
393
|
+
};
|
|
394
|
+
var normalize_event = (e) => ({
|
|
395
|
+
stream: e.stream,
|
|
396
|
+
version: e.version,
|
|
397
|
+
name: e.name,
|
|
398
|
+
data: e.data
|
|
399
|
+
});
|
|
400
|
+
var build_plan = (seed, stream_count) => {
|
|
401
|
+
const rng = mulberry322(seed);
|
|
402
|
+
const tag = uid();
|
|
403
|
+
const event_prefix = `diff-${tag}-evt-`;
|
|
404
|
+
const sub_prefix = `diff-${tag}-sub-`;
|
|
405
|
+
const event_streams = Array.from(
|
|
406
|
+
{ length: stream_count },
|
|
407
|
+
(_, i) => `${event_prefix}${i}`
|
|
408
|
+
);
|
|
409
|
+
let type_cursor = 0;
|
|
410
|
+
const next_msg = () => {
|
|
411
|
+
const kind = type_cursor++ % 3;
|
|
412
|
+
const amount = 1 + Math.floor(rng() * 9);
|
|
413
|
+
return kind === 0 ? inc(amount) : kind === 1 ? dec(amount) : reset();
|
|
414
|
+
};
|
|
415
|
+
const batch = () => Array.from({ length: 1 + Math.floor(rng() * 3) }, next_msg);
|
|
416
|
+
const pick_stream = () => event_streams[Math.floor(rng() * event_streams.length)];
|
|
417
|
+
const ops = [];
|
|
418
|
+
for (const stream of event_streams)
|
|
419
|
+
ops.push({ t: "commit", stream, msgs: batch() });
|
|
420
|
+
const middle = 8 + Math.floor(rng() * 16);
|
|
421
|
+
for (let i = 0; i < middle; i++) {
|
|
422
|
+
const stream = pick_stream();
|
|
423
|
+
const kind = Math.floor(rng() * 3);
|
|
424
|
+
if (kind === 0) ops.push({ t: "commit", stream, msgs: batch() });
|
|
425
|
+
else if (kind === 1)
|
|
426
|
+
ops.push({ t: "snapshot", stream, count: Math.floor(rng() * 1e3) });
|
|
427
|
+
else ops.push({ t: "truncate", stream, count: Math.floor(rng() * 1e3) });
|
|
428
|
+
}
|
|
429
|
+
for (const stream of event_streams)
|
|
430
|
+
ops.push({ t: "commit", stream, msgs: batch() });
|
|
431
|
+
const lanes = ["default", "slow", "fast"];
|
|
432
|
+
const subs = event_streams.map((source, i) => ({
|
|
433
|
+
stream: `${sub_prefix}${i}`,
|
|
434
|
+
source,
|
|
435
|
+
lane: lanes[i % lanes.length],
|
|
436
|
+
priority: i % 4
|
|
437
|
+
}));
|
|
438
|
+
return { event_prefix, event_streams, sub_prefix, ops, subs };
|
|
439
|
+
};
|
|
440
|
+
var apply_plan = async (store, plan) => {
|
|
441
|
+
for (const op of plan.ops) {
|
|
442
|
+
if (op.t === "commit") {
|
|
443
|
+
await store.commit(
|
|
444
|
+
op.stream,
|
|
445
|
+
op.msgs,
|
|
446
|
+
make_meta({ stream: op.stream })
|
|
447
|
+
);
|
|
448
|
+
} else if (op.t === "snapshot") {
|
|
449
|
+
await store.commit(
|
|
450
|
+
op.stream,
|
|
451
|
+
[{ name: SNAP_EVENT, data: { count: op.count } }],
|
|
452
|
+
make_meta({ stream: op.stream })
|
|
453
|
+
);
|
|
454
|
+
} else {
|
|
455
|
+
await store.truncate([
|
|
456
|
+
{ stream: op.stream, snapshot: { count: op.count } }
|
|
457
|
+
]);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
await store.subscribe(
|
|
461
|
+
plan.subs.map((s) => ({
|
|
462
|
+
stream: s.stream,
|
|
463
|
+
source: s.source,
|
|
464
|
+
lane: s.lane,
|
|
465
|
+
priority: s.priority
|
|
466
|
+
}))
|
|
467
|
+
);
|
|
468
|
+
};
|
|
469
|
+
var runStoreDifferentialTck = (options) => {
|
|
470
|
+
describe6(`TCK / Store differential / ${options.name}`, () => {
|
|
471
|
+
const base_seed = options.seed ?? 2759;
|
|
472
|
+
const stream_count = options.streams ?? 4;
|
|
473
|
+
const plans = Array.from(
|
|
474
|
+
{ length: options.runs ?? 8 },
|
|
475
|
+
(_, r) => build_plan(base_seed + r, stream_count)
|
|
476
|
+
);
|
|
477
|
+
const live = [];
|
|
478
|
+
beforeAll2(async () => {
|
|
479
|
+
for (const spec of options.stores) {
|
|
480
|
+
const store = await spec.factory();
|
|
481
|
+
await store.drop();
|
|
482
|
+
await store.seed();
|
|
483
|
+
for (const plan of plans) await apply_plan(store, plan);
|
|
484
|
+
live.push({ name: spec.name, store });
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
afterAll2(async () => {
|
|
488
|
+
for (const { store } of live) await store.dispose();
|
|
489
|
+
});
|
|
490
|
+
const assert_identical = async (label, produce) => {
|
|
491
|
+
const reference = await produce(live[0].store);
|
|
492
|
+
for (let i = 1; i < live.length; i++) {
|
|
493
|
+
const actual = await produce(live[i].store);
|
|
494
|
+
expect6(
|
|
495
|
+
actual,
|
|
496
|
+
`${live[i].name} diverged from ${live[0].name} on "${label}"`
|
|
497
|
+
).toEqual(reference);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
plans.forEach((plan, run) => {
|
|
501
|
+
const seed_hex = `0x${(base_seed + run).toString(16)}`;
|
|
502
|
+
describe6(`workload ${run} (seed ${seed_hex})`, () => {
|
|
503
|
+
it6("yields identical event order under a global forward query", async () => {
|
|
504
|
+
await assert_identical("forward query", async (store) => {
|
|
505
|
+
const out = [];
|
|
506
|
+
await store.query(
|
|
507
|
+
(e) => {
|
|
508
|
+
out.push(normalize_event(e));
|
|
509
|
+
},
|
|
510
|
+
{ stream: `^${plan.event_prefix}` }
|
|
511
|
+
);
|
|
512
|
+
return out;
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
it6("yields identical snapshot floors under with_snaps", async () => {
|
|
516
|
+
await assert_identical("with_snaps floor", async (store) => {
|
|
517
|
+
const by_stream = {};
|
|
518
|
+
for (const stream of plan.event_streams) {
|
|
519
|
+
const out = [];
|
|
520
|
+
await store.query(
|
|
521
|
+
(e) => {
|
|
522
|
+
out.push(normalize_event(e));
|
|
523
|
+
},
|
|
524
|
+
{ stream, stream_exact: true, with_snaps: true }
|
|
525
|
+
);
|
|
526
|
+
by_stream[stream] = out;
|
|
527
|
+
}
|
|
528
|
+
return by_stream;
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
it6("yields identical order under backward traversal", async () => {
|
|
532
|
+
await assert_identical("backward query", async (store) => {
|
|
533
|
+
const by_stream = {};
|
|
534
|
+
for (const stream of plan.event_streams) {
|
|
535
|
+
const out = [];
|
|
536
|
+
await store.query(
|
|
537
|
+
(e) => {
|
|
538
|
+
out.push(normalize_event(e));
|
|
539
|
+
},
|
|
540
|
+
{ stream, stream_exact: true, backward: true }
|
|
541
|
+
);
|
|
542
|
+
by_stream[stream] = out;
|
|
543
|
+
}
|
|
544
|
+
return by_stream;
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
it6("yields identical query_stats output (head/tail/count/names)", async () => {
|
|
548
|
+
await assert_identical("query_stats", async (store) => {
|
|
549
|
+
const stats = await store.query_stats(
|
|
550
|
+
{ stream: `^${plan.event_prefix}` },
|
|
551
|
+
{ tail: true, count: true, names: true }
|
|
552
|
+
);
|
|
553
|
+
const keys = [...stats.keys()];
|
|
554
|
+
const content = {};
|
|
555
|
+
for (const [stream, s] of stats) {
|
|
556
|
+
content[stream] = {
|
|
557
|
+
head: normalize_event(s.head),
|
|
558
|
+
tail: normalize_event(
|
|
559
|
+
s.tail
|
|
560
|
+
),
|
|
561
|
+
count: s.count,
|
|
562
|
+
names: s.names
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
return { keys, content };
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
it6("yields identical query_streams output", async () => {
|
|
569
|
+
await assert_identical("query_streams", async (store) => {
|
|
570
|
+
const rows = [];
|
|
571
|
+
const { count } = await store.query_streams(
|
|
572
|
+
(p) => {
|
|
573
|
+
rows.push({
|
|
574
|
+
stream: p.stream,
|
|
575
|
+
source: p.source,
|
|
576
|
+
at: p.at,
|
|
577
|
+
blocked: p.blocked,
|
|
578
|
+
priority: p.priority,
|
|
579
|
+
lane: p.lane
|
|
580
|
+
});
|
|
581
|
+
},
|
|
582
|
+
{ stream: `^${plan.sub_prefix}`, limit: 1e3 }
|
|
583
|
+
);
|
|
584
|
+
rows.sort((a, b) => a.stream.localeCompare(b.stream));
|
|
585
|
+
return { count, rows };
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
};
|
|
592
|
+
|
|
231
593
|
// src/store-property-tck.ts
|
|
232
594
|
import { fc, test } from "@fast-check/vitest";
|
|
233
|
-
import { afterAll, beforeAll, describe as
|
|
595
|
+
import { afterAll as afterAll3, beforeAll as beforeAll3, describe as describe7, expect as expect7 } from "vitest";
|
|
234
596
|
var streamArb = fc.constantFrom("s1", "s2", "s3");
|
|
235
597
|
var commitArb = fc.record({
|
|
236
598
|
stream: streamArb,
|
|
@@ -246,20 +608,20 @@ var opArb = fc.oneof(
|
|
|
246
608
|
var events = (count) => Array.from({ length: count }, () => inc(1));
|
|
247
609
|
var runStorePropertyTck = (options) => {
|
|
248
610
|
const numRuns = options.numRuns ?? 100;
|
|
249
|
-
|
|
611
|
+
describe7(`TCK / Store properties / ${options.name}`, () => {
|
|
250
612
|
let store;
|
|
251
|
-
|
|
613
|
+
beforeAll3(async () => {
|
|
252
614
|
store = await options.factory();
|
|
253
615
|
await store.seed();
|
|
254
616
|
});
|
|
255
|
-
|
|
617
|
+
afterAll3(async () => {
|
|
256
618
|
await store.dispose();
|
|
257
619
|
});
|
|
258
620
|
const reset2 = async () => {
|
|
259
621
|
await store.drop();
|
|
260
622
|
await store.seed();
|
|
261
623
|
};
|
|
262
|
-
|
|
624
|
+
describe7("commit version invariants", () => {
|
|
263
625
|
test.prop([fc.array(commitArb, { minLength: 0, maxLength: 30 })], {
|
|
264
626
|
numRuns
|
|
265
627
|
})(
|
|
@@ -275,14 +637,14 @@ var runStorePropertyTck = (options) => {
|
|
|
275
637
|
make_meta({ stream })
|
|
276
638
|
);
|
|
277
639
|
committed.forEach((e, i) => {
|
|
278
|
-
|
|
640
|
+
expect7(e.version).toBe(before + 1 + i);
|
|
279
641
|
});
|
|
280
642
|
expected.set(stream, before + count);
|
|
281
643
|
}
|
|
282
644
|
for (const stream of new Set(commits.map((c) => c.stream))) {
|
|
283
645
|
const seen = await collect(store, { stream, stream_exact: true });
|
|
284
646
|
seen.forEach((e, i) => {
|
|
285
|
-
|
|
647
|
+
expect7(e.version).toBe(i);
|
|
286
648
|
});
|
|
287
649
|
}
|
|
288
650
|
}
|
|
@@ -302,7 +664,7 @@ var runStorePropertyTck = (options) => {
|
|
|
302
664
|
}
|
|
303
665
|
const stream = commits[0].stream;
|
|
304
666
|
const before = await collect(store, { stream, stream_exact: true });
|
|
305
|
-
await
|
|
667
|
+
await expect7(
|
|
306
668
|
store.commit(
|
|
307
669
|
stream,
|
|
308
670
|
[inc(1)],
|
|
@@ -311,11 +673,11 @@ var runStorePropertyTck = (options) => {
|
|
|
311
673
|
)
|
|
312
674
|
).rejects.toThrow();
|
|
313
675
|
const after = await collect(store, { stream, stream_exact: true });
|
|
314
|
-
|
|
676
|
+
expect7(after.length).toBe(before.length);
|
|
315
677
|
}
|
|
316
678
|
);
|
|
317
679
|
});
|
|
318
|
-
|
|
680
|
+
describe7("claim/lease lifecycle invariants", () => {
|
|
319
681
|
test.prop([fc.array(opArb, { minLength: 1, maxLength: 30 })], {
|
|
320
682
|
numRuns
|
|
321
683
|
})(
|
|
@@ -357,7 +719,7 @@ var runStorePropertyTck = (options) => {
|
|
|
357
719
|
);
|
|
358
720
|
}
|
|
359
721
|
}
|
|
360
|
-
|
|
722
|
+
expect7(totalResolved + pending.length).toBe(totalClaims);
|
|
361
723
|
}
|
|
362
724
|
);
|
|
363
725
|
test.prop(
|
|
@@ -395,7 +757,7 @@ var runStorePropertyTck = (options) => {
|
|
|
395
757
|
await store.claim(10, 10, "worker", 6e4)
|
|
396
758
|
);
|
|
397
759
|
for (const lease of acked2) {
|
|
398
|
-
|
|
760
|
+
expect7(lease.at).toBeGreaterThanOrEqual(
|
|
399
761
|
watermark1.get(lease.stream)
|
|
400
762
|
);
|
|
401
763
|
}
|
|
@@ -424,7 +786,7 @@ var runStorePropertyTck = (options) => {
|
|
|
424
786
|
make_meta({ stream: "ctrl" })
|
|
425
787
|
);
|
|
426
788
|
const reclaim = await store.claim(10, 10, "worker2", 6e4);
|
|
427
|
-
for (const l of reclaim)
|
|
789
|
+
for (const l of reclaim) expect7(blockedSet.has(l.stream)).toBe(false);
|
|
428
790
|
});
|
|
429
791
|
});
|
|
430
792
|
});
|
|
@@ -435,41 +797,41 @@ import {
|
|
|
435
797
|
act,
|
|
436
798
|
ConcurrencyError,
|
|
437
799
|
InMemoryCache,
|
|
438
|
-
SNAP_EVENT,
|
|
800
|
+
SNAP_EVENT as SNAP_EVENT2,
|
|
439
801
|
TOMBSTONE_EVENT
|
|
440
802
|
} from "@rotorsoft/act";
|
|
441
|
-
import { afterAll as
|
|
803
|
+
import { afterAll as afterAll4, beforeAll as beforeAll4, describe as describe8, expect as expect8, it as it7 } from "vitest";
|
|
442
804
|
var runStoreTck = (options) => {
|
|
443
|
-
|
|
805
|
+
describe8(`TCK / Store / ${options.name}`, () => {
|
|
444
806
|
let store;
|
|
445
807
|
const caps = { ...options.capabilities };
|
|
446
|
-
|
|
808
|
+
beforeAll4(async () => {
|
|
447
809
|
store = await options.factory();
|
|
448
810
|
await store.drop();
|
|
449
811
|
await store.seed();
|
|
450
812
|
});
|
|
451
|
-
|
|
813
|
+
afterAll4(async () => {
|
|
452
814
|
await store.dispose();
|
|
453
815
|
});
|
|
454
|
-
|
|
455
|
-
|
|
816
|
+
describe8("commit", () => {
|
|
817
|
+
it7("returns committed events with sequenced ids and versions", async () => {
|
|
456
818
|
const s = `commit-seq-${uid()}`;
|
|
457
819
|
const committed = await store.commit(
|
|
458
820
|
s,
|
|
459
821
|
[inc(1), inc(2), dec(3)],
|
|
460
822
|
make_meta({ stream: s })
|
|
461
823
|
);
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
824
|
+
expect8(committed).toHaveLength(3);
|
|
825
|
+
expect8(committed[0].version).toBe(0);
|
|
826
|
+
expect8(committed[1].version).toBe(1);
|
|
827
|
+
expect8(committed[2].version).toBe(2);
|
|
828
|
+
expect8(committed[0].name).toBe("Incremented");
|
|
829
|
+
expect8(committed[2].data).toEqual({ amount: 3 });
|
|
468
830
|
for (let i = 1; i < committed.length; i++) {
|
|
469
|
-
|
|
831
|
+
expect8(committed[i].id).toBeGreaterThan(committed[i - 1].id);
|
|
470
832
|
}
|
|
471
833
|
});
|
|
472
|
-
|
|
834
|
+
it7("attaches correlation and stream metadata", async () => {
|
|
473
835
|
const s = `commit-meta-${uid()}`;
|
|
474
836
|
const correlation = `cor-${uid()}`;
|
|
475
837
|
const committed = await store.commit(
|
|
@@ -477,10 +839,10 @@ var runStoreTck = (options) => {
|
|
|
477
839
|
[inc(1)],
|
|
478
840
|
make_meta({ stream: s, correlation })
|
|
479
841
|
);
|
|
480
|
-
|
|
481
|
-
|
|
842
|
+
expect8(committed[0].stream).toBe(s);
|
|
843
|
+
expect8(committed[0].meta.correlation).toBe(correlation);
|
|
482
844
|
});
|
|
483
|
-
|
|
845
|
+
it7("throws ConcurrencyError when expectedVersion is wrong", async () => {
|
|
484
846
|
const s = `commit-cc-${uid()}`;
|
|
485
847
|
await store.commit(
|
|
486
848
|
s,
|
|
@@ -493,26 +855,26 @@ var runStoreTck = (options) => {
|
|
|
493
855
|
make_meta({ stream: s }),
|
|
494
856
|
0
|
|
495
857
|
);
|
|
496
|
-
await
|
|
858
|
+
await expect8(
|
|
497
859
|
store.commit(s, [inc(1)], make_meta({ stream: s }), 0)
|
|
498
860
|
).rejects.toBeInstanceOf(ConcurrencyError);
|
|
499
861
|
});
|
|
500
|
-
|
|
862
|
+
it7("preserves prior events when a concurrent commit is rejected", async () => {
|
|
501
863
|
const s = `commit-cc-preserve-${uid()}`;
|
|
502
864
|
await store.commit(
|
|
503
865
|
s,
|
|
504
866
|
[inc(1), inc(2)],
|
|
505
867
|
make_meta({ stream: s })
|
|
506
868
|
);
|
|
507
|
-
await
|
|
869
|
+
await expect8(
|
|
508
870
|
store.commit(s, [inc(3)], make_meta({ stream: s }), 0)
|
|
509
871
|
).rejects.toBeInstanceOf(ConcurrencyError);
|
|
510
872
|
const found = await collect(store, { stream: s, stream_exact: true });
|
|
511
|
-
|
|
873
|
+
expect8(found).toHaveLength(2);
|
|
512
874
|
});
|
|
513
875
|
});
|
|
514
|
-
|
|
515
|
-
|
|
876
|
+
describe8("query", () => {
|
|
877
|
+
it7("filters by stream, names, correlation, limit, with_snaps", async () => {
|
|
516
878
|
const s1 = `q-s1-${uid()}`;
|
|
517
879
|
const s2 = `q-s2-${uid()}`;
|
|
518
880
|
const cor = `q-cor-${uid()}`;
|
|
@@ -530,23 +892,23 @@ var runStoreTck = (options) => {
|
|
|
530
892
|
stream: s1,
|
|
531
893
|
stream_exact: true
|
|
532
894
|
});
|
|
533
|
-
|
|
895
|
+
expect8(by_stream).toHaveLength(2);
|
|
534
896
|
const by_name = await collect(store, {
|
|
535
897
|
stream: s2,
|
|
536
898
|
stream_exact: true,
|
|
537
899
|
names: ["Reset"]
|
|
538
900
|
});
|
|
539
|
-
|
|
540
|
-
|
|
901
|
+
expect8(by_name).toHaveLength(1);
|
|
902
|
+
expect8(by_name[0].name).toBe("Reset");
|
|
541
903
|
const by_correlation = await collect(store, { correlation: cor });
|
|
542
|
-
|
|
904
|
+
expect8(by_correlation).toHaveLength(5);
|
|
543
905
|
const limited = await collect(store, {
|
|
544
906
|
correlation: cor,
|
|
545
907
|
limit: 2
|
|
546
908
|
});
|
|
547
|
-
|
|
909
|
+
expect8(limited).toHaveLength(2);
|
|
548
910
|
});
|
|
549
|
-
|
|
911
|
+
it7("with_snaps resumes from the latest snapshot per stream", async () => {
|
|
550
912
|
const s = `q-snap-${uid()}`;
|
|
551
913
|
await store.commit(
|
|
552
914
|
s,
|
|
@@ -555,7 +917,7 @@ var runStoreTck = (options) => {
|
|
|
555
917
|
);
|
|
556
918
|
const [snap] = await store.commit(
|
|
557
919
|
s,
|
|
558
|
-
[{ name:
|
|
920
|
+
[{ name: SNAP_EVENT2, data: { count: 2 } }],
|
|
559
921
|
make_meta({ stream: s })
|
|
560
922
|
);
|
|
561
923
|
await store.commit(
|
|
@@ -568,17 +930,17 @@ var runStoreTck = (options) => {
|
|
|
568
930
|
stream_exact: true,
|
|
569
931
|
with_snaps: true
|
|
570
932
|
});
|
|
571
|
-
|
|
572
|
-
|
|
933
|
+
expect8(from_snap).toHaveLength(4);
|
|
934
|
+
expect8(from_snap[0].name).toBe(SNAP_EVENT2);
|
|
573
935
|
const domain = await collect(store, { stream: s, stream_exact: true });
|
|
574
|
-
|
|
936
|
+
expect8(domain).toHaveLength(5);
|
|
575
937
|
const after_snap = await collect(store, {
|
|
576
938
|
stream: s,
|
|
577
939
|
stream_exact: true,
|
|
578
940
|
with_snaps: true,
|
|
579
941
|
after: snap.id
|
|
580
942
|
});
|
|
581
|
-
|
|
943
|
+
expect8(after_snap).toHaveLength(3);
|
|
582
944
|
const s2 = `q-nosnap-${uid()}`;
|
|
583
945
|
await store.commit(
|
|
584
946
|
s2,
|
|
@@ -590,9 +952,9 @@ var runStoreTck = (options) => {
|
|
|
590
952
|
stream_exact: true,
|
|
591
953
|
with_snaps: true
|
|
592
954
|
});
|
|
593
|
-
|
|
955
|
+
expect8(full).toHaveLength(2);
|
|
594
956
|
});
|
|
595
|
-
|
|
957
|
+
it7("supports backward traversal", async () => {
|
|
596
958
|
const s = `q-back-${uid()}`;
|
|
597
959
|
const committed = await store.commit(
|
|
598
960
|
s,
|
|
@@ -605,8 +967,8 @@ var runStoreTck = (options) => {
|
|
|
605
967
|
stream_exact: true,
|
|
606
968
|
backward: true
|
|
607
969
|
});
|
|
608
|
-
|
|
609
|
-
|
|
970
|
+
expect8(forward.map((e) => e.id)).toEqual(committed.map((c) => c.id));
|
|
971
|
+
expect8(backward.map((e) => e.id)).toEqual(
|
|
610
972
|
[...committed].reverse().map((c) => c.id)
|
|
611
973
|
);
|
|
612
974
|
const latest = await collect(store, {
|
|
@@ -615,10 +977,10 @@ var runStoreTck = (options) => {
|
|
|
615
977
|
backward: true,
|
|
616
978
|
limit: 1
|
|
617
979
|
});
|
|
618
|
-
|
|
619
|
-
|
|
980
|
+
expect8(latest).toHaveLength(1);
|
|
981
|
+
expect8(latest[0].id).toBe(committed.at(-1).id);
|
|
620
982
|
});
|
|
621
|
-
|
|
983
|
+
it7("after/before bound the id range", async () => {
|
|
622
984
|
const s = `q-bounds-${uid()}`;
|
|
623
985
|
const committed = await store.commit(
|
|
624
986
|
s,
|
|
@@ -630,7 +992,7 @@ var runStoreTck = (options) => {
|
|
|
630
992
|
stream_exact: true,
|
|
631
993
|
after: committed[0].id
|
|
632
994
|
});
|
|
633
|
-
|
|
995
|
+
expect8(after_first.map((e) => e.id)).toEqual(
|
|
634
996
|
committed.slice(1).map((c) => c.id)
|
|
635
997
|
);
|
|
636
998
|
const before_last = await collect(store, {
|
|
@@ -638,11 +1000,11 @@ var runStoreTck = (options) => {
|
|
|
638
1000
|
stream_exact: true,
|
|
639
1001
|
before: committed[committed.length - 1].id
|
|
640
1002
|
});
|
|
641
|
-
|
|
1003
|
+
expect8(before_last.map((e) => e.id)).toEqual(
|
|
642
1004
|
committed.slice(0, -1).map((c) => c.id)
|
|
643
1005
|
);
|
|
644
1006
|
});
|
|
645
|
-
|
|
1007
|
+
it7("created_after/created_before filter by timestamp", async () => {
|
|
646
1008
|
const s = `q-ts-${uid()}`;
|
|
647
1009
|
const committed = await store.commit(
|
|
648
1010
|
s,
|
|
@@ -658,15 +1020,15 @@ var runStoreTck = (options) => {
|
|
|
658
1020
|
created_after: before,
|
|
659
1021
|
created_before: future
|
|
660
1022
|
});
|
|
661
|
-
|
|
1023
|
+
expect8(in_window.length).toBe(1);
|
|
662
1024
|
const out_of_window = await collect(store, {
|
|
663
1025
|
stream: s,
|
|
664
1026
|
stream_exact: true,
|
|
665
1027
|
created_after: future
|
|
666
1028
|
});
|
|
667
|
-
|
|
1029
|
+
expect8(out_of_window.length).toBe(0);
|
|
668
1030
|
});
|
|
669
|
-
|
|
1031
|
+
it7("backward traversal short-circuits at `after` id boundary", async () => {
|
|
670
1032
|
const s = `q-back-after-${uid()}`;
|
|
671
1033
|
const committed = await store.commit(
|
|
672
1034
|
s,
|
|
@@ -679,12 +1041,12 @@ var runStoreTck = (options) => {
|
|
|
679
1041
|
backward: true,
|
|
680
1042
|
after: committed[0].id
|
|
681
1043
|
});
|
|
682
|
-
|
|
1044
|
+
expect8(got.map((e) => e.id)).toEqual([
|
|
683
1045
|
committed[2].id,
|
|
684
1046
|
committed[1].id
|
|
685
1047
|
]);
|
|
686
1048
|
});
|
|
687
|
-
|
|
1049
|
+
it7("backward traversal short-circuits at `created_after` boundary", async () => {
|
|
688
1050
|
const s = `q-back-cafter-${uid()}`;
|
|
689
1051
|
await store.commit(
|
|
690
1052
|
s,
|
|
@@ -698,9 +1060,9 @@ var runStoreTck = (options) => {
|
|
|
698
1060
|
backward: true,
|
|
699
1061
|
created_after: future
|
|
700
1062
|
});
|
|
701
|
-
|
|
1063
|
+
expect8(got).toHaveLength(0);
|
|
702
1064
|
});
|
|
703
|
-
|
|
1065
|
+
it7("backward traversal honors created_before by skipping newer events", async () => {
|
|
704
1066
|
const s = `q-back-ts-${uid()}`;
|
|
705
1067
|
const committed = await store.commit(
|
|
706
1068
|
s,
|
|
@@ -714,9 +1076,9 @@ var runStoreTck = (options) => {
|
|
|
714
1076
|
backward: true,
|
|
715
1077
|
created_before: past
|
|
716
1078
|
});
|
|
717
|
-
|
|
1079
|
+
expect8(got).toHaveLength(0);
|
|
718
1080
|
});
|
|
719
|
-
|
|
1081
|
+
it7("stream_exact disables regex matching", async () => {
|
|
720
1082
|
const tag = uid();
|
|
721
1083
|
const a = `q-exact-${tag}`;
|
|
722
1084
|
const b = `q-exact-${tag}-extra`;
|
|
@@ -731,10 +1093,10 @@ var runStoreTck = (options) => {
|
|
|
731
1093
|
make_meta({ stream: b })
|
|
732
1094
|
);
|
|
733
1095
|
const exact = await collect(store, { stream: a, stream_exact: true });
|
|
734
|
-
|
|
735
|
-
|
|
1096
|
+
expect8(exact).toHaveLength(1);
|
|
1097
|
+
expect8(exact[0].data).toEqual({ amount: 1 });
|
|
736
1098
|
});
|
|
737
|
-
|
|
1099
|
+
it7("plain regex without anchors is a substring match", async () => {
|
|
738
1100
|
const tag = uid();
|
|
739
1101
|
const inner = `qr-${tag}-inner`;
|
|
740
1102
|
const longer = `qr-${tag}-inner-extra`;
|
|
@@ -749,9 +1111,9 @@ var runStoreTck = (options) => {
|
|
|
749
1111
|
make_meta({ stream: longer })
|
|
750
1112
|
);
|
|
751
1113
|
const got = await collect(store, { stream: `qr-${tag}-inner` });
|
|
752
|
-
|
|
1114
|
+
expect8(got.map((e) => e.stream).sort()).toEqual([inner, longer].sort());
|
|
753
1115
|
});
|
|
754
|
-
|
|
1116
|
+
it7("caller-anchored `^name$` matches only the whole string", async () => {
|
|
755
1117
|
const tag = uid();
|
|
756
1118
|
const inner = `qr-${tag}-anchor`;
|
|
757
1119
|
const longer = `qr-${tag}-anchor-extra`;
|
|
@@ -766,10 +1128,10 @@ var runStoreTck = (options) => {
|
|
|
766
1128
|
make_meta({ stream: longer })
|
|
767
1129
|
);
|
|
768
1130
|
const got = await collect(store, { stream: `^qr-${tag}-anchor$` });
|
|
769
|
-
|
|
770
|
-
|
|
1131
|
+
expect8(got).toHaveLength(1);
|
|
1132
|
+
expect8(got[0].stream).toBe(inner);
|
|
771
1133
|
});
|
|
772
|
-
|
|
1134
|
+
it7("caller-anchored `^prefix` matches by prefix", async () => {
|
|
773
1135
|
const tag = uid();
|
|
774
1136
|
const a = `qr-${tag}-pfx-a`;
|
|
775
1137
|
const b = `qr-${tag}-pfx-b`;
|
|
@@ -790,18 +1152,39 @@ var runStoreTck = (options) => {
|
|
|
790
1152
|
make_meta({ stream: other })
|
|
791
1153
|
);
|
|
792
1154
|
const got = await collect(store, { stream: `^qr-${tag}-pfx-` });
|
|
793
|
-
|
|
1155
|
+
expect8(got.map((e) => e.stream).sort()).toEqual([a, b].sort());
|
|
794
1156
|
});
|
|
795
1157
|
});
|
|
796
|
-
|
|
797
|
-
|
|
1158
|
+
describe8("subscribe + claim + ack", () => {
|
|
1159
|
+
it7("subscribes new streams and is idempotent on repeat", async () => {
|
|
798
1160
|
const s = `sub-${uid()}`;
|
|
799
1161
|
const first = await store.subscribe([{ stream: s }]);
|
|
800
|
-
|
|
1162
|
+
expect8(first.subscribed).toBe(1);
|
|
801
1163
|
const second = await store.subscribe([{ stream: s }]);
|
|
802
|
-
|
|
1164
|
+
expect8(second.subscribed).toBe(0);
|
|
1165
|
+
});
|
|
1166
|
+
it7("keeps the maximum priority when a stream is re-subscribed", async () => {
|
|
1167
|
+
const s = `sub-pri-${uid()}`;
|
|
1168
|
+
const read = async () => {
|
|
1169
|
+
const got = {};
|
|
1170
|
+
await store.query_streams(
|
|
1171
|
+
(p) => {
|
|
1172
|
+
got.priority = p.priority;
|
|
1173
|
+
},
|
|
1174
|
+
{ stream: s, stream_exact: true }
|
|
1175
|
+
);
|
|
1176
|
+
return got.priority;
|
|
1177
|
+
};
|
|
1178
|
+
await store.subscribe([{ stream: s, priority: 3 }]);
|
|
1179
|
+
expect8(await read()).toBe(3);
|
|
1180
|
+
await store.subscribe([{ stream: s, priority: 10 }]);
|
|
1181
|
+
expect8(await read()).toBe(10);
|
|
1182
|
+
await store.subscribe([{ stream: s, priority: 1 }]);
|
|
1183
|
+
expect8(await read()).toBe(10);
|
|
1184
|
+
await store.subscribe([{ stream: s }]);
|
|
1185
|
+
expect8(await read()).toBe(10);
|
|
803
1186
|
});
|
|
804
|
-
|
|
1187
|
+
it7("claims a subscribed stream and ack releases the lease", async () => {
|
|
805
1188
|
const s = `claim-${uid()}`;
|
|
806
1189
|
await store.subscribe([{ stream: s }]);
|
|
807
1190
|
await store.commit(
|
|
@@ -812,11 +1195,11 @@ var runStoreTck = (options) => {
|
|
|
812
1195
|
const by = `worker-${uid()}`;
|
|
813
1196
|
const leased = await store.claim(100, 0, by, 1e4);
|
|
814
1197
|
const mine = leased.find((l) => l.stream === s);
|
|
815
|
-
|
|
816
|
-
|
|
1198
|
+
expect8(mine).toBeDefined();
|
|
1199
|
+
expect8(mine.by).toBe(by);
|
|
817
1200
|
await store.ack([{ ...mine, at: mine.at + 1 }]);
|
|
818
1201
|
});
|
|
819
|
-
|
|
1202
|
+
it7("does not double-claim a held lease", async () => {
|
|
820
1203
|
const s = `claim-held-${uid()}`;
|
|
821
1204
|
const other = `claim-other-${uid()}`;
|
|
822
1205
|
await store.subscribe([{ stream: s }]);
|
|
@@ -827,7 +1210,7 @@ var runStoreTck = (options) => {
|
|
|
827
1210
|
);
|
|
828
1211
|
const leasedA = await store.claim(100, 0, `wA-${uid()}`, 1e5);
|
|
829
1212
|
const targetA = leasedA.find((l) => l.stream === s);
|
|
830
|
-
|
|
1213
|
+
expect8(targetA).toBeDefined();
|
|
831
1214
|
await store.subscribe([{ stream: other }]);
|
|
832
1215
|
await store.commit(
|
|
833
1216
|
other,
|
|
@@ -835,11 +1218,11 @@ var runStoreTck = (options) => {
|
|
|
835
1218
|
make_meta({ stream: other })
|
|
836
1219
|
);
|
|
837
1220
|
const leasedB = await store.claim(100, 0, `wB-${uid()}`, 1e5);
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1221
|
+
expect8(leasedB.length).toBeGreaterThan(0);
|
|
1222
|
+
expect8(leasedB.find((l) => l.stream === s)).toBeUndefined();
|
|
1223
|
+
expect8(leasedB.find((l) => l.stream === other)).toBeDefined();
|
|
841
1224
|
});
|
|
842
|
-
|
|
1225
|
+
it7("supports dual frontiers (lagging + leading)", async () => {
|
|
843
1226
|
const s = `claim-dual-${uid()}`;
|
|
844
1227
|
await store.subscribe([{ stream: s }]);
|
|
845
1228
|
await store.commit(
|
|
@@ -849,12 +1232,12 @@ var runStoreTck = (options) => {
|
|
|
849
1232
|
);
|
|
850
1233
|
const first = await store.claim(100, 0, `w-${uid()}`, 1);
|
|
851
1234
|
const mine = first.find((l) => l.stream === s);
|
|
852
|
-
|
|
1235
|
+
expect8(mine).toBeDefined();
|
|
853
1236
|
await store.ack([{ ...mine, at: mine.at + 1 }]);
|
|
854
1237
|
const second = await store.claim(0, 100, `w-${uid()}`, 1);
|
|
855
|
-
|
|
1238
|
+
expect8(second.find((l) => l.stream === s)).toBeDefined();
|
|
856
1239
|
});
|
|
857
|
-
|
|
1240
|
+
it7("dedupes when both frontiers would return the same stream", async () => {
|
|
858
1241
|
const s = `claim-dedup-${uid()}`;
|
|
859
1242
|
await store.subscribe([{ stream: s }]);
|
|
860
1243
|
await store.commit(
|
|
@@ -864,9 +1247,9 @@ var runStoreTck = (options) => {
|
|
|
864
1247
|
);
|
|
865
1248
|
const claimed = await store.claim(100, 100, `w-${uid()}`, 1e5);
|
|
866
1249
|
const matches = claimed.filter((l) => l.stream === s);
|
|
867
|
-
|
|
1250
|
+
expect8(matches).toHaveLength(1);
|
|
868
1251
|
});
|
|
869
|
-
|
|
1252
|
+
it7("silently ignores ack from the wrong holder", async () => {
|
|
870
1253
|
const s = `ack-wrong-${uid()}`;
|
|
871
1254
|
const sibling = `ack-sibling-${uid()}`;
|
|
872
1255
|
await store.subscribe([{ stream: s }, { stream: sibling }]);
|
|
@@ -883,40 +1266,40 @@ var runStoreTck = (options) => {
|
|
|
883
1266
|
const leased = await store.claim(100, 0, `right-${uid()}`, 1e5);
|
|
884
1267
|
const mine = leased.find((l) => l.stream === s);
|
|
885
1268
|
const sibling_lease = leased.find((l) => l.stream === sibling);
|
|
886
|
-
|
|
887
|
-
|
|
1269
|
+
expect8(mine).toBeDefined();
|
|
1270
|
+
expect8(sibling_lease).toBeDefined();
|
|
888
1271
|
const acked = await store.ack([
|
|
889
1272
|
{ ...mine, by: "imposter" },
|
|
890
1273
|
sibling_lease
|
|
891
1274
|
]);
|
|
892
|
-
|
|
893
|
-
|
|
1275
|
+
expect8(acked.length).toBeGreaterThan(0);
|
|
1276
|
+
expect8(acked.find((l) => l.stream === s)).toBeUndefined();
|
|
894
1277
|
});
|
|
895
|
-
|
|
1278
|
+
it7("ack with a stale (lower) watermark does not throw", async () => {
|
|
896
1279
|
const s = `ack-stale-${uid()}`;
|
|
897
1280
|
await store.subscribe([{ stream: s }]);
|
|
898
1281
|
const by = `w-${uid()}`;
|
|
899
1282
|
const leased = await store.claim(100, 0, by, 1e5);
|
|
900
1283
|
const mine = leased.find((l) => l.stream === s);
|
|
901
|
-
|
|
902
|
-
await
|
|
1284
|
+
expect8(mine).toBeDefined();
|
|
1285
|
+
await expect8(
|
|
903
1286
|
store.ack([{ ...mine, at: -5 }])
|
|
904
1287
|
).resolves.toBeDefined();
|
|
905
1288
|
});
|
|
906
|
-
|
|
1289
|
+
it7("claim with no subscribed streams returns an empty array", async () => {
|
|
907
1290
|
const fresh = await options.factory();
|
|
908
1291
|
try {
|
|
909
1292
|
await fresh.drop();
|
|
910
1293
|
await fresh.seed();
|
|
911
1294
|
const claimed = await fresh.claim(1, 1, `w-${uid()}`, 1e3);
|
|
912
|
-
|
|
1295
|
+
expect8(claimed).toEqual([]);
|
|
913
1296
|
} finally {
|
|
914
1297
|
await fresh.dispose();
|
|
915
1298
|
}
|
|
916
1299
|
});
|
|
917
1300
|
});
|
|
918
|
-
|
|
919
|
-
|
|
1301
|
+
describe8("lease semantics", () => {
|
|
1302
|
+
it7("returns retry=0 on first claim and increments on re-claim without ack", async () => {
|
|
920
1303
|
const fresh = await options.factory();
|
|
921
1304
|
try {
|
|
922
1305
|
await fresh.drop();
|
|
@@ -930,17 +1313,17 @@ var runStoreTck = (options) => {
|
|
|
930
1313
|
);
|
|
931
1314
|
const first = await fresh.claim(1, 0, `w-${uid()}`, 0);
|
|
932
1315
|
const f = first.find((l) => l.stream === s);
|
|
933
|
-
|
|
934
|
-
|
|
1316
|
+
expect8(f).toBeDefined();
|
|
1317
|
+
expect8(f.retry).toBe(0);
|
|
935
1318
|
const second = await fresh.claim(1, 0, `w-${uid()}`, 1e5);
|
|
936
1319
|
const sec = second.find((l) => l.stream === s);
|
|
937
|
-
|
|
938
|
-
|
|
1320
|
+
expect8(sec).toBeDefined();
|
|
1321
|
+
expect8(sec.retry).toBe(1);
|
|
939
1322
|
} finally {
|
|
940
1323
|
await fresh.dispose();
|
|
941
1324
|
}
|
|
942
1325
|
});
|
|
943
|
-
|
|
1326
|
+
it7("reports lagging=true from the lagging frontier and false from the leading frontier", async () => {
|
|
944
1327
|
const fresh = await options.factory();
|
|
945
1328
|
try {
|
|
946
1329
|
await fresh.drop();
|
|
@@ -953,16 +1336,16 @@ var runStoreTck = (options) => {
|
|
|
953
1336
|
make_meta({ stream: s })
|
|
954
1337
|
);
|
|
955
1338
|
const lag = await fresh.claim(1, 0, `w-${uid()}`, 0);
|
|
956
|
-
|
|
1339
|
+
expect8(lag.find((l) => l.stream === s)?.lagging).toBe(true);
|
|
957
1340
|
const lead = await fresh.claim(0, 1, `w-${uid()}`, 1e5);
|
|
958
|
-
|
|
1341
|
+
expect8(lead.find((l) => l.stream === s)?.lagging).toBe(false);
|
|
959
1342
|
} finally {
|
|
960
1343
|
await fresh.dispose();
|
|
961
1344
|
}
|
|
962
1345
|
});
|
|
963
1346
|
});
|
|
964
|
-
|
|
965
|
-
|
|
1347
|
+
describe8.skipIf(!caps.concurrent_claim)("concurrency (capability)", () => {
|
|
1348
|
+
it7("never double-leases a stream across concurrent claimers", async () => {
|
|
966
1349
|
const fresh = await options.factory();
|
|
967
1350
|
try {
|
|
968
1351
|
await fresh.drop();
|
|
@@ -985,15 +1368,15 @@ var runStoreTck = (options) => {
|
|
|
985
1368
|
fresh.claim(100, 100, `wB-${uid()}`, 6e4)
|
|
986
1369
|
]);
|
|
987
1370
|
const claimed = [...a, ...b].map((l) => l.stream).filter((stream) => owned.has(stream));
|
|
988
|
-
|
|
989
|
-
|
|
1371
|
+
expect8(new Set(claimed).size).toBe(claimed.length);
|
|
1372
|
+
expect8(claimed.length).toBe(owned.size);
|
|
990
1373
|
} finally {
|
|
991
1374
|
await fresh.dispose();
|
|
992
1375
|
}
|
|
993
1376
|
});
|
|
994
1377
|
});
|
|
995
|
-
|
|
996
|
-
|
|
1378
|
+
describe8("block", () => {
|
|
1379
|
+
it7("hides blocked streams from claim", async () => {
|
|
997
1380
|
const s = `block-${uid()}`;
|
|
998
1381
|
await store.subscribe([{ stream: s }]);
|
|
999
1382
|
await store.commit(
|
|
@@ -1003,18 +1386,18 @@ var runStoreTck = (options) => {
|
|
|
1003
1386
|
);
|
|
1004
1387
|
const leased = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1005
1388
|
const mine = leased.find((l) => l.stream === s);
|
|
1006
|
-
|
|
1389
|
+
expect8(mine).toBeDefined();
|
|
1007
1390
|
const others = leased.filter((l) => l.stream !== s);
|
|
1008
1391
|
await store.ack(others);
|
|
1009
1392
|
const blocked = await store.block([
|
|
1010
1393
|
{ ...mine, error: "boom" }
|
|
1011
1394
|
]);
|
|
1012
|
-
|
|
1013
|
-
|
|
1395
|
+
expect8(blocked).toHaveLength(1);
|
|
1396
|
+
expect8(blocked[0].error).toBe("boom");
|
|
1014
1397
|
const again = await store.claim(100, 100, `w2-${uid()}`, 1e5);
|
|
1015
|
-
|
|
1398
|
+
expect8(again.find((l) => l.stream === s)).toBeUndefined();
|
|
1016
1399
|
});
|
|
1017
|
-
|
|
1400
|
+
it7("rejects block calls from a different holder", async () => {
|
|
1018
1401
|
const s = `block-wrong-${uid()}`;
|
|
1019
1402
|
await store.subscribe([{ stream: s }]);
|
|
1020
1403
|
await store.commit(
|
|
@@ -1024,17 +1407,115 @@ var runStoreTck = (options) => {
|
|
|
1024
1407
|
);
|
|
1025
1408
|
const leased = await store.claim(100, 0, `right-${uid()}`, 1e5);
|
|
1026
1409
|
const mine = leased.find((l) => l.stream === s);
|
|
1027
|
-
|
|
1410
|
+
expect8(mine).toBeDefined();
|
|
1028
1411
|
const others = leased.filter((l) => l.stream !== s);
|
|
1029
1412
|
await store.ack(others);
|
|
1030
1413
|
const blocked = await store.block([
|
|
1031
1414
|
{ ...mine, by: "imposter", error: "no" }
|
|
1032
1415
|
]);
|
|
1033
|
-
|
|
1416
|
+
expect8(blocked).toHaveLength(0);
|
|
1417
|
+
});
|
|
1418
|
+
});
|
|
1419
|
+
describe8("defer", () => {
|
|
1420
|
+
it7("hides a stream from claim until its deferred_at passes", async () => {
|
|
1421
|
+
const s = `defer-${uid()}`;
|
|
1422
|
+
const ctl = `defer-ctl-${uid()}`;
|
|
1423
|
+
await store.subscribe([{ stream: s }, { stream: ctl }]);
|
|
1424
|
+
for (const st of [s, ctl])
|
|
1425
|
+
await store.commit(
|
|
1426
|
+
st,
|
|
1427
|
+
[inc(1)],
|
|
1428
|
+
make_meta({ stream: st })
|
|
1429
|
+
);
|
|
1430
|
+
expect8(await store.defer([s], Date.now() + 36e5)).toBe(1);
|
|
1431
|
+
const leased = await store.claim(100, 100, `w-${uid()}`, 1e5);
|
|
1432
|
+
expect8(leased.find((l) => l.stream === ctl)).toBeDefined();
|
|
1433
|
+
expect8(leased.find((l) => l.stream === s)).toBeUndefined();
|
|
1434
|
+
});
|
|
1435
|
+
it7("makes a stream claimable once the deferred_at is in the past", async () => {
|
|
1436
|
+
const s = `defer-past-${uid()}`;
|
|
1437
|
+
await store.subscribe([{ stream: s }]);
|
|
1438
|
+
await store.commit(
|
|
1439
|
+
s,
|
|
1440
|
+
[inc(1)],
|
|
1441
|
+
make_meta({ stream: s })
|
|
1442
|
+
);
|
|
1443
|
+
expect8(await store.defer([s], Date.now() - 1e3)).toBe(1);
|
|
1444
|
+
const leased = await store.claim(100, 100, `w-${uid()}`, 1e5);
|
|
1445
|
+
const mine = leased.find((l) => l.stream === s);
|
|
1446
|
+
expect8(mine).toBeDefined();
|
|
1447
|
+
await store.ack(
|
|
1448
|
+
leased.filter((l) => l.stream !== s).concat(mine)
|
|
1449
|
+
);
|
|
1450
|
+
});
|
|
1451
|
+
it7("does not bump retry while a stream is deferred", async () => {
|
|
1452
|
+
const s = `defer-retry-${uid()}`;
|
|
1453
|
+
await store.subscribe([{ stream: s }]);
|
|
1454
|
+
await store.commit(
|
|
1455
|
+
s,
|
|
1456
|
+
[inc(1)],
|
|
1457
|
+
make_meta({ stream: s })
|
|
1458
|
+
);
|
|
1459
|
+
await store.defer([s], Date.now() + 36e5);
|
|
1460
|
+
await store.claim(100, 100, `w1-${uid()}`, 1e5);
|
|
1461
|
+
await store.claim(100, 100, `w2-${uid()}`, 1e5);
|
|
1462
|
+
await store.defer([s], Date.now() - 1e3);
|
|
1463
|
+
const leased = await store.claim(100, 100, `w3-${uid()}`, 1e5);
|
|
1464
|
+
const mine = leased.find((l) => l.stream === s);
|
|
1465
|
+
expect8(mine).toBeDefined();
|
|
1466
|
+
expect8(mine.retry).toBe(0);
|
|
1467
|
+
await store.ack(
|
|
1468
|
+
leased.filter((l) => l.stream !== s).concat(mine)
|
|
1469
|
+
);
|
|
1470
|
+
});
|
|
1471
|
+
it7("reset clears a pending defer", async () => {
|
|
1472
|
+
const s = `defer-reset-${uid()}`;
|
|
1473
|
+
await store.subscribe([{ stream: s }]);
|
|
1474
|
+
await store.commit(
|
|
1475
|
+
s,
|
|
1476
|
+
[inc(1)],
|
|
1477
|
+
make_meta({ stream: s })
|
|
1478
|
+
);
|
|
1479
|
+
await store.defer([s], Date.now() + 36e5);
|
|
1480
|
+
expect8(await store.reset([s])).toBe(1);
|
|
1481
|
+
const leased = await store.claim(100, 100, `w-${uid()}`, 1e5);
|
|
1482
|
+
expect8(leased.find((l) => l.stream === s)).toBeDefined();
|
|
1483
|
+
});
|
|
1484
|
+
it7("defers streams matching a filter and counts matches", async () => {
|
|
1485
|
+
const tag = uid();
|
|
1486
|
+
const a = `deferfilter-${tag}-a`;
|
|
1487
|
+
const b = `deferfilter-${tag}-b`;
|
|
1488
|
+
await store.subscribe([{ stream: a }, { stream: b }]);
|
|
1489
|
+
for (const s of [a, b])
|
|
1490
|
+
await store.commit(
|
|
1491
|
+
s,
|
|
1492
|
+
[inc(1)],
|
|
1493
|
+
make_meta({ stream: s })
|
|
1494
|
+
);
|
|
1495
|
+
const ctl = `defer-filterctl-${tag}`;
|
|
1496
|
+
await store.subscribe([{ stream: ctl }]);
|
|
1497
|
+
await store.commit(
|
|
1498
|
+
ctl,
|
|
1499
|
+
[inc(1)],
|
|
1500
|
+
make_meta({ stream: ctl })
|
|
1501
|
+
);
|
|
1502
|
+
const n = await store.defer(
|
|
1503
|
+
{ stream: `^deferfilter-${tag}-`, stream_exact: false },
|
|
1504
|
+
Date.now() + 36e5
|
|
1505
|
+
);
|
|
1506
|
+
expect8(n).toBe(2);
|
|
1507
|
+
const leased = await store.claim(100, 100, `w-${uid()}`, 1e5);
|
|
1508
|
+
expect8(leased.find((l) => l.stream === ctl)).toBeDefined();
|
|
1509
|
+
expect8(leased.find((l) => l.stream === a)).toBeUndefined();
|
|
1510
|
+
expect8(leased.find((l) => l.stream === b)).toBeUndefined();
|
|
1511
|
+
});
|
|
1512
|
+
it7("returns 0 for unknown streams and empty input", async () => {
|
|
1513
|
+
expect8(await store.defer([`missing-${uid()}`], Date.now())).toBe(0);
|
|
1514
|
+
expect8(await store.defer([], Date.now())).toBe(0);
|
|
1034
1515
|
});
|
|
1035
1516
|
});
|
|
1036
|
-
|
|
1037
|
-
|
|
1517
|
+
describe8("reset", () => {
|
|
1518
|
+
it7("rewinds a stream watermark to -1", async () => {
|
|
1038
1519
|
const s = `reset-${uid()}`;
|
|
1039
1520
|
await store.subscribe([{ stream: s }]);
|
|
1040
1521
|
await store.commit(
|
|
@@ -1044,15 +1525,15 @@ var runStoreTck = (options) => {
|
|
|
1044
1525
|
);
|
|
1045
1526
|
const leased = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1046
1527
|
const mine = leased.find((l) => l.stream === s);
|
|
1047
|
-
|
|
1528
|
+
expect8(mine).toBeDefined();
|
|
1048
1529
|
await store.ack([{ ...mine, at: 99 }]);
|
|
1049
|
-
|
|
1530
|
+
expect8(await store.reset([s])).toBe(1);
|
|
1050
1531
|
const after = await store.claim(100, 0, `w2-${uid()}`, 1e5);
|
|
1051
1532
|
const back = after.find((l) => l.stream === s);
|
|
1052
|
-
|
|
1053
|
-
|
|
1533
|
+
expect8(back).toBeDefined();
|
|
1534
|
+
expect8(back.at).toBe(-1);
|
|
1054
1535
|
});
|
|
1055
|
-
|
|
1536
|
+
it7("clears blocked status when resetting", async () => {
|
|
1056
1537
|
const s = `reset-blk-${uid()}`;
|
|
1057
1538
|
await store.subscribe([{ stream: s }]);
|
|
1058
1539
|
await store.commit(
|
|
@@ -1065,17 +1546,17 @@ var runStoreTck = (options) => {
|
|
|
1065
1546
|
const others = leased.filter((l) => l.stream !== s);
|
|
1066
1547
|
await store.ack(others);
|
|
1067
1548
|
await store.block([{ ...mine, error: "boom" }]);
|
|
1068
|
-
|
|
1549
|
+
expect8(await store.reset([s])).toBe(1);
|
|
1069
1550
|
const after = await store.claim(100, 0, `w2-${uid()}`, 1e5);
|
|
1070
|
-
|
|
1551
|
+
expect8(after.find((l) => l.stream === s)).toBeDefined();
|
|
1071
1552
|
});
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1553
|
+
it7("returns 0 for unknown streams and empty input", async () => {
|
|
1554
|
+
expect8(await store.reset([`missing-${uid()}`])).toBe(0);
|
|
1555
|
+
expect8(await store.reset([])).toBe(0);
|
|
1075
1556
|
});
|
|
1076
1557
|
});
|
|
1077
|
-
|
|
1078
|
-
|
|
1558
|
+
describe8("unblock", () => {
|
|
1559
|
+
it7("clears blocked flag and preserves the watermark", async () => {
|
|
1079
1560
|
const s = `unblock-${uid()}`;
|
|
1080
1561
|
await store.subscribe([{ stream: s }]);
|
|
1081
1562
|
await store.commit(
|
|
@@ -1093,7 +1574,7 @@ var runStoreTck = (options) => {
|
|
|
1093
1574
|
await store.ack([{ ...m1, at: m1.at }]);
|
|
1094
1575
|
const before_block = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1095
1576
|
const m2 = before_block.find((l) => l.stream === s);
|
|
1096
|
-
|
|
1577
|
+
expect8(m2).toBeDefined();
|
|
1097
1578
|
const watermark_before = m2.at;
|
|
1098
1579
|
await store.block([{ ...m2, error: "permanent" }]);
|
|
1099
1580
|
let blocked_flag;
|
|
@@ -1103,15 +1584,15 @@ var runStoreTck = (options) => {
|
|
|
1103
1584
|
},
|
|
1104
1585
|
{ stream: s, stream_exact: true, limit: 1 }
|
|
1105
1586
|
);
|
|
1106
|
-
|
|
1107
|
-
|
|
1587
|
+
expect8(blocked_flag).toBe(true);
|
|
1588
|
+
expect8(await store.unblock([s])).toBe(1);
|
|
1108
1589
|
const after = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1109
1590
|
const back = after.find((l) => l.stream === s);
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1591
|
+
expect8(back).toBeDefined();
|
|
1592
|
+
expect8(back.at).toBe(watermark_before);
|
|
1593
|
+
expect8(back.retry).toBe(0);
|
|
1113
1594
|
});
|
|
1114
|
-
|
|
1595
|
+
it7("returns 0 when the stream is not blocked", async () => {
|
|
1115
1596
|
const s = `unblock-noop-${uid()}`;
|
|
1116
1597
|
await store.subscribe([{ stream: s }]);
|
|
1117
1598
|
await store.commit(
|
|
@@ -1119,13 +1600,13 @@ var runStoreTck = (options) => {
|
|
|
1119
1600
|
[inc(1)],
|
|
1120
1601
|
make_meta({ stream: s })
|
|
1121
1602
|
);
|
|
1122
|
-
|
|
1603
|
+
expect8(await store.unblock([s])).toBe(0);
|
|
1123
1604
|
});
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1605
|
+
it7("returns 0 for unknown streams and empty input", async () => {
|
|
1606
|
+
expect8(await store.unblock([`missing-${uid()}`])).toBe(0);
|
|
1607
|
+
expect8(await store.unblock([])).toBe(0);
|
|
1127
1608
|
});
|
|
1128
|
-
|
|
1609
|
+
it7("only counts streams that were actually blocked", async () => {
|
|
1129
1610
|
const s1 = `unblock-mix-a-${uid()}`;
|
|
1130
1611
|
const s2 = `unblock-mix-b-${uid()}`;
|
|
1131
1612
|
await store.subscribe([{ stream: s1 }, { stream: s2 }]);
|
|
@@ -1144,9 +1625,9 @@ var runStoreTck = (options) => {
|
|
|
1144
1625
|
const others = leased.filter((l) => l.stream !== s1);
|
|
1145
1626
|
await store.ack(others);
|
|
1146
1627
|
await store.block([{ ...m1, error: "boom" }]);
|
|
1147
|
-
|
|
1628
|
+
expect8(await store.unblock([s1, s2])).toBe(1);
|
|
1148
1629
|
});
|
|
1149
|
-
|
|
1630
|
+
it7("filter form: unblocks by stream pattern", async () => {
|
|
1150
1631
|
const tag = uid();
|
|
1151
1632
|
const s1 = `unblock-filter-${tag}-a`;
|
|
1152
1633
|
const s2 = `unblock-filter-${tag}-b`;
|
|
@@ -1178,13 +1659,13 @@ var runStoreTck = (options) => {
|
|
|
1178
1659
|
const count = await store.unblock({
|
|
1179
1660
|
stream: `^unblock-filter-${tag}-`
|
|
1180
1661
|
});
|
|
1181
|
-
|
|
1662
|
+
expect8(count).toBe(2);
|
|
1182
1663
|
const after = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1664
|
+
expect8(after.find((l) => l.stream === s3)).toBeUndefined();
|
|
1665
|
+
expect8(after.find((l) => l.stream === s1)).toBeDefined();
|
|
1666
|
+
expect8(after.find((l) => l.stream === s2)).toBeDefined();
|
|
1186
1667
|
});
|
|
1187
|
-
|
|
1668
|
+
it7("filter form: empty filter unblocks every blocked stream", async () => {
|
|
1188
1669
|
const tag = uid();
|
|
1189
1670
|
const s1 = `unblock-empty-${tag}-a`;
|
|
1190
1671
|
const s2 = `unblock-empty-${tag}-b`;
|
|
@@ -1208,9 +1689,9 @@ var runStoreTck = (options) => {
|
|
|
1208
1689
|
const count = await store.unblock({
|
|
1209
1690
|
stream: `^unblock-empty-${tag}-`
|
|
1210
1691
|
});
|
|
1211
|
-
|
|
1692
|
+
expect8(count).toBe(2);
|
|
1212
1693
|
});
|
|
1213
|
-
|
|
1694
|
+
it7("filter form: explicit blocked:false matches nothing", async () => {
|
|
1214
1695
|
const tag = uid();
|
|
1215
1696
|
const s = `unblock-blocked-false-${tag}`;
|
|
1216
1697
|
await store.subscribe([{ stream: s }]);
|
|
@@ -1219,7 +1700,7 @@ var runStoreTck = (options) => {
|
|
|
1219
1700
|
[inc(1)],
|
|
1220
1701
|
make_meta({ stream: s })
|
|
1221
1702
|
);
|
|
1222
|
-
|
|
1703
|
+
expect8(
|
|
1223
1704
|
await store.unblock({
|
|
1224
1705
|
stream: `^unblock-blocked-false-${tag}`,
|
|
1225
1706
|
blocked: false
|
|
@@ -1227,8 +1708,8 @@ var runStoreTck = (options) => {
|
|
|
1227
1708
|
).toBe(0);
|
|
1228
1709
|
});
|
|
1229
1710
|
});
|
|
1230
|
-
|
|
1231
|
-
|
|
1711
|
+
describe8("reset filter form", () => {
|
|
1712
|
+
it7("resets streams matching a stream pattern", async () => {
|
|
1232
1713
|
const tag = uid();
|
|
1233
1714
|
const s1 = `reset-filter-${tag}-a`;
|
|
1234
1715
|
const s2 = `reset-filter-${tag}-b`;
|
|
@@ -1259,7 +1740,7 @@ var runStoreTck = (options) => {
|
|
|
1259
1740
|
);
|
|
1260
1741
|
await store.ack(mine.map((l) => ({ ...l, at: l.at + 100 })));
|
|
1261
1742
|
const count = await store.reset({ stream: `^reset-filter-${tag}-` });
|
|
1262
|
-
|
|
1743
|
+
expect8(count).toBe(2);
|
|
1263
1744
|
const position_for = async (name) => {
|
|
1264
1745
|
let at = null;
|
|
1265
1746
|
await store.query_streams(
|
|
@@ -1270,11 +1751,11 @@ var runStoreTck = (options) => {
|
|
|
1270
1751
|
);
|
|
1271
1752
|
return at;
|
|
1272
1753
|
};
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1754
|
+
expect8(await position_for(s1)).toBe(-1);
|
|
1755
|
+
expect8(await position_for(s2)).toBe(-1);
|
|
1756
|
+
expect8(await position_for(other)).toBeGreaterThan(-1);
|
|
1276
1757
|
});
|
|
1277
|
-
|
|
1758
|
+
it7("filter form: resets only blocked streams when blocked:true", async () => {
|
|
1278
1759
|
const tag = uid();
|
|
1279
1760
|
const s1 = `reset-blocked-${tag}-blocked`;
|
|
1280
1761
|
const s2 = `reset-blocked-${tag}-fine`;
|
|
@@ -1297,11 +1778,11 @@ var runStoreTck = (options) => {
|
|
|
1297
1778
|
stream: `^reset-blocked-${tag}-`,
|
|
1298
1779
|
blocked: true
|
|
1299
1780
|
});
|
|
1300
|
-
|
|
1781
|
+
expect8(count).toBe(1);
|
|
1301
1782
|
});
|
|
1302
1783
|
});
|
|
1303
|
-
|
|
1304
|
-
|
|
1784
|
+
describe8("prioritize", () => {
|
|
1785
|
+
it7("sets priority directly, overriding subscribe's max() rule", async () => {
|
|
1305
1786
|
const tag = uid();
|
|
1306
1787
|
const s1 = `pri-${tag}-a`;
|
|
1307
1788
|
const s2 = `pri-${tag}-b`;
|
|
@@ -1313,7 +1794,7 @@ var runStoreTck = (options) => {
|
|
|
1313
1794
|
{ stream: s1, stream_exact: true },
|
|
1314
1795
|
3
|
|
1315
1796
|
);
|
|
1316
|
-
|
|
1797
|
+
expect8(updated).toBe(1);
|
|
1317
1798
|
const got1 = {};
|
|
1318
1799
|
const got2 = {};
|
|
1319
1800
|
await store.query_streams(
|
|
@@ -1323,12 +1804,12 @@ var runStoreTck = (options) => {
|
|
|
1323
1804
|
},
|
|
1324
1805
|
{ stream: `pri-${tag}-.*`, limit: 100 }
|
|
1325
1806
|
);
|
|
1326
|
-
|
|
1327
|
-
|
|
1807
|
+
expect8(got1.priority).toBe(3);
|
|
1808
|
+
expect8(got2.priority).toBe(5);
|
|
1328
1809
|
});
|
|
1329
1810
|
});
|
|
1330
|
-
|
|
1331
|
-
|
|
1811
|
+
describe8("lanes", () => {
|
|
1812
|
+
it7("subscribe defaults lane to 'default' when omitted", async () => {
|
|
1332
1813
|
const s = `lane-default-${uid()}`;
|
|
1333
1814
|
await store.subscribe([{ stream: s }]);
|
|
1334
1815
|
const seen = [];
|
|
@@ -1336,9 +1817,9 @@ var runStoreTck = (options) => {
|
|
|
1336
1817
|
stream: s,
|
|
1337
1818
|
stream_exact: true
|
|
1338
1819
|
});
|
|
1339
|
-
|
|
1820
|
+
expect8(seen).toEqual(["default"]);
|
|
1340
1821
|
});
|
|
1341
|
-
|
|
1822
|
+
it7("subscribe records the lane passed in", async () => {
|
|
1342
1823
|
const s = `lane-set-${uid()}`;
|
|
1343
1824
|
await store.subscribe([{ stream: s, lane: "slow" }]);
|
|
1344
1825
|
const seen = [];
|
|
@@ -1346,9 +1827,9 @@ var runStoreTck = (options) => {
|
|
|
1346
1827
|
stream: s,
|
|
1347
1828
|
stream_exact: true
|
|
1348
1829
|
});
|
|
1349
|
-
|
|
1830
|
+
expect8(seen).toEqual(["slow"]);
|
|
1350
1831
|
});
|
|
1351
|
-
|
|
1832
|
+
it7("subscribe re-lanes existing streams on subsequent calls", async () => {
|
|
1352
1833
|
const s = `lane-upsert-${uid()}`;
|
|
1353
1834
|
await store.subscribe([{ stream: s, lane: "slow" }]);
|
|
1354
1835
|
await store.subscribe([{ stream: s, lane: "fast" }]);
|
|
@@ -1357,9 +1838,9 @@ var runStoreTck = (options) => {
|
|
|
1357
1838
|
stream: s,
|
|
1358
1839
|
stream_exact: true
|
|
1359
1840
|
});
|
|
1360
|
-
|
|
1841
|
+
expect8(seen).toEqual(["fast"]);
|
|
1361
1842
|
});
|
|
1362
|
-
|
|
1843
|
+
it7("claim() filters by lane when supplied and returns lane on the Lease", async () => {
|
|
1363
1844
|
const tag = uid();
|
|
1364
1845
|
const src1 = `lane-claim-src1-${tag}`;
|
|
1365
1846
|
const src2 = `lane-claim-src2-${tag}`;
|
|
@@ -1383,19 +1864,19 @@ var runStoreTck = (options) => {
|
|
|
1383
1864
|
const slow_mine = slow.filter(
|
|
1384
1865
|
(l) => l.stream === sub_default || l.stream === sub_slow
|
|
1385
1866
|
);
|
|
1386
|
-
|
|
1387
|
-
|
|
1867
|
+
expect8(slow_mine.map((l) => l.stream)).toEqual([sub_slow]);
|
|
1868
|
+
expect8(slow_mine[0]?.lane).toBe("slow");
|
|
1388
1869
|
await store.ack(slow_mine.map((l) => ({ ...l, at: l.at + 1 })));
|
|
1389
1870
|
const all = await store.claim(50, 0, `w-all-${tag}`, 1e3);
|
|
1390
1871
|
const all_mine = all.filter((l) => l.stream === sub_default || l.stream === sub_slow).map((l) => ({ stream: l.stream, lane: l.lane }));
|
|
1391
|
-
|
|
1392
|
-
|
|
1872
|
+
expect8(all_mine).toEqual(
|
|
1873
|
+
expect8.arrayContaining([
|
|
1393
1874
|
{ stream: sub_default, lane: "default" },
|
|
1394
1875
|
{ stream: sub_slow, lane: "slow" }
|
|
1395
1876
|
])
|
|
1396
1877
|
);
|
|
1397
1878
|
});
|
|
1398
|
-
|
|
1879
|
+
it7("query_streams filters by lane", async () => {
|
|
1399
1880
|
const tag = uid();
|
|
1400
1881
|
const a = `lane-q-a-${tag}`;
|
|
1401
1882
|
const b = `lane-q-b-${tag}`;
|
|
@@ -1411,9 +1892,9 @@ var runStoreTck = (options) => {
|
|
|
1411
1892
|
stream: `lane-q-.*-${tag}`,
|
|
1412
1893
|
limit: 100
|
|
1413
1894
|
});
|
|
1414
|
-
|
|
1895
|
+
expect8(seen.sort()).toEqual([a, c]);
|
|
1415
1896
|
});
|
|
1416
|
-
|
|
1897
|
+
it7("prioritize filters by lane", async () => {
|
|
1417
1898
|
const tag = uid();
|
|
1418
1899
|
const a = `lane-pri-a-${tag}`;
|
|
1419
1900
|
const b = `lane-pri-b-${tag}`;
|
|
@@ -1422,16 +1903,16 @@ var runStoreTck = (options) => {
|
|
|
1422
1903
|
{ stream: b, lane: `pfast-${tag}` }
|
|
1423
1904
|
]);
|
|
1424
1905
|
const updated = await store.prioritize({ lane: `pslow-${tag}` }, 7);
|
|
1425
|
-
|
|
1906
|
+
expect8(updated).toBe(1);
|
|
1426
1907
|
const seen = /* @__PURE__ */ new Map();
|
|
1427
1908
|
await store.query_streams((p) => seen.set(p.stream, p.priority), {
|
|
1428
1909
|
stream: `lane-pri-.*-${tag}`,
|
|
1429
1910
|
limit: 100
|
|
1430
1911
|
});
|
|
1431
|
-
|
|
1432
|
-
|
|
1912
|
+
expect8(seen.get(a)).toBe(7);
|
|
1913
|
+
expect8(seen.get(b)).toBe(0);
|
|
1433
1914
|
});
|
|
1434
|
-
|
|
1915
|
+
it7("reset filters by lane", async () => {
|
|
1435
1916
|
const tag = uid();
|
|
1436
1917
|
const src = `lane-reset-src-${tag}`;
|
|
1437
1918
|
const a = `lane-reset-a-${tag}`;
|
|
@@ -1449,7 +1930,7 @@ var runStoreTck = (options) => {
|
|
|
1449
1930
|
const mine = leases.filter((l) => l.stream === a || l.stream === b);
|
|
1450
1931
|
await store.ack(mine.map((l) => ({ ...l, at: l.at + 1 })));
|
|
1451
1932
|
const count = await store.reset({ lane: `rslow-${tag}` });
|
|
1452
|
-
|
|
1933
|
+
expect8(count).toBe(1);
|
|
1453
1934
|
const ats = /* @__PURE__ */ new Map();
|
|
1454
1935
|
for (const name of [a, b]) {
|
|
1455
1936
|
await store.query_streams((p) => ats.set(p.stream, p.at), {
|
|
@@ -1457,10 +1938,10 @@ var runStoreTck = (options) => {
|
|
|
1457
1938
|
stream_exact: true
|
|
1458
1939
|
});
|
|
1459
1940
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1941
|
+
expect8(ats.get(a)).toBe(-1);
|
|
1942
|
+
expect8(ats.get(b)).toBeGreaterThanOrEqual(0);
|
|
1462
1943
|
});
|
|
1463
|
-
|
|
1944
|
+
it7("unblock filters by lane", async () => {
|
|
1464
1945
|
const tag = uid();
|
|
1465
1946
|
const src = `lane-ub-src-${tag}`;
|
|
1466
1947
|
const a = `lane-ub-a-${tag}`;
|
|
@@ -1478,7 +1959,7 @@ var runStoreTck = (options) => {
|
|
|
1478
1959
|
const mine = leases.filter((l) => l.stream === a || l.stream === b);
|
|
1479
1960
|
await store.block(mine.map((l) => ({ ...l, error: "boom" })));
|
|
1480
1961
|
const count = await store.unblock({ lane: `uslow-${tag}` });
|
|
1481
|
-
|
|
1962
|
+
expect8(count).toBe(1);
|
|
1482
1963
|
const blocked = /* @__PURE__ */ new Map();
|
|
1483
1964
|
for (const name of [a, b]) {
|
|
1484
1965
|
await store.query_streams((p) => blocked.set(p.stream, p.blocked), {
|
|
@@ -1486,12 +1967,12 @@ var runStoreTck = (options) => {
|
|
|
1486
1967
|
stream_exact: true
|
|
1487
1968
|
});
|
|
1488
1969
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1970
|
+
expect8(blocked.get(a)).toBe(false);
|
|
1971
|
+
expect8(blocked.get(b)).toBe(true);
|
|
1491
1972
|
});
|
|
1492
1973
|
});
|
|
1493
|
-
|
|
1494
|
-
|
|
1974
|
+
describe8("truncate", () => {
|
|
1975
|
+
it7("seeds a tombstone when no snapshot is provided", async () => {
|
|
1495
1976
|
const s = `trunc-tomb-${uid()}`;
|
|
1496
1977
|
await store.commit(
|
|
1497
1978
|
s,
|
|
@@ -1499,7 +1980,7 @@ var runStoreTck = (options) => {
|
|
|
1499
1980
|
make_meta({ stream: s })
|
|
1500
1981
|
);
|
|
1501
1982
|
const result = await store.truncate([{ stream: s }]);
|
|
1502
|
-
|
|
1983
|
+
expect8(result.get(s)?.deleted).toBe(2);
|
|
1503
1984
|
const remaining = [];
|
|
1504
1985
|
await store.query(
|
|
1505
1986
|
(e) => {
|
|
@@ -1507,12 +1988,12 @@ var runStoreTck = (options) => {
|
|
|
1507
1988
|
},
|
|
1508
1989
|
{ stream: s, stream_exact: true }
|
|
1509
1990
|
);
|
|
1510
|
-
|
|
1511
|
-
|
|
1991
|
+
expect8(remaining).toHaveLength(1);
|
|
1992
|
+
expect8(remaining[0].name).toBe(
|
|
1512
1993
|
"__tombstone__"
|
|
1513
1994
|
);
|
|
1514
1995
|
});
|
|
1515
|
-
|
|
1996
|
+
it7("seeds a snapshot when one is provided", async () => {
|
|
1516
1997
|
const s = `trunc-snap-${uid()}`;
|
|
1517
1998
|
await store.commit(
|
|
1518
1999
|
s,
|
|
@@ -1522,7 +2003,7 @@ var runStoreTck = (options) => {
|
|
|
1522
2003
|
const result = await store.truncate([
|
|
1523
2004
|
{ stream: s, snapshot: { count: 7 } }
|
|
1524
2005
|
]);
|
|
1525
|
-
|
|
2006
|
+
expect8(result.get(s)?.deleted).toBe(1);
|
|
1526
2007
|
const remaining = [];
|
|
1527
2008
|
await store.query(
|
|
1528
2009
|
(e) => {
|
|
@@ -1530,24 +2011,24 @@ var runStoreTck = (options) => {
|
|
|
1530
2011
|
},
|
|
1531
2012
|
{ stream: s, stream_exact: true, with_snaps: true }
|
|
1532
2013
|
);
|
|
1533
|
-
|
|
1534
|
-
|
|
2014
|
+
expect8(remaining).toHaveLength(1);
|
|
2015
|
+
expect8(remaining[0].name).toBe(
|
|
1535
2016
|
"__snapshot__"
|
|
1536
2017
|
);
|
|
1537
|
-
|
|
2018
|
+
expect8(remaining[0].data).toEqual({ count: 7 });
|
|
1538
2019
|
});
|
|
1539
|
-
|
|
2020
|
+
it7("returns an empty map for empty input", async () => {
|
|
1540
2021
|
const result = await store.truncate([]);
|
|
1541
|
-
|
|
2022
|
+
expect8(result.size).toBe(0);
|
|
1542
2023
|
});
|
|
1543
|
-
|
|
2024
|
+
it7("returns 0 deleted for streams that don't exist", async () => {
|
|
1544
2025
|
const s = `trunc-missing-${uid()}`;
|
|
1545
2026
|
const result = await store.truncate([{ stream: s }]);
|
|
1546
|
-
|
|
2027
|
+
expect8(result.get(s)?.deleted).toBe(0);
|
|
1547
2028
|
});
|
|
1548
2029
|
});
|
|
1549
|
-
|
|
1550
|
-
|
|
2030
|
+
describe8("query_streams", () => {
|
|
2031
|
+
it7("returns positions filtered by stream regex, exact, source, and source_exact", async () => {
|
|
1551
2032
|
const tag = uid();
|
|
1552
2033
|
const proj1 = `qs-${tag}-projection-tickets`;
|
|
1553
2034
|
const proj2 = `qs-${tag}-projection-users`;
|
|
@@ -1566,37 +2047,37 @@ var runStoreTck = (options) => {
|
|
|
1566
2047
|
(p) => all.push({ stream: p.stream, source: p.source }),
|
|
1567
2048
|
{ stream: `qs-${tag}-.*` }
|
|
1568
2049
|
);
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
2050
|
+
expect8(all_result.count).toBe(4);
|
|
2051
|
+
expect8(all_result.maxEventId).toBeGreaterThanOrEqual(-1);
|
|
2052
|
+
expect8(all.map((p) => p.stream).sort()).toEqual(
|
|
1572
2053
|
[proj1, proj2, dyn1, dyn2].sort()
|
|
1573
2054
|
);
|
|
1574
2055
|
const projections = [];
|
|
1575
2056
|
await store.query_streams((p) => projections.push(p.stream), {
|
|
1576
2057
|
stream: `qs-${tag}-projection-.*`
|
|
1577
2058
|
});
|
|
1578
|
-
|
|
2059
|
+
expect8(projections.sort()).toEqual([proj1, proj2].sort());
|
|
1579
2060
|
const exact = [];
|
|
1580
2061
|
await store.query_streams((p) => exact.push(p.stream), {
|
|
1581
2062
|
stream: dyn1,
|
|
1582
2063
|
stream_exact: true
|
|
1583
2064
|
});
|
|
1584
|
-
|
|
2065
|
+
expect8(exact).toEqual([dyn1]);
|
|
1585
2066
|
const by_source = [];
|
|
1586
2067
|
await store.query_streams((p) => by_source.push(p.stream), {
|
|
1587
2068
|
stream: `qs-${tag}-.*`,
|
|
1588
2069
|
source: `qs-${tag}-src-.*`
|
|
1589
2070
|
});
|
|
1590
|
-
|
|
2071
|
+
expect8(by_source.sort()).toEqual([dyn1, dyn2].sort());
|
|
1591
2072
|
const exact_source = [];
|
|
1592
2073
|
await store.query_streams((p) => exact_source.push(p.stream), {
|
|
1593
2074
|
stream: `qs-${tag}-.*`,
|
|
1594
2075
|
source: src2,
|
|
1595
2076
|
source_exact: true
|
|
1596
2077
|
});
|
|
1597
|
-
|
|
2078
|
+
expect8(exact_source).toEqual([dyn2]);
|
|
1598
2079
|
});
|
|
1599
|
-
|
|
2080
|
+
it7("paginates with limit + after (keyset)", async () => {
|
|
1600
2081
|
const tag = uid();
|
|
1601
2082
|
const streams = [
|
|
1602
2083
|
`qp-${tag}-a`,
|
|
@@ -1610,17 +2091,17 @@ var runStoreTck = (options) => {
|
|
|
1610
2091
|
stream: `qp-${tag}-.*`,
|
|
1611
2092
|
limit: 2
|
|
1612
2093
|
});
|
|
1613
|
-
|
|
2094
|
+
expect8(page1).toHaveLength(2);
|
|
1614
2095
|
const page2 = [];
|
|
1615
2096
|
await store.query_streams((p) => page2.push(p.stream), {
|
|
1616
2097
|
stream: `qp-${tag}-.*`,
|
|
1617
2098
|
limit: 2,
|
|
1618
2099
|
after: page1.at(-1)
|
|
1619
2100
|
});
|
|
1620
|
-
|
|
1621
|
-
|
|
2101
|
+
expect8(page2).toHaveLength(2);
|
|
2102
|
+
expect8([...page1, ...page2].sort()).toEqual([...streams].sort());
|
|
1622
2103
|
});
|
|
1623
|
-
|
|
2104
|
+
it7("filters by blocked status", async () => {
|
|
1624
2105
|
const tag = uid();
|
|
1625
2106
|
const s = `qb-${tag}`;
|
|
1626
2107
|
const sibling = `qb-${tag}-other`;
|
|
@@ -1640,18 +2121,18 @@ var runStoreTck = (options) => {
|
|
|
1640
2121
|
(p) => blocked.push({ stream: p.stream, error: p.error }),
|
|
1641
2122
|
{ stream: `qb-${tag}.*`, blocked: true }
|
|
1642
2123
|
);
|
|
1643
|
-
|
|
1644
|
-
|
|
2124
|
+
expect8(blocked).toHaveLength(1);
|
|
2125
|
+
expect8(blocked[0].error).toBe("boom");
|
|
1645
2126
|
const unblocked = [];
|
|
1646
2127
|
await store.query_streams((p) => unblocked.push(p.stream), {
|
|
1647
2128
|
stream: `qb-${tag}.*`,
|
|
1648
2129
|
blocked: false
|
|
1649
2130
|
});
|
|
1650
|
-
|
|
2131
|
+
expect8(unblocked).toEqual([sibling]);
|
|
1651
2132
|
});
|
|
1652
2133
|
});
|
|
1653
|
-
|
|
1654
|
-
|
|
2134
|
+
describe8("query_stats", () => {
|
|
2135
|
+
it7("array input \u2014 returns head per stream, absent when not in input", async () => {
|
|
1655
2136
|
const tag = uid();
|
|
1656
2137
|
const sA = `qst-${tag}-a`;
|
|
1657
2138
|
const sB = `qst-${tag}-b`;
|
|
@@ -1672,18 +2153,18 @@ var runStoreTck = (options) => {
|
|
|
1672
2153
|
make_meta({ stream: sUnasked })
|
|
1673
2154
|
);
|
|
1674
2155
|
const stats = await store.query_stats([sA, sB]);
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
2156
|
+
expect8(stats.size).toBe(2);
|
|
2157
|
+
expect8(stats.get(sA)?.head.name).toBe("Incremented");
|
|
2158
|
+
expect8((stats.get(sA)?.head.data).amount).toBe(2);
|
|
2159
|
+
expect8(stats.get(sB)?.head.name).toBe("Decremented");
|
|
2160
|
+
expect8((stats.get(sB)?.head.data).amount).toBe(5);
|
|
2161
|
+
expect8(stats.has(sUnasked)).toBe(false);
|
|
1681
2162
|
const empty = await store.query_stats([]);
|
|
1682
|
-
|
|
2163
|
+
expect8(empty.size).toBe(0);
|
|
1683
2164
|
const unknown = await store.query_stats([`qst-${tag}-missing`]);
|
|
1684
|
-
|
|
2165
|
+
expect8(unknown.size).toBe(0);
|
|
1685
2166
|
});
|
|
1686
|
-
|
|
2167
|
+
it7("tail returns the earliest event per stream", async () => {
|
|
1687
2168
|
const tag = uid();
|
|
1688
2169
|
const s = `qst-tail-${tag}`;
|
|
1689
2170
|
await store.commit(
|
|
@@ -1705,12 +2186,12 @@ var runStoreTck = (options) => {
|
|
|
1705
2186
|
tail: true
|
|
1706
2187
|
});
|
|
1707
2188
|
const r = stats.get(s);
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2189
|
+
expect8(r?.head.name).toBe("Incremented");
|
|
2190
|
+
expect8((r?.head.data).amount).toBe(3);
|
|
2191
|
+
expect8(r?.tail?.name).toBe("Incremented");
|
|
2192
|
+
expect8((r?.tail?.data).amount).toBe(1);
|
|
1712
2193
|
});
|
|
1713
|
-
|
|
2194
|
+
it7("count + names \u2014 full aggregates including framework markers", async () => {
|
|
1714
2195
|
const tag = uid();
|
|
1715
2196
|
const s = `qst-cn-${tag}`;
|
|
1716
2197
|
await store.commit(
|
|
@@ -1729,13 +2210,13 @@ var runStoreTck = (options) => {
|
|
|
1729
2210
|
names: true
|
|
1730
2211
|
});
|
|
1731
2212
|
const r = stats.get(s);
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2213
|
+
expect8(r?.count).toBe(4);
|
|
2214
|
+
expect8(r?.names?.[SNAP_EVENT2]).toBe(1);
|
|
2215
|
+
expect8(r?.names?.Incremented).toBe(2);
|
|
2216
|
+
expect8(r?.names?.Decremented).toBe(1);
|
|
2217
|
+
expect8(r?.names?.[SNAP_EVENT2]).toBe(1);
|
|
1737
2218
|
});
|
|
1738
|
-
|
|
2219
|
+
it7("exclude shifts head past filtered events; stream absent when all filtered", async () => {
|
|
1739
2220
|
const tag = uid();
|
|
1740
2221
|
const s = `qst-excl-${tag}`;
|
|
1741
2222
|
const sAllOut = `qst-allout-${tag}`;
|
|
@@ -1750,23 +2231,23 @@ var runStoreTck = (options) => {
|
|
|
1750
2231
|
make_meta({ stream: sAllOut })
|
|
1751
2232
|
);
|
|
1752
2233
|
const all = await store.query_stats([s]);
|
|
1753
|
-
|
|
1754
|
-
|
|
2234
|
+
expect8(all.get(s)?.head.name).toBe("Incremented");
|
|
2235
|
+
expect8((all.get(s)?.head.data).amount).toBe(3);
|
|
1755
2236
|
const excl = await store.query_stats([s], {
|
|
1756
2237
|
exclude: ["Incremented"]
|
|
1757
2238
|
});
|
|
1758
|
-
|
|
1759
|
-
|
|
2239
|
+
expect8(excl.get(s)?.head.name).toBe("Decremented");
|
|
2240
|
+
expect8((excl.get(s)?.head.data).amount).toBe(2);
|
|
1760
2241
|
const wipe = await store.query_stats([sAllOut], {
|
|
1761
2242
|
exclude: ["Incremented", "Decremented", "Reset"]
|
|
1762
2243
|
});
|
|
1763
|
-
|
|
2244
|
+
expect8(wipe.has(sAllOut)).toBe(false);
|
|
1764
2245
|
const no_tomb = await store.query_stats([s], {
|
|
1765
2246
|
exclude: [TOMBSTONE_EVENT]
|
|
1766
2247
|
});
|
|
1767
|
-
|
|
2248
|
+
expect8(no_tomb.get(s)?.head.name).toBe("Incremented");
|
|
1768
2249
|
});
|
|
1769
|
-
|
|
2250
|
+
it7("before \u2014 time travel narrows head/tail/count", async () => {
|
|
1770
2251
|
const tag = uid();
|
|
1771
2252
|
const s = `qst-tt-${tag}`;
|
|
1772
2253
|
const c1 = await store.commit(
|
|
@@ -1791,15 +2272,15 @@ var runStoreTck = (options) => {
|
|
|
1791
2272
|
before
|
|
1792
2273
|
});
|
|
1793
2274
|
const r = stats.get(s);
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
2275
|
+
expect8(r?.count).toBe(1);
|
|
2276
|
+
expect8(r?.head.id).toBe(c1[0].id);
|
|
2277
|
+
expect8(r?.tail?.id).toBe(c1[0].id);
|
|
1797
2278
|
const empty = await store.query_stats([s], {
|
|
1798
2279
|
before: 0
|
|
1799
2280
|
});
|
|
1800
|
-
|
|
2281
|
+
expect8(empty.has(s)).toBe(false);
|
|
1801
2282
|
});
|
|
1802
|
-
|
|
2283
|
+
it7("filter form \u2014 stream regex, stream_exact, empty {} match", async () => {
|
|
1803
2284
|
const tag = uid();
|
|
1804
2285
|
const sA = `qsf-${tag}-orders-1`;
|
|
1805
2286
|
const sB = `qsf-${tag}-orders-2`;
|
|
@@ -1822,18 +2303,18 @@ var runStoreTck = (options) => {
|
|
|
1822
2303
|
const orders = await store.query_stats({
|
|
1823
2304
|
stream: `^qsf-${tag}-orders-`
|
|
1824
2305
|
});
|
|
1825
|
-
|
|
2306
|
+
expect8([...orders.keys()].sort()).toEqual([sA, sB].sort());
|
|
1826
2307
|
const exact = await store.query_stats({
|
|
1827
2308
|
stream: sA,
|
|
1828
2309
|
stream_exact: true
|
|
1829
2310
|
});
|
|
1830
|
-
|
|
2311
|
+
expect8([...exact.keys()]).toEqual([sA]);
|
|
1831
2312
|
const all = await store.query_stats({
|
|
1832
2313
|
stream: `^qsf-${tag}-`
|
|
1833
2314
|
});
|
|
1834
|
-
|
|
2315
|
+
expect8([...all.keys()].sort()).toEqual([sA, sB, sOther].sort());
|
|
1835
2316
|
});
|
|
1836
|
-
|
|
2317
|
+
it7("compose with query_streams for subscription-level filters", async () => {
|
|
1837
2318
|
const tag = uid();
|
|
1838
2319
|
const a = `qsc-${tag}-a`;
|
|
1839
2320
|
const b = `qsc-${tag}-b`;
|
|
@@ -1850,7 +2331,7 @@ var runStoreTck = (options) => {
|
|
|
1850
2331
|
);
|
|
1851
2332
|
const leased = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1852
2333
|
const mine = leased.find((l) => l.stream === a);
|
|
1853
|
-
|
|
2334
|
+
expect8(mine).toBeDefined();
|
|
1854
2335
|
const others = leased.filter((l) => l.stream !== a);
|
|
1855
2336
|
await store.ack(others);
|
|
1856
2337
|
await store.block([{ ...mine, error: "boom" }]);
|
|
@@ -1859,12 +2340,12 @@ var runStoreTck = (options) => {
|
|
|
1859
2340
|
stream: `^qsc-${tag}-`,
|
|
1860
2341
|
blocked: true
|
|
1861
2342
|
});
|
|
1862
|
-
|
|
2343
|
+
expect8(blocked_names).toEqual([a]);
|
|
1863
2344
|
const stats = await store.query_stats(blocked_names);
|
|
1864
|
-
|
|
1865
|
-
|
|
2345
|
+
expect8(stats.get(a)?.head.name).toBe("Incremented");
|
|
2346
|
+
expect8(stats.has(b)).toBe(false);
|
|
1866
2347
|
});
|
|
1867
|
-
|
|
2348
|
+
it7("empty filter {} \u2014 matches every event-bearing stream", async () => {
|
|
1868
2349
|
const tag = uid();
|
|
1869
2350
|
const a = `qse-${tag}-a`;
|
|
1870
2351
|
const b = `qse-${tag}-b`;
|
|
@@ -1879,10 +2360,10 @@ var runStoreTck = (options) => {
|
|
|
1879
2360
|
make_meta({ stream: b })
|
|
1880
2361
|
);
|
|
1881
2362
|
const all = await store.query_stats({});
|
|
1882
|
-
|
|
1883
|
-
|
|
2363
|
+
expect8(all.has(a)).toBe(true);
|
|
2364
|
+
expect8(all.has(b)).toBe(true);
|
|
1884
2365
|
});
|
|
1885
|
-
|
|
2366
|
+
it7("stat-flag combinations \u2014 count-only, names-only, tail-only", async () => {
|
|
1886
2367
|
const tag = uid();
|
|
1887
2368
|
const s = `qsfl-${tag}`;
|
|
1888
2369
|
await store.commit(
|
|
@@ -1893,22 +2374,22 @@ var runStoreTck = (options) => {
|
|
|
1893
2374
|
const c = await store.query_stats([s], {
|
|
1894
2375
|
count: true
|
|
1895
2376
|
});
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2377
|
+
expect8(c.get(s)?.count).toBe(3);
|
|
2378
|
+
expect8(c.get(s)?.names).toBeUndefined();
|
|
2379
|
+
expect8(c.get(s)?.tail).toBeUndefined();
|
|
1899
2380
|
const n = await store.query_stats([s], {
|
|
1900
2381
|
names: true
|
|
1901
2382
|
});
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2383
|
+
expect8(n.get(s)?.names).toEqual({ Incremented: 2, Decremented: 1 });
|
|
2384
|
+
expect8(n.get(s)?.count).toBeUndefined();
|
|
2385
|
+
expect8(n.get(s)?.tail).toBeUndefined();
|
|
1905
2386
|
const t = await store.query_stats([s], { tail: true });
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
2387
|
+
expect8(t.get(s)?.tail?.name).toBe("Incremented");
|
|
2388
|
+
expect8((t.get(s)?.tail?.data).amount).toBe(1);
|
|
2389
|
+
expect8(t.get(s)?.count).toBeUndefined();
|
|
2390
|
+
expect8(t.get(s)?.names).toBeUndefined();
|
|
1910
2391
|
});
|
|
1911
|
-
|
|
2392
|
+
it7("paginates with limit + after (keyset), ordered by stream name", async () => {
|
|
1912
2393
|
const tag = uid();
|
|
1913
2394
|
const streams = [
|
|
1914
2395
|
`qsp-${tag}-a`,
|
|
@@ -1928,28 +2409,28 @@ var runStoreTck = (options) => {
|
|
|
1928
2409
|
{ limit: 2 }
|
|
1929
2410
|
);
|
|
1930
2411
|
const k1 = [...page1.keys()];
|
|
1931
|
-
|
|
2412
|
+
expect8(k1).toEqual([`qsp-${tag}-a`, `qsp-${tag}-b`]);
|
|
1932
2413
|
const page2 = await store.query_stats(
|
|
1933
2414
|
{ stream: `qsp-${tag}-.*` },
|
|
1934
2415
|
{ limit: 2, after: k1.at(-1) }
|
|
1935
2416
|
);
|
|
1936
2417
|
const k2 = [...page2.keys()];
|
|
1937
|
-
|
|
2418
|
+
expect8(k2).toEqual([`qsp-${tag}-c`, `qsp-${tag}-d`]);
|
|
1938
2419
|
const page3 = await store.query_stats(
|
|
1939
2420
|
{ stream: `qsp-${tag}-.*` },
|
|
1940
2421
|
{ limit: 2, after: k2.at(-1) }
|
|
1941
2422
|
);
|
|
1942
|
-
|
|
2423
|
+
expect8(page3.size).toBe(0);
|
|
1943
2424
|
const all = await store.query_stats({
|
|
1944
2425
|
stream: `qsp-${tag}-.*`
|
|
1945
2426
|
});
|
|
1946
|
-
|
|
2427
|
+
expect8([...all.keys()].sort()).toEqual([...streams].sort());
|
|
1947
2428
|
});
|
|
1948
2429
|
});
|
|
1949
|
-
|
|
2430
|
+
describe8.skipIf(!caps.source_matches)(
|
|
1950
2431
|
"query_streams source_matches (capability)",
|
|
1951
2432
|
() => {
|
|
1952
|
-
|
|
2433
|
+
it7("returns only subscriptions whose source pattern matches a name", async () => {
|
|
1953
2434
|
const tag = uid();
|
|
1954
2435
|
const subConcreteA = `sm-${tag}-sub-a`;
|
|
1955
2436
|
const subConcreteB = `sm-${tag}-sub-b`;
|
|
@@ -1970,7 +2451,7 @@ var runStoreTck = (options) => {
|
|
|
1970
2451
|
stream: `sm-${tag}-sub-.*`,
|
|
1971
2452
|
source_matches: [srcA]
|
|
1972
2453
|
});
|
|
1973
|
-
|
|
2454
|
+
expect8(matched.sort()).toEqual(
|
|
1974
2455
|
[subConcreteA, subRegex, subNoSource].sort()
|
|
1975
2456
|
);
|
|
1976
2457
|
const none = [];
|
|
@@ -1978,20 +2459,20 @@ var runStoreTck = (options) => {
|
|
|
1978
2459
|
stream: `sm-${tag}-sub-.*`,
|
|
1979
2460
|
source_matches: [`sm-${tag}-unrelated`]
|
|
1980
2461
|
});
|
|
1981
|
-
|
|
2462
|
+
expect8(none).toEqual([subNoSource]);
|
|
1982
2463
|
const both = [];
|
|
1983
2464
|
await store.query_streams((p) => both.push(p.stream), {
|
|
1984
2465
|
stream: `sm-${tag}-sub-.*`,
|
|
1985
2466
|
source_matches: [srcA, srcB]
|
|
1986
2467
|
});
|
|
1987
|
-
|
|
2468
|
+
expect8(both.sort()).toEqual(
|
|
1988
2469
|
[subConcreteA, subConcreteB, subRegex, subNoSource].sort()
|
|
1989
2470
|
);
|
|
1990
2471
|
});
|
|
1991
2472
|
}
|
|
1992
2473
|
);
|
|
1993
|
-
|
|
1994
|
-
|
|
2474
|
+
describe8("query_streams anchor contract", () => {
|
|
2475
|
+
it7("plain regex without anchors is a substring match", async () => {
|
|
1995
2476
|
const tag = uid();
|
|
1996
2477
|
const inner = `qsr-${tag}-inner`;
|
|
1997
2478
|
const longer = `qsr-${tag}-inner-extra`;
|
|
@@ -2005,9 +2486,9 @@ var runStoreTck = (options) => {
|
|
|
2005
2486
|
await store.query_streams((p) => seen.push(p.stream), {
|
|
2006
2487
|
stream: `qsr-${tag}-inner`
|
|
2007
2488
|
});
|
|
2008
|
-
|
|
2489
|
+
expect8(seen.sort()).toEqual([inner, longer].sort());
|
|
2009
2490
|
});
|
|
2010
|
-
|
|
2491
|
+
it7("caller-anchored `^name$` matches only the whole string", async () => {
|
|
2011
2492
|
const tag = uid();
|
|
2012
2493
|
const inner = `qsr-${tag}-anchor`;
|
|
2013
2494
|
const longer = `qsr-${tag}-anchor-extra`;
|
|
@@ -2016,9 +2497,9 @@ var runStoreTck = (options) => {
|
|
|
2016
2497
|
await store.query_streams((p) => seen.push(p.stream), {
|
|
2017
2498
|
stream: `^qsr-${tag}-anchor$`
|
|
2018
2499
|
});
|
|
2019
|
-
|
|
2500
|
+
expect8(seen).toEqual([inner]);
|
|
2020
2501
|
});
|
|
2021
|
-
|
|
2502
|
+
it7("caller-anchored `^prefix` matches by prefix", async () => {
|
|
2022
2503
|
const tag = uid();
|
|
2023
2504
|
const a = `qsr-${tag}-pfx-a`;
|
|
2024
2505
|
const b = `qsr-${tag}-pfx-b`;
|
|
@@ -2032,11 +2513,11 @@ var runStoreTck = (options) => {
|
|
|
2032
2513
|
await store.query_streams((p) => seen.push(p.stream), {
|
|
2033
2514
|
stream: `^qsr-${tag}-pfx-`
|
|
2034
2515
|
});
|
|
2035
|
-
|
|
2516
|
+
expect8(seen.sort()).toEqual([a, b].sort());
|
|
2036
2517
|
});
|
|
2037
2518
|
});
|
|
2038
|
-
|
|
2039
|
-
|
|
2519
|
+
describe8("prioritize anchor contract", () => {
|
|
2520
|
+
it7("caller-anchored `^name$` filter matches only the whole string", async () => {
|
|
2040
2521
|
const tag = uid();
|
|
2041
2522
|
const inner = `pr-${tag}-anchor`;
|
|
2042
2523
|
const longer = `pr-${tag}-anchor-extra`;
|
|
@@ -2048,17 +2529,17 @@ var runStoreTck = (options) => {
|
|
|
2048
2529
|
{ stream: `^pr-${tag}-anchor$` },
|
|
2049
2530
|
7
|
|
2050
2531
|
);
|
|
2051
|
-
|
|
2532
|
+
expect8(updated).toBe(1);
|
|
2052
2533
|
const seen = /* @__PURE__ */ new Map();
|
|
2053
2534
|
await store.query_streams((p) => seen.set(p.stream, p.priority), {
|
|
2054
2535
|
stream: `pr-${tag}-anchor`
|
|
2055
2536
|
});
|
|
2056
|
-
|
|
2057
|
-
|
|
2537
|
+
expect8(seen.get(inner)).toBe(7);
|
|
2538
|
+
expect8(seen.get(longer)).toBe(0);
|
|
2058
2539
|
});
|
|
2059
2540
|
});
|
|
2060
|
-
|
|
2061
|
-
|
|
2541
|
+
describe8("query_streams head", () => {
|
|
2542
|
+
it7("maxEventId tracks the highest committed id", async () => {
|
|
2062
2543
|
const s = `head-${uid()}`;
|
|
2063
2544
|
await store.subscribe([{ stream: s }]);
|
|
2064
2545
|
await store.commit(
|
|
@@ -2071,21 +2552,21 @@ var runStoreTck = (options) => {
|
|
|
2071
2552
|
(p) => positions.push(p.stream),
|
|
2072
2553
|
{ stream: s, stream_exact: true, limit: 1 }
|
|
2073
2554
|
);
|
|
2074
|
-
|
|
2075
|
-
|
|
2555
|
+
expect8(maxEventId).toBeGreaterThanOrEqual(0);
|
|
2556
|
+
expect8(positions).toEqual([s]);
|
|
2076
2557
|
});
|
|
2077
2558
|
});
|
|
2078
|
-
|
|
2079
|
-
|
|
2559
|
+
describe8("seed_stream helper coverage", () => {
|
|
2560
|
+
it7("commits N events with monotonically increasing ids", async () => {
|
|
2080
2561
|
const s = `seed-${uid()}`;
|
|
2081
2562
|
const committed = await seed_stream(store, s, 3);
|
|
2082
|
-
|
|
2563
|
+
expect8(committed).toHaveLength(3);
|
|
2083
2564
|
for (let i = 1; i < committed.length; i++) {
|
|
2084
|
-
|
|
2565
|
+
expect8(committed[i].id).toBeGreaterThan(committed[i - 1].id);
|
|
2085
2566
|
}
|
|
2086
2567
|
});
|
|
2087
2568
|
});
|
|
2088
|
-
|
|
2569
|
+
describe8.skipIf(!caps.restore)("restore (capability)", () => {
|
|
2089
2570
|
beforeEach(async () => {
|
|
2090
2571
|
await store.drop();
|
|
2091
2572
|
await store.seed();
|
|
@@ -2122,18 +2603,18 @@ var runStoreTck = (options) => {
|
|
|
2122
2603
|
await cache.dispose();
|
|
2123
2604
|
}
|
|
2124
2605
|
};
|
|
2125
|
-
|
|
2606
|
+
it7("returns kept=0 on an empty source", async () => {
|
|
2126
2607
|
const result = await restore(as_source([]));
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2608
|
+
expect8(result.kept).toBe(0);
|
|
2609
|
+
expect8(result.duration_ms).toBeGreaterThanOrEqual(0);
|
|
2610
|
+
expect8(result.dropped).toEqual({
|
|
2130
2611
|
closed_streams: 0,
|
|
2131
2612
|
snapshots: 0
|
|
2132
2613
|
});
|
|
2133
2614
|
const events2 = await collect(store, { limit: 10 });
|
|
2134
|
-
|
|
2615
|
+
expect8(events2).toHaveLength(0);
|
|
2135
2616
|
});
|
|
2136
|
-
|
|
2617
|
+
it7("rebuilds a single stream and preserves `created` verbatim", async () => {
|
|
2137
2618
|
const s = `restore-single-${uid()}`;
|
|
2138
2619
|
const t0 = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
|
|
2139
2620
|
const t1 = /* @__PURE__ */ new Date("2020-01-02T00:00:00.000Z");
|
|
@@ -2144,7 +2625,7 @@ var runStoreTck = (options) => {
|
|
|
2144
2625
|
event(3, s, 2, "Decremented", t2, { amount: 1 })
|
|
2145
2626
|
];
|
|
2146
2627
|
const result = await restore(as_source(events2));
|
|
2147
|
-
|
|
2628
|
+
expect8(result.kept).toBe(3);
|
|
2148
2629
|
const back = [];
|
|
2149
2630
|
await store.query(
|
|
2150
2631
|
(e) => {
|
|
@@ -2152,8 +2633,8 @@ var runStoreTck = (options) => {
|
|
|
2152
2633
|
},
|
|
2153
2634
|
{ stream: s, stream_exact: true }
|
|
2154
2635
|
);
|
|
2155
|
-
|
|
2156
|
-
|
|
2636
|
+
expect8(back).toHaveLength(3);
|
|
2637
|
+
expect8(
|
|
2157
2638
|
back.map((e) => ({
|
|
2158
2639
|
stream: e.stream,
|
|
2159
2640
|
version: e.version,
|
|
@@ -2185,7 +2666,7 @@ var runStoreTck = (options) => {
|
|
|
2185
2666
|
}
|
|
2186
2667
|
]);
|
|
2187
2668
|
});
|
|
2188
|
-
|
|
2669
|
+
it7("rebuilds multiple streams interleaved", async () => {
|
|
2189
2670
|
const a = `restore-multi-a-${uid()}`;
|
|
2190
2671
|
const b = `restore-multi-b-${uid()}`;
|
|
2191
2672
|
const t = /* @__PURE__ */ new Date("2020-06-01T00:00:00.000Z");
|
|
@@ -2196,7 +2677,7 @@ var runStoreTck = (options) => {
|
|
|
2196
2677
|
event(4, b, 1, "Incremented", t, { amount: 30 })
|
|
2197
2678
|
];
|
|
2198
2679
|
const result = await restore(as_source(events2));
|
|
2199
|
-
|
|
2680
|
+
expect8(result.kept).toBe(4);
|
|
2200
2681
|
const aBack = [];
|
|
2201
2682
|
const bBack = [];
|
|
2202
2683
|
await store.query(
|
|
@@ -2211,10 +2692,10 @@ var runStoreTck = (options) => {
|
|
|
2211
2692
|
},
|
|
2212
2693
|
{ stream: b, stream_exact: true }
|
|
2213
2694
|
);
|
|
2214
|
-
|
|
2215
|
-
|
|
2695
|
+
expect8(aBack.map((e) => e.version)).toEqual([0, 1]);
|
|
2696
|
+
expect8(bBack.map((e) => e.version)).toEqual([0, 1]);
|
|
2216
2697
|
});
|
|
2217
|
-
|
|
2698
|
+
it7("preserves Date `created` verbatim", async () => {
|
|
2218
2699
|
const s = `restore-isoc-${uid()}`;
|
|
2219
2700
|
const iso = "2021-07-15T12:34:56.789Z";
|
|
2220
2701
|
await restore(
|
|
@@ -2237,10 +2718,10 @@ var runStoreTck = (options) => {
|
|
|
2237
2718
|
},
|
|
2238
2719
|
{ stream: s, stream_exact: true }
|
|
2239
2720
|
);
|
|
2240
|
-
|
|
2241
|
-
|
|
2721
|
+
expect8(back).toHaveLength(1);
|
|
2722
|
+
expect8(back[0].created.toISOString()).toBe(iso);
|
|
2242
2723
|
});
|
|
2243
|
-
|
|
2724
|
+
it7("wipes pre-existing events before inserting", async () => {
|
|
2244
2725
|
const old = `restore-old-${uid()}`;
|
|
2245
2726
|
await store.commit(
|
|
2246
2727
|
old,
|
|
@@ -2256,14 +2737,14 @@ var runStoreTck = (options) => {
|
|
|
2256
2737
|
stream: old,
|
|
2257
2738
|
stream_exact: true
|
|
2258
2739
|
});
|
|
2259
|
-
|
|
2740
|
+
expect8(old_back).toHaveLength(0);
|
|
2260
2741
|
const fresh_back = await collect(store, {
|
|
2261
2742
|
stream: fresh,
|
|
2262
2743
|
stream_exact: true
|
|
2263
2744
|
});
|
|
2264
|
-
|
|
2745
|
+
expect8(fresh_back).toHaveLength(1);
|
|
2265
2746
|
});
|
|
2266
|
-
|
|
2747
|
+
it7("clears subscription/stream-position metadata", async () => {
|
|
2267
2748
|
const sub = `restore-sub-${uid()}`;
|
|
2268
2749
|
await store.subscribe([{ stream: sub, source: "anything" }]);
|
|
2269
2750
|
const collect_streams = async () => {
|
|
@@ -2274,12 +2755,12 @@ var runStoreTck = (options) => {
|
|
|
2274
2755
|
return out;
|
|
2275
2756
|
};
|
|
2276
2757
|
const before = await collect_streams();
|
|
2277
|
-
|
|
2758
|
+
expect8(before.includes(sub)).toBe(true);
|
|
2278
2759
|
await restore(as_source([]));
|
|
2279
2760
|
const after = await collect_streams();
|
|
2280
|
-
|
|
2761
|
+
expect8(after.includes(sub)).toBe(false);
|
|
2281
2762
|
});
|
|
2282
|
-
|
|
2763
|
+
it7("preserves snapshot events through restore", async () => {
|
|
2283
2764
|
const s = `restore-snap-${uid()}`;
|
|
2284
2765
|
const t = /* @__PURE__ */ new Date("2020-04-01T00:00:00.000Z");
|
|
2285
2766
|
await restore(
|
|
@@ -2288,7 +2769,7 @@ var runStoreTck = (options) => {
|
|
|
2288
2769
|
id: 1,
|
|
2289
2770
|
stream: s,
|
|
2290
2771
|
version: 0,
|
|
2291
|
-
name:
|
|
2772
|
+
name: SNAP_EVENT2,
|
|
2292
2773
|
data: { count: 42 },
|
|
2293
2774
|
created: t,
|
|
2294
2775
|
meta: { correlation: "snap", causation: {} }
|
|
@@ -2300,10 +2781,10 @@ var runStoreTck = (options) => {
|
|
|
2300
2781
|
stream_exact: true,
|
|
2301
2782
|
with_snaps: true
|
|
2302
2783
|
});
|
|
2303
|
-
|
|
2304
|
-
|
|
2784
|
+
expect8(back).toHaveLength(1);
|
|
2785
|
+
expect8(back[0].name).toBe(SNAP_EVENT2);
|
|
2305
2786
|
});
|
|
2306
|
-
|
|
2787
|
+
it7("rewrites causation refs through the old\u2192new id map", async () => {
|
|
2307
2788
|
const s = `restore-caus-${uid()}`;
|
|
2308
2789
|
const t = /* @__PURE__ */ new Date("2020-08-01T00:00:00.000Z");
|
|
2309
2790
|
const events2 = [
|
|
@@ -2353,12 +2834,12 @@ var runStoreTck = (options) => {
|
|
|
2353
2834
|
},
|
|
2354
2835
|
{ stream: s, stream_exact: true }
|
|
2355
2836
|
);
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2837
|
+
expect8(back).toHaveLength(3);
|
|
2838
|
+
expect8(back[0].meta.causation.event).toBeUndefined();
|
|
2839
|
+
expect8(back[1].meta.causation.event?.id).toBe(back[0].id);
|
|
2840
|
+
expect8(back[2].meta.causation.event?.id).toBe(back[1].id);
|
|
2360
2841
|
});
|
|
2361
|
-
|
|
2842
|
+
it7("leaves causation refs unmapped when the target isn't in the source", async () => {
|
|
2362
2843
|
const s = `restore-orphan-${uid()}`;
|
|
2363
2844
|
const t = /* @__PURE__ */ new Date("2020-09-01T00:00:00.000Z");
|
|
2364
2845
|
await restore(
|
|
@@ -2386,9 +2867,9 @@ var runStoreTck = (options) => {
|
|
|
2386
2867
|
},
|
|
2387
2868
|
{ stream: s, stream_exact: true }
|
|
2388
2869
|
);
|
|
2389
|
-
|
|
2870
|
+
expect8(back[0].meta.causation.event?.id).toBe(999);
|
|
2390
2871
|
});
|
|
2391
|
-
|
|
2872
|
+
it7("rolls back atomically when the source throws mid-iteration", async () => {
|
|
2392
2873
|
const original = `restore-pre-${uid()}`;
|
|
2393
2874
|
const committed = await store.commit(
|
|
2394
2875
|
original,
|
|
@@ -2414,7 +2895,7 @@ var runStoreTck = (options) => {
|
|
|
2414
2895
|
async dispose() {
|
|
2415
2896
|
}
|
|
2416
2897
|
};
|
|
2417
|
-
await
|
|
2898
|
+
await expect8(restore(explosive)).rejects.toThrow("boom");
|
|
2418
2899
|
const back = [];
|
|
2419
2900
|
await store.query(
|
|
2420
2901
|
(e) => {
|
|
@@ -2422,10 +2903,10 @@ var runStoreTck = (options) => {
|
|
|
2422
2903
|
},
|
|
2423
2904
|
{ stream: original, stream_exact: true }
|
|
2424
2905
|
);
|
|
2425
|
-
|
|
2426
|
-
|
|
2906
|
+
expect8(back).toHaveLength(3);
|
|
2907
|
+
expect8(back.map((e) => e.id)).toEqual(committed.map((c) => c.id));
|
|
2427
2908
|
});
|
|
2428
|
-
|
|
2909
|
+
it7("drop_snapshots: skips SNAP_EVENT rows and counts them", async () => {
|
|
2429
2910
|
const s = `restore-drop-snap-${uid()}`;
|
|
2430
2911
|
const t = /* @__PURE__ */ new Date("2020-10-01T00:00:00.000Z");
|
|
2431
2912
|
const result = await restore(
|
|
@@ -2435,7 +2916,7 @@ var runStoreTck = (options) => {
|
|
|
2435
2916
|
id: 2,
|
|
2436
2917
|
stream: s,
|
|
2437
2918
|
version: 1,
|
|
2438
|
-
name:
|
|
2919
|
+
name: SNAP_EVENT2,
|
|
2439
2920
|
data: { count: 1 },
|
|
2440
2921
|
created: t,
|
|
2441
2922
|
meta: { correlation: "snap", causation: {} }
|
|
@@ -2444,19 +2925,19 @@ var runStoreTck = (options) => {
|
|
|
2444
2925
|
]),
|
|
2445
2926
|
{ drop_snapshots: true }
|
|
2446
2927
|
);
|
|
2447
|
-
|
|
2448
|
-
|
|
2928
|
+
expect8(result.kept).toBe(2);
|
|
2929
|
+
expect8(result.dropped.snapshots).toBe(1);
|
|
2449
2930
|
const back = await collect(store, {
|
|
2450
2931
|
stream: s,
|
|
2451
2932
|
stream_exact: true,
|
|
2452
2933
|
with_snaps: true
|
|
2453
2934
|
});
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
back.every((e) => e.name !==
|
|
2935
|
+
expect8(back).toHaveLength(2);
|
|
2936
|
+
expect8(
|
|
2937
|
+
back.every((e) => e.name !== SNAP_EVENT2)
|
|
2457
2938
|
).toBe(true);
|
|
2458
2939
|
});
|
|
2459
|
-
|
|
2940
|
+
it7("on_progress fires once per event (caller throttles)", async () => {
|
|
2460
2941
|
const calls = [];
|
|
2461
2942
|
const s = `restore-progress-${uid()}`;
|
|
2462
2943
|
const t = /* @__PURE__ */ new Date("2021-02-01T00:00:00.000Z");
|
|
@@ -2467,11 +2948,11 @@ var runStoreTck = (options) => {
|
|
|
2467
2948
|
]),
|
|
2468
2949
|
{ on_progress: (p) => calls.push(p.processed) }
|
|
2469
2950
|
);
|
|
2470
|
-
|
|
2951
|
+
expect8(calls).toEqual([1, 2]);
|
|
2471
2952
|
});
|
|
2472
2953
|
});
|
|
2473
|
-
|
|
2474
|
-
|
|
2954
|
+
describe8.skipIf(!caps.pii_isolation)("pii_isolation (capability)", () => {
|
|
2955
|
+
it7("commits and loads pii alongside data", async () => {
|
|
2475
2956
|
const s = `pii-roundtrip-${uid()}`;
|
|
2476
2957
|
const committed = await store.commit(
|
|
2477
2958
|
s,
|
|
@@ -2484,8 +2965,8 @@ var runStoreTck = (options) => {
|
|
|
2484
2965
|
],
|
|
2485
2966
|
make_meta({ stream: s })
|
|
2486
2967
|
);
|
|
2487
|
-
|
|
2488
|
-
|
|
2968
|
+
expect8(committed).toHaveLength(1);
|
|
2969
|
+
expect8(committed[0].pii).toEqual({
|
|
2489
2970
|
email: "u@example.com",
|
|
2490
2971
|
name: "Ursula"
|
|
2491
2972
|
});
|
|
@@ -2496,11 +2977,11 @@ var runStoreTck = (options) => {
|
|
|
2496
2977
|
},
|
|
2497
2978
|
{ stream: s, stream_exact: true }
|
|
2498
2979
|
);
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2980
|
+
expect8(seen).toHaveLength(1);
|
|
2981
|
+
expect8(seen[0].pii).toEqual({ email: "u@example.com", name: "Ursula" });
|
|
2982
|
+
expect8(seen[0].data).toEqual({ amount: 1 });
|
|
2502
2983
|
});
|
|
2503
|
-
|
|
2984
|
+
it7("passes through events without pii (pii is null or undefined on load)", async () => {
|
|
2504
2985
|
const s = `pii-none-${uid()}`;
|
|
2505
2986
|
await store.commit(
|
|
2506
2987
|
s,
|
|
@@ -2514,10 +2995,10 @@ var runStoreTck = (options) => {
|
|
|
2514
2995
|
},
|
|
2515
2996
|
{ stream: s, stream_exact: true }
|
|
2516
2997
|
);
|
|
2517
|
-
|
|
2518
|
-
|
|
2998
|
+
expect8(seen).toHaveLength(1);
|
|
2999
|
+
expect8(seen[0].pii == null).toBe(true);
|
|
2519
3000
|
});
|
|
2520
|
-
|
|
3001
|
+
it7("wipes pii for every event on the stream via forget_pii", async () => {
|
|
2521
3002
|
const s = `pii-forget-${uid()}`;
|
|
2522
3003
|
await store.commit(
|
|
2523
3004
|
s,
|
|
@@ -2536,9 +3017,9 @@ var runStoreTck = (options) => {
|
|
|
2536
3017
|
make_meta({ stream: s })
|
|
2537
3018
|
);
|
|
2538
3019
|
const forget = store.forget_pii;
|
|
2539
|
-
|
|
3020
|
+
expect8(forget).toBeDefined();
|
|
2540
3021
|
const wiped = await forget.call(store, s);
|
|
2541
|
-
|
|
3022
|
+
expect8(wiped).toBe(2);
|
|
2542
3023
|
const seen = [];
|
|
2543
3024
|
await store.query(
|
|
2544
3025
|
(e) => {
|
|
@@ -2546,13 +3027,13 @@ var runStoreTck = (options) => {
|
|
|
2546
3027
|
},
|
|
2547
3028
|
{ stream: s, stream_exact: true }
|
|
2548
3029
|
);
|
|
2549
|
-
|
|
3030
|
+
expect8(seen).toHaveLength(2);
|
|
2550
3031
|
for (const e of seen) {
|
|
2551
|
-
|
|
2552
|
-
|
|
3032
|
+
expect8(e.pii == null).toBe(true);
|
|
3033
|
+
expect8(e.data).toBeDefined();
|
|
2553
3034
|
}
|
|
2554
3035
|
});
|
|
2555
|
-
|
|
3036
|
+
it7("is idempotent \u2014 second forget_pii returns 0, no error", async () => {
|
|
2556
3037
|
const s = `pii-forget-idem-${uid()}`;
|
|
2557
3038
|
await store.commit(
|
|
2558
3039
|
s,
|
|
@@ -2567,11 +3048,11 @@ var runStoreTck = (options) => {
|
|
|
2567
3048
|
);
|
|
2568
3049
|
const forget = store.forget_pii;
|
|
2569
3050
|
const first = await forget.call(store, s);
|
|
2570
|
-
|
|
3051
|
+
expect8(first).toBe(1);
|
|
2571
3052
|
const second = await forget.call(store, s);
|
|
2572
|
-
|
|
3053
|
+
expect8(second).toBe(0);
|
|
2573
3054
|
});
|
|
2574
|
-
|
|
3055
|
+
it7("only wipes the targeted stream \u2014 siblings untouched", async () => {
|
|
2575
3056
|
const sA = `pii-iso-a-${uid()}`;
|
|
2576
3057
|
const sB = `pii-iso-b-${uid()}`;
|
|
2577
3058
|
await store.commit(
|
|
@@ -2604,7 +3085,7 @@ var runStoreTck = (options) => {
|
|
|
2604
3085
|
},
|
|
2605
3086
|
{ stream: sA, stream_exact: true }
|
|
2606
3087
|
);
|
|
2607
|
-
|
|
3088
|
+
expect8(a[0].pii == null).toBe(true);
|
|
2608
3089
|
const b = [];
|
|
2609
3090
|
await store.query(
|
|
2610
3091
|
(e) => {
|
|
@@ -2612,9 +3093,9 @@ var runStoreTck = (options) => {
|
|
|
2612
3093
|
},
|
|
2613
3094
|
{ stream: sB, stream_exact: true }
|
|
2614
3095
|
);
|
|
2615
|
-
|
|
3096
|
+
expect8(b[0].pii).toEqual({ email: "bob@example.com" });
|
|
2616
3097
|
});
|
|
2617
|
-
|
|
3098
|
+
it7("forget_pii on a stream with no pii events returns 0", async () => {
|
|
2618
3099
|
const s = `pii-forget-empty-${uid()}`;
|
|
2619
3100
|
await store.commit(
|
|
2620
3101
|
s,
|
|
@@ -2622,14 +3103,14 @@ var runStoreTck = (options) => {
|
|
|
2622
3103
|
make_meta({ stream: s })
|
|
2623
3104
|
);
|
|
2624
3105
|
const wiped = await store.forget_pii.call(store, s);
|
|
2625
|
-
|
|
3106
|
+
expect8(wiped).toBe(0);
|
|
2626
3107
|
});
|
|
2627
3108
|
});
|
|
2628
3109
|
if (caps.notify) {
|
|
2629
|
-
|
|
2630
|
-
|
|
3110
|
+
describe8("notify (capability)", () => {
|
|
3111
|
+
it7("delivers a notification when a different instance commits", async () => {
|
|
2631
3112
|
const notify = store.notify;
|
|
2632
|
-
|
|
3113
|
+
expect8(notify).toBeDefined();
|
|
2633
3114
|
const received = [];
|
|
2634
3115
|
let resolve_arrived;
|
|
2635
3116
|
const arrived = new Promise((res) => {
|
|
@@ -2648,9 +3129,9 @@ var runStoreTck = (options) => {
|
|
|
2648
3129
|
make_meta({ stream })
|
|
2649
3130
|
);
|
|
2650
3131
|
await arrived;
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
3132
|
+
expect8(received.length).toBeGreaterThanOrEqual(1);
|
|
3133
|
+
expect8(received[0].stream).toBe(stream);
|
|
3134
|
+
expect8(received[0].events.length).toBeGreaterThanOrEqual(1);
|
|
2654
3135
|
} finally {
|
|
2655
3136
|
await writer.dispose();
|
|
2656
3137
|
await Promise.resolve(disposer());
|
|
@@ -2671,9 +3152,12 @@ export {
|
|
|
2671
3152
|
dec,
|
|
2672
3153
|
inc,
|
|
2673
3154
|
reset,
|
|
3155
|
+
runCacheDifferentialTck,
|
|
2674
3156
|
runCacheTck,
|
|
3157
|
+
runLoggerDifferentialTck,
|
|
2675
3158
|
runLoggerTck,
|
|
2676
3159
|
runStabilityTck,
|
|
3160
|
+
runStoreDifferentialTck,
|
|
2677
3161
|
runStorePropertyTck,
|
|
2678
3162
|
runStoreTck,
|
|
2679
3163
|
uid
|