@rotorsoft/act-tck 1.17.0 → 1.17.1
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 +903 -514
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +876 -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,17 @@ 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);
|
|
1034
1417
|
});
|
|
1035
1418
|
});
|
|
1036
|
-
|
|
1037
|
-
|
|
1419
|
+
describe8("reset", () => {
|
|
1420
|
+
it7("rewinds a stream watermark to -1", async () => {
|
|
1038
1421
|
const s = `reset-${uid()}`;
|
|
1039
1422
|
await store.subscribe([{ stream: s }]);
|
|
1040
1423
|
await store.commit(
|
|
@@ -1044,15 +1427,15 @@ var runStoreTck = (options) => {
|
|
|
1044
1427
|
);
|
|
1045
1428
|
const leased = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1046
1429
|
const mine = leased.find((l) => l.stream === s);
|
|
1047
|
-
|
|
1430
|
+
expect8(mine).toBeDefined();
|
|
1048
1431
|
await store.ack([{ ...mine, at: 99 }]);
|
|
1049
|
-
|
|
1432
|
+
expect8(await store.reset([s])).toBe(1);
|
|
1050
1433
|
const after = await store.claim(100, 0, `w2-${uid()}`, 1e5);
|
|
1051
1434
|
const back = after.find((l) => l.stream === s);
|
|
1052
|
-
|
|
1053
|
-
|
|
1435
|
+
expect8(back).toBeDefined();
|
|
1436
|
+
expect8(back.at).toBe(-1);
|
|
1054
1437
|
});
|
|
1055
|
-
|
|
1438
|
+
it7("clears blocked status when resetting", async () => {
|
|
1056
1439
|
const s = `reset-blk-${uid()}`;
|
|
1057
1440
|
await store.subscribe([{ stream: s }]);
|
|
1058
1441
|
await store.commit(
|
|
@@ -1065,17 +1448,17 @@ var runStoreTck = (options) => {
|
|
|
1065
1448
|
const others = leased.filter((l) => l.stream !== s);
|
|
1066
1449
|
await store.ack(others);
|
|
1067
1450
|
await store.block([{ ...mine, error: "boom" }]);
|
|
1068
|
-
|
|
1451
|
+
expect8(await store.reset([s])).toBe(1);
|
|
1069
1452
|
const after = await store.claim(100, 0, `w2-${uid()}`, 1e5);
|
|
1070
|
-
|
|
1453
|
+
expect8(after.find((l) => l.stream === s)).toBeDefined();
|
|
1071
1454
|
});
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1455
|
+
it7("returns 0 for unknown streams and empty input", async () => {
|
|
1456
|
+
expect8(await store.reset([`missing-${uid()}`])).toBe(0);
|
|
1457
|
+
expect8(await store.reset([])).toBe(0);
|
|
1075
1458
|
});
|
|
1076
1459
|
});
|
|
1077
|
-
|
|
1078
|
-
|
|
1460
|
+
describe8("unblock", () => {
|
|
1461
|
+
it7("clears blocked flag and preserves the watermark", async () => {
|
|
1079
1462
|
const s = `unblock-${uid()}`;
|
|
1080
1463
|
await store.subscribe([{ stream: s }]);
|
|
1081
1464
|
await store.commit(
|
|
@@ -1093,7 +1476,7 @@ var runStoreTck = (options) => {
|
|
|
1093
1476
|
await store.ack([{ ...m1, at: m1.at }]);
|
|
1094
1477
|
const before_block = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1095
1478
|
const m2 = before_block.find((l) => l.stream === s);
|
|
1096
|
-
|
|
1479
|
+
expect8(m2).toBeDefined();
|
|
1097
1480
|
const watermark_before = m2.at;
|
|
1098
1481
|
await store.block([{ ...m2, error: "permanent" }]);
|
|
1099
1482
|
let blocked_flag;
|
|
@@ -1103,15 +1486,15 @@ var runStoreTck = (options) => {
|
|
|
1103
1486
|
},
|
|
1104
1487
|
{ stream: s, stream_exact: true, limit: 1 }
|
|
1105
1488
|
);
|
|
1106
|
-
|
|
1107
|
-
|
|
1489
|
+
expect8(blocked_flag).toBe(true);
|
|
1490
|
+
expect8(await store.unblock([s])).toBe(1);
|
|
1108
1491
|
const after = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1109
1492
|
const back = after.find((l) => l.stream === s);
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1493
|
+
expect8(back).toBeDefined();
|
|
1494
|
+
expect8(back.at).toBe(watermark_before);
|
|
1495
|
+
expect8(back.retry).toBe(0);
|
|
1113
1496
|
});
|
|
1114
|
-
|
|
1497
|
+
it7("returns 0 when the stream is not blocked", async () => {
|
|
1115
1498
|
const s = `unblock-noop-${uid()}`;
|
|
1116
1499
|
await store.subscribe([{ stream: s }]);
|
|
1117
1500
|
await store.commit(
|
|
@@ -1119,13 +1502,13 @@ var runStoreTck = (options) => {
|
|
|
1119
1502
|
[inc(1)],
|
|
1120
1503
|
make_meta({ stream: s })
|
|
1121
1504
|
);
|
|
1122
|
-
|
|
1505
|
+
expect8(await store.unblock([s])).toBe(0);
|
|
1123
1506
|
});
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1507
|
+
it7("returns 0 for unknown streams and empty input", async () => {
|
|
1508
|
+
expect8(await store.unblock([`missing-${uid()}`])).toBe(0);
|
|
1509
|
+
expect8(await store.unblock([])).toBe(0);
|
|
1127
1510
|
});
|
|
1128
|
-
|
|
1511
|
+
it7("only counts streams that were actually blocked", async () => {
|
|
1129
1512
|
const s1 = `unblock-mix-a-${uid()}`;
|
|
1130
1513
|
const s2 = `unblock-mix-b-${uid()}`;
|
|
1131
1514
|
await store.subscribe([{ stream: s1 }, { stream: s2 }]);
|
|
@@ -1144,9 +1527,9 @@ var runStoreTck = (options) => {
|
|
|
1144
1527
|
const others = leased.filter((l) => l.stream !== s1);
|
|
1145
1528
|
await store.ack(others);
|
|
1146
1529
|
await store.block([{ ...m1, error: "boom" }]);
|
|
1147
|
-
|
|
1530
|
+
expect8(await store.unblock([s1, s2])).toBe(1);
|
|
1148
1531
|
});
|
|
1149
|
-
|
|
1532
|
+
it7("filter form: unblocks by stream pattern", async () => {
|
|
1150
1533
|
const tag = uid();
|
|
1151
1534
|
const s1 = `unblock-filter-${tag}-a`;
|
|
1152
1535
|
const s2 = `unblock-filter-${tag}-b`;
|
|
@@ -1178,13 +1561,13 @@ var runStoreTck = (options) => {
|
|
|
1178
1561
|
const count = await store.unblock({
|
|
1179
1562
|
stream: `^unblock-filter-${tag}-`
|
|
1180
1563
|
});
|
|
1181
|
-
|
|
1564
|
+
expect8(count).toBe(2);
|
|
1182
1565
|
const after = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1566
|
+
expect8(after.find((l) => l.stream === s3)).toBeUndefined();
|
|
1567
|
+
expect8(after.find((l) => l.stream === s1)).toBeDefined();
|
|
1568
|
+
expect8(after.find((l) => l.stream === s2)).toBeDefined();
|
|
1186
1569
|
});
|
|
1187
|
-
|
|
1570
|
+
it7("filter form: empty filter unblocks every blocked stream", async () => {
|
|
1188
1571
|
const tag = uid();
|
|
1189
1572
|
const s1 = `unblock-empty-${tag}-a`;
|
|
1190
1573
|
const s2 = `unblock-empty-${tag}-b`;
|
|
@@ -1208,9 +1591,9 @@ var runStoreTck = (options) => {
|
|
|
1208
1591
|
const count = await store.unblock({
|
|
1209
1592
|
stream: `^unblock-empty-${tag}-`
|
|
1210
1593
|
});
|
|
1211
|
-
|
|
1594
|
+
expect8(count).toBe(2);
|
|
1212
1595
|
});
|
|
1213
|
-
|
|
1596
|
+
it7("filter form: explicit blocked:false matches nothing", async () => {
|
|
1214
1597
|
const tag = uid();
|
|
1215
1598
|
const s = `unblock-blocked-false-${tag}`;
|
|
1216
1599
|
await store.subscribe([{ stream: s }]);
|
|
@@ -1219,7 +1602,7 @@ var runStoreTck = (options) => {
|
|
|
1219
1602
|
[inc(1)],
|
|
1220
1603
|
make_meta({ stream: s })
|
|
1221
1604
|
);
|
|
1222
|
-
|
|
1605
|
+
expect8(
|
|
1223
1606
|
await store.unblock({
|
|
1224
1607
|
stream: `^unblock-blocked-false-${tag}`,
|
|
1225
1608
|
blocked: false
|
|
@@ -1227,8 +1610,8 @@ var runStoreTck = (options) => {
|
|
|
1227
1610
|
).toBe(0);
|
|
1228
1611
|
});
|
|
1229
1612
|
});
|
|
1230
|
-
|
|
1231
|
-
|
|
1613
|
+
describe8("reset filter form", () => {
|
|
1614
|
+
it7("resets streams matching a stream pattern", async () => {
|
|
1232
1615
|
const tag = uid();
|
|
1233
1616
|
const s1 = `reset-filter-${tag}-a`;
|
|
1234
1617
|
const s2 = `reset-filter-${tag}-b`;
|
|
@@ -1259,7 +1642,7 @@ var runStoreTck = (options) => {
|
|
|
1259
1642
|
);
|
|
1260
1643
|
await store.ack(mine.map((l) => ({ ...l, at: l.at + 100 })));
|
|
1261
1644
|
const count = await store.reset({ stream: `^reset-filter-${tag}-` });
|
|
1262
|
-
|
|
1645
|
+
expect8(count).toBe(2);
|
|
1263
1646
|
const position_for = async (name) => {
|
|
1264
1647
|
let at = null;
|
|
1265
1648
|
await store.query_streams(
|
|
@@ -1270,11 +1653,11 @@ var runStoreTck = (options) => {
|
|
|
1270
1653
|
);
|
|
1271
1654
|
return at;
|
|
1272
1655
|
};
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1656
|
+
expect8(await position_for(s1)).toBe(-1);
|
|
1657
|
+
expect8(await position_for(s2)).toBe(-1);
|
|
1658
|
+
expect8(await position_for(other)).toBeGreaterThan(-1);
|
|
1276
1659
|
});
|
|
1277
|
-
|
|
1660
|
+
it7("filter form: resets only blocked streams when blocked:true", async () => {
|
|
1278
1661
|
const tag = uid();
|
|
1279
1662
|
const s1 = `reset-blocked-${tag}-blocked`;
|
|
1280
1663
|
const s2 = `reset-blocked-${tag}-fine`;
|
|
@@ -1297,11 +1680,11 @@ var runStoreTck = (options) => {
|
|
|
1297
1680
|
stream: `^reset-blocked-${tag}-`,
|
|
1298
1681
|
blocked: true
|
|
1299
1682
|
});
|
|
1300
|
-
|
|
1683
|
+
expect8(count).toBe(1);
|
|
1301
1684
|
});
|
|
1302
1685
|
});
|
|
1303
|
-
|
|
1304
|
-
|
|
1686
|
+
describe8("prioritize", () => {
|
|
1687
|
+
it7("sets priority directly, overriding subscribe's max() rule", async () => {
|
|
1305
1688
|
const tag = uid();
|
|
1306
1689
|
const s1 = `pri-${tag}-a`;
|
|
1307
1690
|
const s2 = `pri-${tag}-b`;
|
|
@@ -1313,7 +1696,7 @@ var runStoreTck = (options) => {
|
|
|
1313
1696
|
{ stream: s1, stream_exact: true },
|
|
1314
1697
|
3
|
|
1315
1698
|
);
|
|
1316
|
-
|
|
1699
|
+
expect8(updated).toBe(1);
|
|
1317
1700
|
const got1 = {};
|
|
1318
1701
|
const got2 = {};
|
|
1319
1702
|
await store.query_streams(
|
|
@@ -1323,12 +1706,12 @@ var runStoreTck = (options) => {
|
|
|
1323
1706
|
},
|
|
1324
1707
|
{ stream: `pri-${tag}-.*`, limit: 100 }
|
|
1325
1708
|
);
|
|
1326
|
-
|
|
1327
|
-
|
|
1709
|
+
expect8(got1.priority).toBe(3);
|
|
1710
|
+
expect8(got2.priority).toBe(5);
|
|
1328
1711
|
});
|
|
1329
1712
|
});
|
|
1330
|
-
|
|
1331
|
-
|
|
1713
|
+
describe8("lanes", () => {
|
|
1714
|
+
it7("subscribe defaults lane to 'default' when omitted", async () => {
|
|
1332
1715
|
const s = `lane-default-${uid()}`;
|
|
1333
1716
|
await store.subscribe([{ stream: s }]);
|
|
1334
1717
|
const seen = [];
|
|
@@ -1336,9 +1719,9 @@ var runStoreTck = (options) => {
|
|
|
1336
1719
|
stream: s,
|
|
1337
1720
|
stream_exact: true
|
|
1338
1721
|
});
|
|
1339
|
-
|
|
1722
|
+
expect8(seen).toEqual(["default"]);
|
|
1340
1723
|
});
|
|
1341
|
-
|
|
1724
|
+
it7("subscribe records the lane passed in", async () => {
|
|
1342
1725
|
const s = `lane-set-${uid()}`;
|
|
1343
1726
|
await store.subscribe([{ stream: s, lane: "slow" }]);
|
|
1344
1727
|
const seen = [];
|
|
@@ -1346,9 +1729,9 @@ var runStoreTck = (options) => {
|
|
|
1346
1729
|
stream: s,
|
|
1347
1730
|
stream_exact: true
|
|
1348
1731
|
});
|
|
1349
|
-
|
|
1732
|
+
expect8(seen).toEqual(["slow"]);
|
|
1350
1733
|
});
|
|
1351
|
-
|
|
1734
|
+
it7("subscribe re-lanes existing streams on subsequent calls", async () => {
|
|
1352
1735
|
const s = `lane-upsert-${uid()}`;
|
|
1353
1736
|
await store.subscribe([{ stream: s, lane: "slow" }]);
|
|
1354
1737
|
await store.subscribe([{ stream: s, lane: "fast" }]);
|
|
@@ -1357,9 +1740,9 @@ var runStoreTck = (options) => {
|
|
|
1357
1740
|
stream: s,
|
|
1358
1741
|
stream_exact: true
|
|
1359
1742
|
});
|
|
1360
|
-
|
|
1743
|
+
expect8(seen).toEqual(["fast"]);
|
|
1361
1744
|
});
|
|
1362
|
-
|
|
1745
|
+
it7("claim() filters by lane when supplied and returns lane on the Lease", async () => {
|
|
1363
1746
|
const tag = uid();
|
|
1364
1747
|
const src1 = `lane-claim-src1-${tag}`;
|
|
1365
1748
|
const src2 = `lane-claim-src2-${tag}`;
|
|
@@ -1383,19 +1766,19 @@ var runStoreTck = (options) => {
|
|
|
1383
1766
|
const slow_mine = slow.filter(
|
|
1384
1767
|
(l) => l.stream === sub_default || l.stream === sub_slow
|
|
1385
1768
|
);
|
|
1386
|
-
|
|
1387
|
-
|
|
1769
|
+
expect8(slow_mine.map((l) => l.stream)).toEqual([sub_slow]);
|
|
1770
|
+
expect8(slow_mine[0]?.lane).toBe("slow");
|
|
1388
1771
|
await store.ack(slow_mine.map((l) => ({ ...l, at: l.at + 1 })));
|
|
1389
1772
|
const all = await store.claim(50, 0, `w-all-${tag}`, 1e3);
|
|
1390
1773
|
const all_mine = all.filter((l) => l.stream === sub_default || l.stream === sub_slow).map((l) => ({ stream: l.stream, lane: l.lane }));
|
|
1391
|
-
|
|
1392
|
-
|
|
1774
|
+
expect8(all_mine).toEqual(
|
|
1775
|
+
expect8.arrayContaining([
|
|
1393
1776
|
{ stream: sub_default, lane: "default" },
|
|
1394
1777
|
{ stream: sub_slow, lane: "slow" }
|
|
1395
1778
|
])
|
|
1396
1779
|
);
|
|
1397
1780
|
});
|
|
1398
|
-
|
|
1781
|
+
it7("query_streams filters by lane", async () => {
|
|
1399
1782
|
const tag = uid();
|
|
1400
1783
|
const a = `lane-q-a-${tag}`;
|
|
1401
1784
|
const b = `lane-q-b-${tag}`;
|
|
@@ -1411,9 +1794,9 @@ var runStoreTck = (options) => {
|
|
|
1411
1794
|
stream: `lane-q-.*-${tag}`,
|
|
1412
1795
|
limit: 100
|
|
1413
1796
|
});
|
|
1414
|
-
|
|
1797
|
+
expect8(seen.sort()).toEqual([a, c]);
|
|
1415
1798
|
});
|
|
1416
|
-
|
|
1799
|
+
it7("prioritize filters by lane", async () => {
|
|
1417
1800
|
const tag = uid();
|
|
1418
1801
|
const a = `lane-pri-a-${tag}`;
|
|
1419
1802
|
const b = `lane-pri-b-${tag}`;
|
|
@@ -1422,16 +1805,16 @@ var runStoreTck = (options) => {
|
|
|
1422
1805
|
{ stream: b, lane: `pfast-${tag}` }
|
|
1423
1806
|
]);
|
|
1424
1807
|
const updated = await store.prioritize({ lane: `pslow-${tag}` }, 7);
|
|
1425
|
-
|
|
1808
|
+
expect8(updated).toBe(1);
|
|
1426
1809
|
const seen = /* @__PURE__ */ new Map();
|
|
1427
1810
|
await store.query_streams((p) => seen.set(p.stream, p.priority), {
|
|
1428
1811
|
stream: `lane-pri-.*-${tag}`,
|
|
1429
1812
|
limit: 100
|
|
1430
1813
|
});
|
|
1431
|
-
|
|
1432
|
-
|
|
1814
|
+
expect8(seen.get(a)).toBe(7);
|
|
1815
|
+
expect8(seen.get(b)).toBe(0);
|
|
1433
1816
|
});
|
|
1434
|
-
|
|
1817
|
+
it7("reset filters by lane", async () => {
|
|
1435
1818
|
const tag = uid();
|
|
1436
1819
|
const src = `lane-reset-src-${tag}`;
|
|
1437
1820
|
const a = `lane-reset-a-${tag}`;
|
|
@@ -1449,7 +1832,7 @@ var runStoreTck = (options) => {
|
|
|
1449
1832
|
const mine = leases.filter((l) => l.stream === a || l.stream === b);
|
|
1450
1833
|
await store.ack(mine.map((l) => ({ ...l, at: l.at + 1 })));
|
|
1451
1834
|
const count = await store.reset({ lane: `rslow-${tag}` });
|
|
1452
|
-
|
|
1835
|
+
expect8(count).toBe(1);
|
|
1453
1836
|
const ats = /* @__PURE__ */ new Map();
|
|
1454
1837
|
for (const name of [a, b]) {
|
|
1455
1838
|
await store.query_streams((p) => ats.set(p.stream, p.at), {
|
|
@@ -1457,10 +1840,10 @@ var runStoreTck = (options) => {
|
|
|
1457
1840
|
stream_exact: true
|
|
1458
1841
|
});
|
|
1459
1842
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1843
|
+
expect8(ats.get(a)).toBe(-1);
|
|
1844
|
+
expect8(ats.get(b)).toBeGreaterThanOrEqual(0);
|
|
1462
1845
|
});
|
|
1463
|
-
|
|
1846
|
+
it7("unblock filters by lane", async () => {
|
|
1464
1847
|
const tag = uid();
|
|
1465
1848
|
const src = `lane-ub-src-${tag}`;
|
|
1466
1849
|
const a = `lane-ub-a-${tag}`;
|
|
@@ -1478,7 +1861,7 @@ var runStoreTck = (options) => {
|
|
|
1478
1861
|
const mine = leases.filter((l) => l.stream === a || l.stream === b);
|
|
1479
1862
|
await store.block(mine.map((l) => ({ ...l, error: "boom" })));
|
|
1480
1863
|
const count = await store.unblock({ lane: `uslow-${tag}` });
|
|
1481
|
-
|
|
1864
|
+
expect8(count).toBe(1);
|
|
1482
1865
|
const blocked = /* @__PURE__ */ new Map();
|
|
1483
1866
|
for (const name of [a, b]) {
|
|
1484
1867
|
await store.query_streams((p) => blocked.set(p.stream, p.blocked), {
|
|
@@ -1486,12 +1869,12 @@ var runStoreTck = (options) => {
|
|
|
1486
1869
|
stream_exact: true
|
|
1487
1870
|
});
|
|
1488
1871
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1872
|
+
expect8(blocked.get(a)).toBe(false);
|
|
1873
|
+
expect8(blocked.get(b)).toBe(true);
|
|
1491
1874
|
});
|
|
1492
1875
|
});
|
|
1493
|
-
|
|
1494
|
-
|
|
1876
|
+
describe8("truncate", () => {
|
|
1877
|
+
it7("seeds a tombstone when no snapshot is provided", async () => {
|
|
1495
1878
|
const s = `trunc-tomb-${uid()}`;
|
|
1496
1879
|
await store.commit(
|
|
1497
1880
|
s,
|
|
@@ -1499,7 +1882,7 @@ var runStoreTck = (options) => {
|
|
|
1499
1882
|
make_meta({ stream: s })
|
|
1500
1883
|
);
|
|
1501
1884
|
const result = await store.truncate([{ stream: s }]);
|
|
1502
|
-
|
|
1885
|
+
expect8(result.get(s)?.deleted).toBe(2);
|
|
1503
1886
|
const remaining = [];
|
|
1504
1887
|
await store.query(
|
|
1505
1888
|
(e) => {
|
|
@@ -1507,12 +1890,12 @@ var runStoreTck = (options) => {
|
|
|
1507
1890
|
},
|
|
1508
1891
|
{ stream: s, stream_exact: true }
|
|
1509
1892
|
);
|
|
1510
|
-
|
|
1511
|
-
|
|
1893
|
+
expect8(remaining).toHaveLength(1);
|
|
1894
|
+
expect8(remaining[0].name).toBe(
|
|
1512
1895
|
"__tombstone__"
|
|
1513
1896
|
);
|
|
1514
1897
|
});
|
|
1515
|
-
|
|
1898
|
+
it7("seeds a snapshot when one is provided", async () => {
|
|
1516
1899
|
const s = `trunc-snap-${uid()}`;
|
|
1517
1900
|
await store.commit(
|
|
1518
1901
|
s,
|
|
@@ -1522,7 +1905,7 @@ var runStoreTck = (options) => {
|
|
|
1522
1905
|
const result = await store.truncate([
|
|
1523
1906
|
{ stream: s, snapshot: { count: 7 } }
|
|
1524
1907
|
]);
|
|
1525
|
-
|
|
1908
|
+
expect8(result.get(s)?.deleted).toBe(1);
|
|
1526
1909
|
const remaining = [];
|
|
1527
1910
|
await store.query(
|
|
1528
1911
|
(e) => {
|
|
@@ -1530,24 +1913,24 @@ var runStoreTck = (options) => {
|
|
|
1530
1913
|
},
|
|
1531
1914
|
{ stream: s, stream_exact: true, with_snaps: true }
|
|
1532
1915
|
);
|
|
1533
|
-
|
|
1534
|
-
|
|
1916
|
+
expect8(remaining).toHaveLength(1);
|
|
1917
|
+
expect8(remaining[0].name).toBe(
|
|
1535
1918
|
"__snapshot__"
|
|
1536
1919
|
);
|
|
1537
|
-
|
|
1920
|
+
expect8(remaining[0].data).toEqual({ count: 7 });
|
|
1538
1921
|
});
|
|
1539
|
-
|
|
1922
|
+
it7("returns an empty map for empty input", async () => {
|
|
1540
1923
|
const result = await store.truncate([]);
|
|
1541
|
-
|
|
1924
|
+
expect8(result.size).toBe(0);
|
|
1542
1925
|
});
|
|
1543
|
-
|
|
1926
|
+
it7("returns 0 deleted for streams that don't exist", async () => {
|
|
1544
1927
|
const s = `trunc-missing-${uid()}`;
|
|
1545
1928
|
const result = await store.truncate([{ stream: s }]);
|
|
1546
|
-
|
|
1929
|
+
expect8(result.get(s)?.deleted).toBe(0);
|
|
1547
1930
|
});
|
|
1548
1931
|
});
|
|
1549
|
-
|
|
1550
|
-
|
|
1932
|
+
describe8("query_streams", () => {
|
|
1933
|
+
it7("returns positions filtered by stream regex, exact, source, and source_exact", async () => {
|
|
1551
1934
|
const tag = uid();
|
|
1552
1935
|
const proj1 = `qs-${tag}-projection-tickets`;
|
|
1553
1936
|
const proj2 = `qs-${tag}-projection-users`;
|
|
@@ -1566,37 +1949,37 @@ var runStoreTck = (options) => {
|
|
|
1566
1949
|
(p) => all.push({ stream: p.stream, source: p.source }),
|
|
1567
1950
|
{ stream: `qs-${tag}-.*` }
|
|
1568
1951
|
);
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1952
|
+
expect8(all_result.count).toBe(4);
|
|
1953
|
+
expect8(all_result.maxEventId).toBeGreaterThanOrEqual(-1);
|
|
1954
|
+
expect8(all.map((p) => p.stream).sort()).toEqual(
|
|
1572
1955
|
[proj1, proj2, dyn1, dyn2].sort()
|
|
1573
1956
|
);
|
|
1574
1957
|
const projections = [];
|
|
1575
1958
|
await store.query_streams((p) => projections.push(p.stream), {
|
|
1576
1959
|
stream: `qs-${tag}-projection-.*`
|
|
1577
1960
|
});
|
|
1578
|
-
|
|
1961
|
+
expect8(projections.sort()).toEqual([proj1, proj2].sort());
|
|
1579
1962
|
const exact = [];
|
|
1580
1963
|
await store.query_streams((p) => exact.push(p.stream), {
|
|
1581
1964
|
stream: dyn1,
|
|
1582
1965
|
stream_exact: true
|
|
1583
1966
|
});
|
|
1584
|
-
|
|
1967
|
+
expect8(exact).toEqual([dyn1]);
|
|
1585
1968
|
const by_source = [];
|
|
1586
1969
|
await store.query_streams((p) => by_source.push(p.stream), {
|
|
1587
1970
|
stream: `qs-${tag}-.*`,
|
|
1588
1971
|
source: `qs-${tag}-src-.*`
|
|
1589
1972
|
});
|
|
1590
|
-
|
|
1973
|
+
expect8(by_source.sort()).toEqual([dyn1, dyn2].sort());
|
|
1591
1974
|
const exact_source = [];
|
|
1592
1975
|
await store.query_streams((p) => exact_source.push(p.stream), {
|
|
1593
1976
|
stream: `qs-${tag}-.*`,
|
|
1594
1977
|
source: src2,
|
|
1595
1978
|
source_exact: true
|
|
1596
1979
|
});
|
|
1597
|
-
|
|
1980
|
+
expect8(exact_source).toEqual([dyn2]);
|
|
1598
1981
|
});
|
|
1599
|
-
|
|
1982
|
+
it7("paginates with limit + after (keyset)", async () => {
|
|
1600
1983
|
const tag = uid();
|
|
1601
1984
|
const streams = [
|
|
1602
1985
|
`qp-${tag}-a`,
|
|
@@ -1610,17 +1993,17 @@ var runStoreTck = (options) => {
|
|
|
1610
1993
|
stream: `qp-${tag}-.*`,
|
|
1611
1994
|
limit: 2
|
|
1612
1995
|
});
|
|
1613
|
-
|
|
1996
|
+
expect8(page1).toHaveLength(2);
|
|
1614
1997
|
const page2 = [];
|
|
1615
1998
|
await store.query_streams((p) => page2.push(p.stream), {
|
|
1616
1999
|
stream: `qp-${tag}-.*`,
|
|
1617
2000
|
limit: 2,
|
|
1618
2001
|
after: page1.at(-1)
|
|
1619
2002
|
});
|
|
1620
|
-
|
|
1621
|
-
|
|
2003
|
+
expect8(page2).toHaveLength(2);
|
|
2004
|
+
expect8([...page1, ...page2].sort()).toEqual([...streams].sort());
|
|
1622
2005
|
});
|
|
1623
|
-
|
|
2006
|
+
it7("filters by blocked status", async () => {
|
|
1624
2007
|
const tag = uid();
|
|
1625
2008
|
const s = `qb-${tag}`;
|
|
1626
2009
|
const sibling = `qb-${tag}-other`;
|
|
@@ -1640,18 +2023,18 @@ var runStoreTck = (options) => {
|
|
|
1640
2023
|
(p) => blocked.push({ stream: p.stream, error: p.error }),
|
|
1641
2024
|
{ stream: `qb-${tag}.*`, blocked: true }
|
|
1642
2025
|
);
|
|
1643
|
-
|
|
1644
|
-
|
|
2026
|
+
expect8(blocked).toHaveLength(1);
|
|
2027
|
+
expect8(blocked[0].error).toBe("boom");
|
|
1645
2028
|
const unblocked = [];
|
|
1646
2029
|
await store.query_streams((p) => unblocked.push(p.stream), {
|
|
1647
2030
|
stream: `qb-${tag}.*`,
|
|
1648
2031
|
blocked: false
|
|
1649
2032
|
});
|
|
1650
|
-
|
|
2033
|
+
expect8(unblocked).toEqual([sibling]);
|
|
1651
2034
|
});
|
|
1652
2035
|
});
|
|
1653
|
-
|
|
1654
|
-
|
|
2036
|
+
describe8("query_stats", () => {
|
|
2037
|
+
it7("array input \u2014 returns head per stream, absent when not in input", async () => {
|
|
1655
2038
|
const tag = uid();
|
|
1656
2039
|
const sA = `qst-${tag}-a`;
|
|
1657
2040
|
const sB = `qst-${tag}-b`;
|
|
@@ -1672,18 +2055,18 @@ var runStoreTck = (options) => {
|
|
|
1672
2055
|
make_meta({ stream: sUnasked })
|
|
1673
2056
|
);
|
|
1674
2057
|
const stats = await store.query_stats([sA, sB]);
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
2058
|
+
expect8(stats.size).toBe(2);
|
|
2059
|
+
expect8(stats.get(sA)?.head.name).toBe("Incremented");
|
|
2060
|
+
expect8((stats.get(sA)?.head.data).amount).toBe(2);
|
|
2061
|
+
expect8(stats.get(sB)?.head.name).toBe("Decremented");
|
|
2062
|
+
expect8((stats.get(sB)?.head.data).amount).toBe(5);
|
|
2063
|
+
expect8(stats.has(sUnasked)).toBe(false);
|
|
1681
2064
|
const empty = await store.query_stats([]);
|
|
1682
|
-
|
|
2065
|
+
expect8(empty.size).toBe(0);
|
|
1683
2066
|
const unknown = await store.query_stats([`qst-${tag}-missing`]);
|
|
1684
|
-
|
|
2067
|
+
expect8(unknown.size).toBe(0);
|
|
1685
2068
|
});
|
|
1686
|
-
|
|
2069
|
+
it7("tail returns the earliest event per stream", async () => {
|
|
1687
2070
|
const tag = uid();
|
|
1688
2071
|
const s = `qst-tail-${tag}`;
|
|
1689
2072
|
await store.commit(
|
|
@@ -1705,12 +2088,12 @@ var runStoreTck = (options) => {
|
|
|
1705
2088
|
tail: true
|
|
1706
2089
|
});
|
|
1707
2090
|
const r = stats.get(s);
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2091
|
+
expect8(r?.head.name).toBe("Incremented");
|
|
2092
|
+
expect8((r?.head.data).amount).toBe(3);
|
|
2093
|
+
expect8(r?.tail?.name).toBe("Incremented");
|
|
2094
|
+
expect8((r?.tail?.data).amount).toBe(1);
|
|
1712
2095
|
});
|
|
1713
|
-
|
|
2096
|
+
it7("count + names \u2014 full aggregates including framework markers", async () => {
|
|
1714
2097
|
const tag = uid();
|
|
1715
2098
|
const s = `qst-cn-${tag}`;
|
|
1716
2099
|
await store.commit(
|
|
@@ -1729,13 +2112,13 @@ var runStoreTck = (options) => {
|
|
|
1729
2112
|
names: true
|
|
1730
2113
|
});
|
|
1731
2114
|
const r = stats.get(s);
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2115
|
+
expect8(r?.count).toBe(4);
|
|
2116
|
+
expect8(r?.names?.[SNAP_EVENT2]).toBe(1);
|
|
2117
|
+
expect8(r?.names?.Incremented).toBe(2);
|
|
2118
|
+
expect8(r?.names?.Decremented).toBe(1);
|
|
2119
|
+
expect8(r?.names?.[SNAP_EVENT2]).toBe(1);
|
|
1737
2120
|
});
|
|
1738
|
-
|
|
2121
|
+
it7("exclude shifts head past filtered events; stream absent when all filtered", async () => {
|
|
1739
2122
|
const tag = uid();
|
|
1740
2123
|
const s = `qst-excl-${tag}`;
|
|
1741
2124
|
const sAllOut = `qst-allout-${tag}`;
|
|
@@ -1750,23 +2133,23 @@ var runStoreTck = (options) => {
|
|
|
1750
2133
|
make_meta({ stream: sAllOut })
|
|
1751
2134
|
);
|
|
1752
2135
|
const all = await store.query_stats([s]);
|
|
1753
|
-
|
|
1754
|
-
|
|
2136
|
+
expect8(all.get(s)?.head.name).toBe("Incremented");
|
|
2137
|
+
expect8((all.get(s)?.head.data).amount).toBe(3);
|
|
1755
2138
|
const excl = await store.query_stats([s], {
|
|
1756
2139
|
exclude: ["Incremented"]
|
|
1757
2140
|
});
|
|
1758
|
-
|
|
1759
|
-
|
|
2141
|
+
expect8(excl.get(s)?.head.name).toBe("Decremented");
|
|
2142
|
+
expect8((excl.get(s)?.head.data).amount).toBe(2);
|
|
1760
2143
|
const wipe = await store.query_stats([sAllOut], {
|
|
1761
2144
|
exclude: ["Incremented", "Decremented", "Reset"]
|
|
1762
2145
|
});
|
|
1763
|
-
|
|
2146
|
+
expect8(wipe.has(sAllOut)).toBe(false);
|
|
1764
2147
|
const no_tomb = await store.query_stats([s], {
|
|
1765
2148
|
exclude: [TOMBSTONE_EVENT]
|
|
1766
2149
|
});
|
|
1767
|
-
|
|
2150
|
+
expect8(no_tomb.get(s)?.head.name).toBe("Incremented");
|
|
1768
2151
|
});
|
|
1769
|
-
|
|
2152
|
+
it7("before \u2014 time travel narrows head/tail/count", async () => {
|
|
1770
2153
|
const tag = uid();
|
|
1771
2154
|
const s = `qst-tt-${tag}`;
|
|
1772
2155
|
const c1 = await store.commit(
|
|
@@ -1791,15 +2174,15 @@ var runStoreTck = (options) => {
|
|
|
1791
2174
|
before
|
|
1792
2175
|
});
|
|
1793
2176
|
const r = stats.get(s);
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
2177
|
+
expect8(r?.count).toBe(1);
|
|
2178
|
+
expect8(r?.head.id).toBe(c1[0].id);
|
|
2179
|
+
expect8(r?.tail?.id).toBe(c1[0].id);
|
|
1797
2180
|
const empty = await store.query_stats([s], {
|
|
1798
2181
|
before: 0
|
|
1799
2182
|
});
|
|
1800
|
-
|
|
2183
|
+
expect8(empty.has(s)).toBe(false);
|
|
1801
2184
|
});
|
|
1802
|
-
|
|
2185
|
+
it7("filter form \u2014 stream regex, stream_exact, empty {} match", async () => {
|
|
1803
2186
|
const tag = uid();
|
|
1804
2187
|
const sA = `qsf-${tag}-orders-1`;
|
|
1805
2188
|
const sB = `qsf-${tag}-orders-2`;
|
|
@@ -1822,18 +2205,18 @@ var runStoreTck = (options) => {
|
|
|
1822
2205
|
const orders = await store.query_stats({
|
|
1823
2206
|
stream: `^qsf-${tag}-orders-`
|
|
1824
2207
|
});
|
|
1825
|
-
|
|
2208
|
+
expect8([...orders.keys()].sort()).toEqual([sA, sB].sort());
|
|
1826
2209
|
const exact = await store.query_stats({
|
|
1827
2210
|
stream: sA,
|
|
1828
2211
|
stream_exact: true
|
|
1829
2212
|
});
|
|
1830
|
-
|
|
2213
|
+
expect8([...exact.keys()]).toEqual([sA]);
|
|
1831
2214
|
const all = await store.query_stats({
|
|
1832
2215
|
stream: `^qsf-${tag}-`
|
|
1833
2216
|
});
|
|
1834
|
-
|
|
2217
|
+
expect8([...all.keys()].sort()).toEqual([sA, sB, sOther].sort());
|
|
1835
2218
|
});
|
|
1836
|
-
|
|
2219
|
+
it7("compose with query_streams for subscription-level filters", async () => {
|
|
1837
2220
|
const tag = uid();
|
|
1838
2221
|
const a = `qsc-${tag}-a`;
|
|
1839
2222
|
const b = `qsc-${tag}-b`;
|
|
@@ -1850,7 +2233,7 @@ var runStoreTck = (options) => {
|
|
|
1850
2233
|
);
|
|
1851
2234
|
const leased = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1852
2235
|
const mine = leased.find((l) => l.stream === a);
|
|
1853
|
-
|
|
2236
|
+
expect8(mine).toBeDefined();
|
|
1854
2237
|
const others = leased.filter((l) => l.stream !== a);
|
|
1855
2238
|
await store.ack(others);
|
|
1856
2239
|
await store.block([{ ...mine, error: "boom" }]);
|
|
@@ -1859,12 +2242,12 @@ var runStoreTck = (options) => {
|
|
|
1859
2242
|
stream: `^qsc-${tag}-`,
|
|
1860
2243
|
blocked: true
|
|
1861
2244
|
});
|
|
1862
|
-
|
|
2245
|
+
expect8(blocked_names).toEqual([a]);
|
|
1863
2246
|
const stats = await store.query_stats(blocked_names);
|
|
1864
|
-
|
|
1865
|
-
|
|
2247
|
+
expect8(stats.get(a)?.head.name).toBe("Incremented");
|
|
2248
|
+
expect8(stats.has(b)).toBe(false);
|
|
1866
2249
|
});
|
|
1867
|
-
|
|
2250
|
+
it7("empty filter {} \u2014 matches every event-bearing stream", async () => {
|
|
1868
2251
|
const tag = uid();
|
|
1869
2252
|
const a = `qse-${tag}-a`;
|
|
1870
2253
|
const b = `qse-${tag}-b`;
|
|
@@ -1879,10 +2262,10 @@ var runStoreTck = (options) => {
|
|
|
1879
2262
|
make_meta({ stream: b })
|
|
1880
2263
|
);
|
|
1881
2264
|
const all = await store.query_stats({});
|
|
1882
|
-
|
|
1883
|
-
|
|
2265
|
+
expect8(all.has(a)).toBe(true);
|
|
2266
|
+
expect8(all.has(b)).toBe(true);
|
|
1884
2267
|
});
|
|
1885
|
-
|
|
2268
|
+
it7("stat-flag combinations \u2014 count-only, names-only, tail-only", async () => {
|
|
1886
2269
|
const tag = uid();
|
|
1887
2270
|
const s = `qsfl-${tag}`;
|
|
1888
2271
|
await store.commit(
|
|
@@ -1893,22 +2276,22 @@ var runStoreTck = (options) => {
|
|
|
1893
2276
|
const c = await store.query_stats([s], {
|
|
1894
2277
|
count: true
|
|
1895
2278
|
});
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2279
|
+
expect8(c.get(s)?.count).toBe(3);
|
|
2280
|
+
expect8(c.get(s)?.names).toBeUndefined();
|
|
2281
|
+
expect8(c.get(s)?.tail).toBeUndefined();
|
|
1899
2282
|
const n = await store.query_stats([s], {
|
|
1900
2283
|
names: true
|
|
1901
2284
|
});
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2285
|
+
expect8(n.get(s)?.names).toEqual({ Incremented: 2, Decremented: 1 });
|
|
2286
|
+
expect8(n.get(s)?.count).toBeUndefined();
|
|
2287
|
+
expect8(n.get(s)?.tail).toBeUndefined();
|
|
1905
2288
|
const t = await store.query_stats([s], { tail: true });
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
2289
|
+
expect8(t.get(s)?.tail?.name).toBe("Incremented");
|
|
2290
|
+
expect8((t.get(s)?.tail?.data).amount).toBe(1);
|
|
2291
|
+
expect8(t.get(s)?.count).toBeUndefined();
|
|
2292
|
+
expect8(t.get(s)?.names).toBeUndefined();
|
|
1910
2293
|
});
|
|
1911
|
-
|
|
2294
|
+
it7("paginates with limit + after (keyset), ordered by stream name", async () => {
|
|
1912
2295
|
const tag = uid();
|
|
1913
2296
|
const streams = [
|
|
1914
2297
|
`qsp-${tag}-a`,
|
|
@@ -1928,28 +2311,28 @@ var runStoreTck = (options) => {
|
|
|
1928
2311
|
{ limit: 2 }
|
|
1929
2312
|
);
|
|
1930
2313
|
const k1 = [...page1.keys()];
|
|
1931
|
-
|
|
2314
|
+
expect8(k1).toEqual([`qsp-${tag}-a`, `qsp-${tag}-b`]);
|
|
1932
2315
|
const page2 = await store.query_stats(
|
|
1933
2316
|
{ stream: `qsp-${tag}-.*` },
|
|
1934
2317
|
{ limit: 2, after: k1.at(-1) }
|
|
1935
2318
|
);
|
|
1936
2319
|
const k2 = [...page2.keys()];
|
|
1937
|
-
|
|
2320
|
+
expect8(k2).toEqual([`qsp-${tag}-c`, `qsp-${tag}-d`]);
|
|
1938
2321
|
const page3 = await store.query_stats(
|
|
1939
2322
|
{ stream: `qsp-${tag}-.*` },
|
|
1940
2323
|
{ limit: 2, after: k2.at(-1) }
|
|
1941
2324
|
);
|
|
1942
|
-
|
|
2325
|
+
expect8(page3.size).toBe(0);
|
|
1943
2326
|
const all = await store.query_stats({
|
|
1944
2327
|
stream: `qsp-${tag}-.*`
|
|
1945
2328
|
});
|
|
1946
|
-
|
|
2329
|
+
expect8([...all.keys()].sort()).toEqual([...streams].sort());
|
|
1947
2330
|
});
|
|
1948
2331
|
});
|
|
1949
|
-
|
|
2332
|
+
describe8.skipIf(!caps.source_matches)(
|
|
1950
2333
|
"query_streams source_matches (capability)",
|
|
1951
2334
|
() => {
|
|
1952
|
-
|
|
2335
|
+
it7("returns only subscriptions whose source pattern matches a name", async () => {
|
|
1953
2336
|
const tag = uid();
|
|
1954
2337
|
const subConcreteA = `sm-${tag}-sub-a`;
|
|
1955
2338
|
const subConcreteB = `sm-${tag}-sub-b`;
|
|
@@ -1970,7 +2353,7 @@ var runStoreTck = (options) => {
|
|
|
1970
2353
|
stream: `sm-${tag}-sub-.*`,
|
|
1971
2354
|
source_matches: [srcA]
|
|
1972
2355
|
});
|
|
1973
|
-
|
|
2356
|
+
expect8(matched.sort()).toEqual(
|
|
1974
2357
|
[subConcreteA, subRegex, subNoSource].sort()
|
|
1975
2358
|
);
|
|
1976
2359
|
const none = [];
|
|
@@ -1978,20 +2361,20 @@ var runStoreTck = (options) => {
|
|
|
1978
2361
|
stream: `sm-${tag}-sub-.*`,
|
|
1979
2362
|
source_matches: [`sm-${tag}-unrelated`]
|
|
1980
2363
|
});
|
|
1981
|
-
|
|
2364
|
+
expect8(none).toEqual([subNoSource]);
|
|
1982
2365
|
const both = [];
|
|
1983
2366
|
await store.query_streams((p) => both.push(p.stream), {
|
|
1984
2367
|
stream: `sm-${tag}-sub-.*`,
|
|
1985
2368
|
source_matches: [srcA, srcB]
|
|
1986
2369
|
});
|
|
1987
|
-
|
|
2370
|
+
expect8(both.sort()).toEqual(
|
|
1988
2371
|
[subConcreteA, subConcreteB, subRegex, subNoSource].sort()
|
|
1989
2372
|
);
|
|
1990
2373
|
});
|
|
1991
2374
|
}
|
|
1992
2375
|
);
|
|
1993
|
-
|
|
1994
|
-
|
|
2376
|
+
describe8("query_streams anchor contract", () => {
|
|
2377
|
+
it7("plain regex without anchors is a substring match", async () => {
|
|
1995
2378
|
const tag = uid();
|
|
1996
2379
|
const inner = `qsr-${tag}-inner`;
|
|
1997
2380
|
const longer = `qsr-${tag}-inner-extra`;
|
|
@@ -2005,9 +2388,9 @@ var runStoreTck = (options) => {
|
|
|
2005
2388
|
await store.query_streams((p) => seen.push(p.stream), {
|
|
2006
2389
|
stream: `qsr-${tag}-inner`
|
|
2007
2390
|
});
|
|
2008
|
-
|
|
2391
|
+
expect8(seen.sort()).toEqual([inner, longer].sort());
|
|
2009
2392
|
});
|
|
2010
|
-
|
|
2393
|
+
it7("caller-anchored `^name$` matches only the whole string", async () => {
|
|
2011
2394
|
const tag = uid();
|
|
2012
2395
|
const inner = `qsr-${tag}-anchor`;
|
|
2013
2396
|
const longer = `qsr-${tag}-anchor-extra`;
|
|
@@ -2016,9 +2399,9 @@ var runStoreTck = (options) => {
|
|
|
2016
2399
|
await store.query_streams((p) => seen.push(p.stream), {
|
|
2017
2400
|
stream: `^qsr-${tag}-anchor$`
|
|
2018
2401
|
});
|
|
2019
|
-
|
|
2402
|
+
expect8(seen).toEqual([inner]);
|
|
2020
2403
|
});
|
|
2021
|
-
|
|
2404
|
+
it7("caller-anchored `^prefix` matches by prefix", async () => {
|
|
2022
2405
|
const tag = uid();
|
|
2023
2406
|
const a = `qsr-${tag}-pfx-a`;
|
|
2024
2407
|
const b = `qsr-${tag}-pfx-b`;
|
|
@@ -2032,11 +2415,11 @@ var runStoreTck = (options) => {
|
|
|
2032
2415
|
await store.query_streams((p) => seen.push(p.stream), {
|
|
2033
2416
|
stream: `^qsr-${tag}-pfx-`
|
|
2034
2417
|
});
|
|
2035
|
-
|
|
2418
|
+
expect8(seen.sort()).toEqual([a, b].sort());
|
|
2036
2419
|
});
|
|
2037
2420
|
});
|
|
2038
|
-
|
|
2039
|
-
|
|
2421
|
+
describe8("prioritize anchor contract", () => {
|
|
2422
|
+
it7("caller-anchored `^name$` filter matches only the whole string", async () => {
|
|
2040
2423
|
const tag = uid();
|
|
2041
2424
|
const inner = `pr-${tag}-anchor`;
|
|
2042
2425
|
const longer = `pr-${tag}-anchor-extra`;
|
|
@@ -2048,17 +2431,17 @@ var runStoreTck = (options) => {
|
|
|
2048
2431
|
{ stream: `^pr-${tag}-anchor$` },
|
|
2049
2432
|
7
|
|
2050
2433
|
);
|
|
2051
|
-
|
|
2434
|
+
expect8(updated).toBe(1);
|
|
2052
2435
|
const seen = /* @__PURE__ */ new Map();
|
|
2053
2436
|
await store.query_streams((p) => seen.set(p.stream, p.priority), {
|
|
2054
2437
|
stream: `pr-${tag}-anchor`
|
|
2055
2438
|
});
|
|
2056
|
-
|
|
2057
|
-
|
|
2439
|
+
expect8(seen.get(inner)).toBe(7);
|
|
2440
|
+
expect8(seen.get(longer)).toBe(0);
|
|
2058
2441
|
});
|
|
2059
2442
|
});
|
|
2060
|
-
|
|
2061
|
-
|
|
2443
|
+
describe8("query_streams head", () => {
|
|
2444
|
+
it7("maxEventId tracks the highest committed id", async () => {
|
|
2062
2445
|
const s = `head-${uid()}`;
|
|
2063
2446
|
await store.subscribe([{ stream: s }]);
|
|
2064
2447
|
await store.commit(
|
|
@@ -2071,21 +2454,21 @@ var runStoreTck = (options) => {
|
|
|
2071
2454
|
(p) => positions.push(p.stream),
|
|
2072
2455
|
{ stream: s, stream_exact: true, limit: 1 }
|
|
2073
2456
|
);
|
|
2074
|
-
|
|
2075
|
-
|
|
2457
|
+
expect8(maxEventId).toBeGreaterThanOrEqual(0);
|
|
2458
|
+
expect8(positions).toEqual([s]);
|
|
2076
2459
|
});
|
|
2077
2460
|
});
|
|
2078
|
-
|
|
2079
|
-
|
|
2461
|
+
describe8("seed_stream helper coverage", () => {
|
|
2462
|
+
it7("commits N events with monotonically increasing ids", async () => {
|
|
2080
2463
|
const s = `seed-${uid()}`;
|
|
2081
2464
|
const committed = await seed_stream(store, s, 3);
|
|
2082
|
-
|
|
2465
|
+
expect8(committed).toHaveLength(3);
|
|
2083
2466
|
for (let i = 1; i < committed.length; i++) {
|
|
2084
|
-
|
|
2467
|
+
expect8(committed[i].id).toBeGreaterThan(committed[i - 1].id);
|
|
2085
2468
|
}
|
|
2086
2469
|
});
|
|
2087
2470
|
});
|
|
2088
|
-
|
|
2471
|
+
describe8.skipIf(!caps.restore)("restore (capability)", () => {
|
|
2089
2472
|
beforeEach(async () => {
|
|
2090
2473
|
await store.drop();
|
|
2091
2474
|
await store.seed();
|
|
@@ -2122,18 +2505,18 @@ var runStoreTck = (options) => {
|
|
|
2122
2505
|
await cache.dispose();
|
|
2123
2506
|
}
|
|
2124
2507
|
};
|
|
2125
|
-
|
|
2508
|
+
it7("returns kept=0 on an empty source", async () => {
|
|
2126
2509
|
const result = await restore(as_source([]));
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2510
|
+
expect8(result.kept).toBe(0);
|
|
2511
|
+
expect8(result.duration_ms).toBeGreaterThanOrEqual(0);
|
|
2512
|
+
expect8(result.dropped).toEqual({
|
|
2130
2513
|
closed_streams: 0,
|
|
2131
2514
|
snapshots: 0
|
|
2132
2515
|
});
|
|
2133
2516
|
const events2 = await collect(store, { limit: 10 });
|
|
2134
|
-
|
|
2517
|
+
expect8(events2).toHaveLength(0);
|
|
2135
2518
|
});
|
|
2136
|
-
|
|
2519
|
+
it7("rebuilds a single stream and preserves `created` verbatim", async () => {
|
|
2137
2520
|
const s = `restore-single-${uid()}`;
|
|
2138
2521
|
const t0 = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
|
|
2139
2522
|
const t1 = /* @__PURE__ */ new Date("2020-01-02T00:00:00.000Z");
|
|
@@ -2144,7 +2527,7 @@ var runStoreTck = (options) => {
|
|
|
2144
2527
|
event(3, s, 2, "Decremented", t2, { amount: 1 })
|
|
2145
2528
|
];
|
|
2146
2529
|
const result = await restore(as_source(events2));
|
|
2147
|
-
|
|
2530
|
+
expect8(result.kept).toBe(3);
|
|
2148
2531
|
const back = [];
|
|
2149
2532
|
await store.query(
|
|
2150
2533
|
(e) => {
|
|
@@ -2152,8 +2535,8 @@ var runStoreTck = (options) => {
|
|
|
2152
2535
|
},
|
|
2153
2536
|
{ stream: s, stream_exact: true }
|
|
2154
2537
|
);
|
|
2155
|
-
|
|
2156
|
-
|
|
2538
|
+
expect8(back).toHaveLength(3);
|
|
2539
|
+
expect8(
|
|
2157
2540
|
back.map((e) => ({
|
|
2158
2541
|
stream: e.stream,
|
|
2159
2542
|
version: e.version,
|
|
@@ -2185,7 +2568,7 @@ var runStoreTck = (options) => {
|
|
|
2185
2568
|
}
|
|
2186
2569
|
]);
|
|
2187
2570
|
});
|
|
2188
|
-
|
|
2571
|
+
it7("rebuilds multiple streams interleaved", async () => {
|
|
2189
2572
|
const a = `restore-multi-a-${uid()}`;
|
|
2190
2573
|
const b = `restore-multi-b-${uid()}`;
|
|
2191
2574
|
const t = /* @__PURE__ */ new Date("2020-06-01T00:00:00.000Z");
|
|
@@ -2196,7 +2579,7 @@ var runStoreTck = (options) => {
|
|
|
2196
2579
|
event(4, b, 1, "Incremented", t, { amount: 30 })
|
|
2197
2580
|
];
|
|
2198
2581
|
const result = await restore(as_source(events2));
|
|
2199
|
-
|
|
2582
|
+
expect8(result.kept).toBe(4);
|
|
2200
2583
|
const aBack = [];
|
|
2201
2584
|
const bBack = [];
|
|
2202
2585
|
await store.query(
|
|
@@ -2211,10 +2594,10 @@ var runStoreTck = (options) => {
|
|
|
2211
2594
|
},
|
|
2212
2595
|
{ stream: b, stream_exact: true }
|
|
2213
2596
|
);
|
|
2214
|
-
|
|
2215
|
-
|
|
2597
|
+
expect8(aBack.map((e) => e.version)).toEqual([0, 1]);
|
|
2598
|
+
expect8(bBack.map((e) => e.version)).toEqual([0, 1]);
|
|
2216
2599
|
});
|
|
2217
|
-
|
|
2600
|
+
it7("preserves Date `created` verbatim", async () => {
|
|
2218
2601
|
const s = `restore-isoc-${uid()}`;
|
|
2219
2602
|
const iso = "2021-07-15T12:34:56.789Z";
|
|
2220
2603
|
await restore(
|
|
@@ -2237,10 +2620,10 @@ var runStoreTck = (options) => {
|
|
|
2237
2620
|
},
|
|
2238
2621
|
{ stream: s, stream_exact: true }
|
|
2239
2622
|
);
|
|
2240
|
-
|
|
2241
|
-
|
|
2623
|
+
expect8(back).toHaveLength(1);
|
|
2624
|
+
expect8(back[0].created.toISOString()).toBe(iso);
|
|
2242
2625
|
});
|
|
2243
|
-
|
|
2626
|
+
it7("wipes pre-existing events before inserting", async () => {
|
|
2244
2627
|
const old = `restore-old-${uid()}`;
|
|
2245
2628
|
await store.commit(
|
|
2246
2629
|
old,
|
|
@@ -2256,14 +2639,14 @@ var runStoreTck = (options) => {
|
|
|
2256
2639
|
stream: old,
|
|
2257
2640
|
stream_exact: true
|
|
2258
2641
|
});
|
|
2259
|
-
|
|
2642
|
+
expect8(old_back).toHaveLength(0);
|
|
2260
2643
|
const fresh_back = await collect(store, {
|
|
2261
2644
|
stream: fresh,
|
|
2262
2645
|
stream_exact: true
|
|
2263
2646
|
});
|
|
2264
|
-
|
|
2647
|
+
expect8(fresh_back).toHaveLength(1);
|
|
2265
2648
|
});
|
|
2266
|
-
|
|
2649
|
+
it7("clears subscription/stream-position metadata", async () => {
|
|
2267
2650
|
const sub = `restore-sub-${uid()}`;
|
|
2268
2651
|
await store.subscribe([{ stream: sub, source: "anything" }]);
|
|
2269
2652
|
const collect_streams = async () => {
|
|
@@ -2274,12 +2657,12 @@ var runStoreTck = (options) => {
|
|
|
2274
2657
|
return out;
|
|
2275
2658
|
};
|
|
2276
2659
|
const before = await collect_streams();
|
|
2277
|
-
|
|
2660
|
+
expect8(before.includes(sub)).toBe(true);
|
|
2278
2661
|
await restore(as_source([]));
|
|
2279
2662
|
const after = await collect_streams();
|
|
2280
|
-
|
|
2663
|
+
expect8(after.includes(sub)).toBe(false);
|
|
2281
2664
|
});
|
|
2282
|
-
|
|
2665
|
+
it7("preserves snapshot events through restore", async () => {
|
|
2283
2666
|
const s = `restore-snap-${uid()}`;
|
|
2284
2667
|
const t = /* @__PURE__ */ new Date("2020-04-01T00:00:00.000Z");
|
|
2285
2668
|
await restore(
|
|
@@ -2288,7 +2671,7 @@ var runStoreTck = (options) => {
|
|
|
2288
2671
|
id: 1,
|
|
2289
2672
|
stream: s,
|
|
2290
2673
|
version: 0,
|
|
2291
|
-
name:
|
|
2674
|
+
name: SNAP_EVENT2,
|
|
2292
2675
|
data: { count: 42 },
|
|
2293
2676
|
created: t,
|
|
2294
2677
|
meta: { correlation: "snap", causation: {} }
|
|
@@ -2300,10 +2683,10 @@ var runStoreTck = (options) => {
|
|
|
2300
2683
|
stream_exact: true,
|
|
2301
2684
|
with_snaps: true
|
|
2302
2685
|
});
|
|
2303
|
-
|
|
2304
|
-
|
|
2686
|
+
expect8(back).toHaveLength(1);
|
|
2687
|
+
expect8(back[0].name).toBe(SNAP_EVENT2);
|
|
2305
2688
|
});
|
|
2306
|
-
|
|
2689
|
+
it7("rewrites causation refs through the old\u2192new id map", async () => {
|
|
2307
2690
|
const s = `restore-caus-${uid()}`;
|
|
2308
2691
|
const t = /* @__PURE__ */ new Date("2020-08-01T00:00:00.000Z");
|
|
2309
2692
|
const events2 = [
|
|
@@ -2353,12 +2736,12 @@ var runStoreTck = (options) => {
|
|
|
2353
2736
|
},
|
|
2354
2737
|
{ stream: s, stream_exact: true }
|
|
2355
2738
|
);
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2739
|
+
expect8(back).toHaveLength(3);
|
|
2740
|
+
expect8(back[0].meta.causation.event).toBeUndefined();
|
|
2741
|
+
expect8(back[1].meta.causation.event?.id).toBe(back[0].id);
|
|
2742
|
+
expect8(back[2].meta.causation.event?.id).toBe(back[1].id);
|
|
2360
2743
|
});
|
|
2361
|
-
|
|
2744
|
+
it7("leaves causation refs unmapped when the target isn't in the source", async () => {
|
|
2362
2745
|
const s = `restore-orphan-${uid()}`;
|
|
2363
2746
|
const t = /* @__PURE__ */ new Date("2020-09-01T00:00:00.000Z");
|
|
2364
2747
|
await restore(
|
|
@@ -2386,9 +2769,9 @@ var runStoreTck = (options) => {
|
|
|
2386
2769
|
},
|
|
2387
2770
|
{ stream: s, stream_exact: true }
|
|
2388
2771
|
);
|
|
2389
|
-
|
|
2772
|
+
expect8(back[0].meta.causation.event?.id).toBe(999);
|
|
2390
2773
|
});
|
|
2391
|
-
|
|
2774
|
+
it7("rolls back atomically when the source throws mid-iteration", async () => {
|
|
2392
2775
|
const original = `restore-pre-${uid()}`;
|
|
2393
2776
|
const committed = await store.commit(
|
|
2394
2777
|
original,
|
|
@@ -2414,7 +2797,7 @@ var runStoreTck = (options) => {
|
|
|
2414
2797
|
async dispose() {
|
|
2415
2798
|
}
|
|
2416
2799
|
};
|
|
2417
|
-
await
|
|
2800
|
+
await expect8(restore(explosive)).rejects.toThrow("boom");
|
|
2418
2801
|
const back = [];
|
|
2419
2802
|
await store.query(
|
|
2420
2803
|
(e) => {
|
|
@@ -2422,10 +2805,10 @@ var runStoreTck = (options) => {
|
|
|
2422
2805
|
},
|
|
2423
2806
|
{ stream: original, stream_exact: true }
|
|
2424
2807
|
);
|
|
2425
|
-
|
|
2426
|
-
|
|
2808
|
+
expect8(back).toHaveLength(3);
|
|
2809
|
+
expect8(back.map((e) => e.id)).toEqual(committed.map((c) => c.id));
|
|
2427
2810
|
});
|
|
2428
|
-
|
|
2811
|
+
it7("drop_snapshots: skips SNAP_EVENT rows and counts them", async () => {
|
|
2429
2812
|
const s = `restore-drop-snap-${uid()}`;
|
|
2430
2813
|
const t = /* @__PURE__ */ new Date("2020-10-01T00:00:00.000Z");
|
|
2431
2814
|
const result = await restore(
|
|
@@ -2435,7 +2818,7 @@ var runStoreTck = (options) => {
|
|
|
2435
2818
|
id: 2,
|
|
2436
2819
|
stream: s,
|
|
2437
2820
|
version: 1,
|
|
2438
|
-
name:
|
|
2821
|
+
name: SNAP_EVENT2,
|
|
2439
2822
|
data: { count: 1 },
|
|
2440
2823
|
created: t,
|
|
2441
2824
|
meta: { correlation: "snap", causation: {} }
|
|
@@ -2444,19 +2827,19 @@ var runStoreTck = (options) => {
|
|
|
2444
2827
|
]),
|
|
2445
2828
|
{ drop_snapshots: true }
|
|
2446
2829
|
);
|
|
2447
|
-
|
|
2448
|
-
|
|
2830
|
+
expect8(result.kept).toBe(2);
|
|
2831
|
+
expect8(result.dropped.snapshots).toBe(1);
|
|
2449
2832
|
const back = await collect(store, {
|
|
2450
2833
|
stream: s,
|
|
2451
2834
|
stream_exact: true,
|
|
2452
2835
|
with_snaps: true
|
|
2453
2836
|
});
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
back.every((e) => e.name !==
|
|
2837
|
+
expect8(back).toHaveLength(2);
|
|
2838
|
+
expect8(
|
|
2839
|
+
back.every((e) => e.name !== SNAP_EVENT2)
|
|
2457
2840
|
).toBe(true);
|
|
2458
2841
|
});
|
|
2459
|
-
|
|
2842
|
+
it7("on_progress fires once per event (caller throttles)", async () => {
|
|
2460
2843
|
const calls = [];
|
|
2461
2844
|
const s = `restore-progress-${uid()}`;
|
|
2462
2845
|
const t = /* @__PURE__ */ new Date("2021-02-01T00:00:00.000Z");
|
|
@@ -2467,11 +2850,11 @@ var runStoreTck = (options) => {
|
|
|
2467
2850
|
]),
|
|
2468
2851
|
{ on_progress: (p) => calls.push(p.processed) }
|
|
2469
2852
|
);
|
|
2470
|
-
|
|
2853
|
+
expect8(calls).toEqual([1, 2]);
|
|
2471
2854
|
});
|
|
2472
2855
|
});
|
|
2473
|
-
|
|
2474
|
-
|
|
2856
|
+
describe8.skipIf(!caps.pii_isolation)("pii_isolation (capability)", () => {
|
|
2857
|
+
it7("commits and loads pii alongside data", async () => {
|
|
2475
2858
|
const s = `pii-roundtrip-${uid()}`;
|
|
2476
2859
|
const committed = await store.commit(
|
|
2477
2860
|
s,
|
|
@@ -2484,8 +2867,8 @@ var runStoreTck = (options) => {
|
|
|
2484
2867
|
],
|
|
2485
2868
|
make_meta({ stream: s })
|
|
2486
2869
|
);
|
|
2487
|
-
|
|
2488
|
-
|
|
2870
|
+
expect8(committed).toHaveLength(1);
|
|
2871
|
+
expect8(committed[0].pii).toEqual({
|
|
2489
2872
|
email: "u@example.com",
|
|
2490
2873
|
name: "Ursula"
|
|
2491
2874
|
});
|
|
@@ -2496,11 +2879,11 @@ var runStoreTck = (options) => {
|
|
|
2496
2879
|
},
|
|
2497
2880
|
{ stream: s, stream_exact: true }
|
|
2498
2881
|
);
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2882
|
+
expect8(seen).toHaveLength(1);
|
|
2883
|
+
expect8(seen[0].pii).toEqual({ email: "u@example.com", name: "Ursula" });
|
|
2884
|
+
expect8(seen[0].data).toEqual({ amount: 1 });
|
|
2502
2885
|
});
|
|
2503
|
-
|
|
2886
|
+
it7("passes through events without pii (pii is null or undefined on load)", async () => {
|
|
2504
2887
|
const s = `pii-none-${uid()}`;
|
|
2505
2888
|
await store.commit(
|
|
2506
2889
|
s,
|
|
@@ -2514,10 +2897,10 @@ var runStoreTck = (options) => {
|
|
|
2514
2897
|
},
|
|
2515
2898
|
{ stream: s, stream_exact: true }
|
|
2516
2899
|
);
|
|
2517
|
-
|
|
2518
|
-
|
|
2900
|
+
expect8(seen).toHaveLength(1);
|
|
2901
|
+
expect8(seen[0].pii == null).toBe(true);
|
|
2519
2902
|
});
|
|
2520
|
-
|
|
2903
|
+
it7("wipes pii for every event on the stream via forget_pii", async () => {
|
|
2521
2904
|
const s = `pii-forget-${uid()}`;
|
|
2522
2905
|
await store.commit(
|
|
2523
2906
|
s,
|
|
@@ -2536,9 +2919,9 @@ var runStoreTck = (options) => {
|
|
|
2536
2919
|
make_meta({ stream: s })
|
|
2537
2920
|
);
|
|
2538
2921
|
const forget = store.forget_pii;
|
|
2539
|
-
|
|
2922
|
+
expect8(forget).toBeDefined();
|
|
2540
2923
|
const wiped = await forget.call(store, s);
|
|
2541
|
-
|
|
2924
|
+
expect8(wiped).toBe(2);
|
|
2542
2925
|
const seen = [];
|
|
2543
2926
|
await store.query(
|
|
2544
2927
|
(e) => {
|
|
@@ -2546,13 +2929,13 @@ var runStoreTck = (options) => {
|
|
|
2546
2929
|
},
|
|
2547
2930
|
{ stream: s, stream_exact: true }
|
|
2548
2931
|
);
|
|
2549
|
-
|
|
2932
|
+
expect8(seen).toHaveLength(2);
|
|
2550
2933
|
for (const e of seen) {
|
|
2551
|
-
|
|
2552
|
-
|
|
2934
|
+
expect8(e.pii == null).toBe(true);
|
|
2935
|
+
expect8(e.data).toBeDefined();
|
|
2553
2936
|
}
|
|
2554
2937
|
});
|
|
2555
|
-
|
|
2938
|
+
it7("is idempotent \u2014 second forget_pii returns 0, no error", async () => {
|
|
2556
2939
|
const s = `pii-forget-idem-${uid()}`;
|
|
2557
2940
|
await store.commit(
|
|
2558
2941
|
s,
|
|
@@ -2567,11 +2950,11 @@ var runStoreTck = (options) => {
|
|
|
2567
2950
|
);
|
|
2568
2951
|
const forget = store.forget_pii;
|
|
2569
2952
|
const first = await forget.call(store, s);
|
|
2570
|
-
|
|
2953
|
+
expect8(first).toBe(1);
|
|
2571
2954
|
const second = await forget.call(store, s);
|
|
2572
|
-
|
|
2955
|
+
expect8(second).toBe(0);
|
|
2573
2956
|
});
|
|
2574
|
-
|
|
2957
|
+
it7("only wipes the targeted stream \u2014 siblings untouched", async () => {
|
|
2575
2958
|
const sA = `pii-iso-a-${uid()}`;
|
|
2576
2959
|
const sB = `pii-iso-b-${uid()}`;
|
|
2577
2960
|
await store.commit(
|
|
@@ -2604,7 +2987,7 @@ var runStoreTck = (options) => {
|
|
|
2604
2987
|
},
|
|
2605
2988
|
{ stream: sA, stream_exact: true }
|
|
2606
2989
|
);
|
|
2607
|
-
|
|
2990
|
+
expect8(a[0].pii == null).toBe(true);
|
|
2608
2991
|
const b = [];
|
|
2609
2992
|
await store.query(
|
|
2610
2993
|
(e) => {
|
|
@@ -2612,9 +2995,9 @@ var runStoreTck = (options) => {
|
|
|
2612
2995
|
},
|
|
2613
2996
|
{ stream: sB, stream_exact: true }
|
|
2614
2997
|
);
|
|
2615
|
-
|
|
2998
|
+
expect8(b[0].pii).toEqual({ email: "bob@example.com" });
|
|
2616
2999
|
});
|
|
2617
|
-
|
|
3000
|
+
it7("forget_pii on a stream with no pii events returns 0", async () => {
|
|
2618
3001
|
const s = `pii-forget-empty-${uid()}`;
|
|
2619
3002
|
await store.commit(
|
|
2620
3003
|
s,
|
|
@@ -2622,14 +3005,14 @@ var runStoreTck = (options) => {
|
|
|
2622
3005
|
make_meta({ stream: s })
|
|
2623
3006
|
);
|
|
2624
3007
|
const wiped = await store.forget_pii.call(store, s);
|
|
2625
|
-
|
|
3008
|
+
expect8(wiped).toBe(0);
|
|
2626
3009
|
});
|
|
2627
3010
|
});
|
|
2628
3011
|
if (caps.notify) {
|
|
2629
|
-
|
|
2630
|
-
|
|
3012
|
+
describe8("notify (capability)", () => {
|
|
3013
|
+
it7("delivers a notification when a different instance commits", async () => {
|
|
2631
3014
|
const notify = store.notify;
|
|
2632
|
-
|
|
3015
|
+
expect8(notify).toBeDefined();
|
|
2633
3016
|
const received = [];
|
|
2634
3017
|
let resolve_arrived;
|
|
2635
3018
|
const arrived = new Promise((res) => {
|
|
@@ -2648,9 +3031,9 @@ var runStoreTck = (options) => {
|
|
|
2648
3031
|
make_meta({ stream })
|
|
2649
3032
|
);
|
|
2650
3033
|
await arrived;
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
3034
|
+
expect8(received.length).toBeGreaterThanOrEqual(1);
|
|
3035
|
+
expect8(received[0].stream).toBe(stream);
|
|
3036
|
+
expect8(received[0].events.length).toBeGreaterThanOrEqual(1);
|
|
2654
3037
|
} finally {
|
|
2655
3038
|
await writer.dispose();
|
|
2656
3039
|
await Promise.resolve(disposer());
|
|
@@ -2671,9 +3054,12 @@ export {
|
|
|
2671
3054
|
dec,
|
|
2672
3055
|
inc,
|
|
2673
3056
|
reset,
|
|
3057
|
+
runCacheDifferentialTck,
|
|
2674
3058
|
runCacheTck,
|
|
3059
|
+
runLoggerDifferentialTck,
|
|
2675
3060
|
runLoggerTck,
|
|
2676
3061
|
runStabilityTck,
|
|
3062
|
+
runStoreDifferentialTck,
|
|
2677
3063
|
runStorePropertyTck,
|
|
2678
3064
|
runStoreTck,
|
|
2679
3065
|
uid
|