@rotorsoft/act-tck 1.13.2 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/index.d.ts +2 -0
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/@types/store-property-tck.d.ts +22 -0
- package/dist/@types/store-property-tck.d.ts.map +1 -0
- package/dist/@types/store-tck.d.ts +11 -0
- package/dist/@types/store-tck.d.ts.map +1 -1
- package/dist/index.cjs +601 -366
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +511 -277
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -228,6 +228,208 @@ ${content}`;
|
|
|
228
228
|
}).join("\n");
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
// src/store-property-tck.ts
|
|
232
|
+
import { fc, test } from "@fast-check/vitest";
|
|
233
|
+
import { afterAll, beforeAll, describe as describe4, expect as expect4 } from "vitest";
|
|
234
|
+
var streamArb = fc.constantFrom("s1", "s2", "s3");
|
|
235
|
+
var commitArb = fc.record({
|
|
236
|
+
stream: streamArb,
|
|
237
|
+
count: fc.integer({ min: 1, max: 3 })
|
|
238
|
+
});
|
|
239
|
+
var claimStreamArb = fc.constantFrom("a", "b", "c");
|
|
240
|
+
var opArb = fc.oneof(
|
|
241
|
+
fc.record({ kind: fc.constant("commit"), stream: claimStreamArb }),
|
|
242
|
+
fc.record({ kind: fc.constant("claim") }),
|
|
243
|
+
fc.record({ kind: fc.constant("ack-all") }),
|
|
244
|
+
fc.record({ kind: fc.constant("block-all") })
|
|
245
|
+
);
|
|
246
|
+
var events = (count) => Array.from({ length: count }, () => inc(1));
|
|
247
|
+
var runStorePropertyTck = (options) => {
|
|
248
|
+
const numRuns = options.numRuns ?? 100;
|
|
249
|
+
describe4(`TCK / Store properties / ${options.name}`, () => {
|
|
250
|
+
let store;
|
|
251
|
+
beforeAll(async () => {
|
|
252
|
+
store = await options.factory();
|
|
253
|
+
await store.seed();
|
|
254
|
+
});
|
|
255
|
+
afterAll(async () => {
|
|
256
|
+
await store.dispose();
|
|
257
|
+
});
|
|
258
|
+
const reset2 = async () => {
|
|
259
|
+
await store.drop();
|
|
260
|
+
await store.seed();
|
|
261
|
+
};
|
|
262
|
+
describe4("commit version invariants", () => {
|
|
263
|
+
test.prop([fc.array(commitArb, { minLength: 0, maxLength: 30 })], {
|
|
264
|
+
numRuns
|
|
265
|
+
})(
|
|
266
|
+
"per-stream versions are 0..N-1 in commit order, regardless of interleaving",
|
|
267
|
+
async (commits) => {
|
|
268
|
+
await reset2();
|
|
269
|
+
const expected = /* @__PURE__ */ new Map();
|
|
270
|
+
for (const { stream, count } of commits) {
|
|
271
|
+
const before = expected.get(stream) ?? -1;
|
|
272
|
+
const committed = await store.commit(
|
|
273
|
+
stream,
|
|
274
|
+
events(count),
|
|
275
|
+
make_meta({ stream })
|
|
276
|
+
);
|
|
277
|
+
committed.forEach((e, i) => {
|
|
278
|
+
expect4(e.version).toBe(before + 1 + i);
|
|
279
|
+
});
|
|
280
|
+
expected.set(stream, before + count);
|
|
281
|
+
}
|
|
282
|
+
for (const stream of new Set(commits.map((c) => c.stream))) {
|
|
283
|
+
const seen = await collect(store, { stream, stream_exact: true });
|
|
284
|
+
seen.forEach((e, i) => {
|
|
285
|
+
expect4(e.version).toBe(i);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
test.prop([fc.array(commitArb, { minLength: 1, maxLength: 20 })], {
|
|
291
|
+
numRuns
|
|
292
|
+
})(
|
|
293
|
+
"bad expectedVersion throws and commits no events",
|
|
294
|
+
async (commits) => {
|
|
295
|
+
await reset2();
|
|
296
|
+
for (const { stream: stream2, count } of commits) {
|
|
297
|
+
await store.commit(
|
|
298
|
+
stream2,
|
|
299
|
+
events(count),
|
|
300
|
+
make_meta({ stream: stream2 })
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
const stream = commits[0].stream;
|
|
304
|
+
const before = await collect(store, { stream, stream_exact: true });
|
|
305
|
+
await expect4(
|
|
306
|
+
store.commit(
|
|
307
|
+
stream,
|
|
308
|
+
[inc(1)],
|
|
309
|
+
make_meta({ stream }),
|
|
310
|
+
before.length
|
|
311
|
+
)
|
|
312
|
+
).rejects.toThrow();
|
|
313
|
+
const after = await collect(store, { stream, stream_exact: true });
|
|
314
|
+
expect4(after.length).toBe(before.length);
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
describe4("claim/lease lifecycle invariants", () => {
|
|
319
|
+
test.prop([fc.array(opArb, { minLength: 1, maxLength: 30 })], {
|
|
320
|
+
numRuns
|
|
321
|
+
})(
|
|
322
|
+
"no leaks: claims are always acked or blocked, never lost",
|
|
323
|
+
async (ops) => {
|
|
324
|
+
await reset2();
|
|
325
|
+
await store.subscribe([
|
|
326
|
+
{ stream: "a" },
|
|
327
|
+
{ stream: "b" },
|
|
328
|
+
{ stream: "c" }
|
|
329
|
+
]);
|
|
330
|
+
let totalClaims = 0;
|
|
331
|
+
let totalResolved = 0;
|
|
332
|
+
let pending = [];
|
|
333
|
+
for (const op of ops) {
|
|
334
|
+
if (op.kind === "commit") {
|
|
335
|
+
await store.commit(
|
|
336
|
+
op.stream,
|
|
337
|
+
[inc(1)],
|
|
338
|
+
make_meta({ stream: op.stream })
|
|
339
|
+
);
|
|
340
|
+
} else if (op.kind === "claim") {
|
|
341
|
+
const claimed = await store.claim(5, 5, "worker", 6e4);
|
|
342
|
+
totalClaims += claimed.length;
|
|
343
|
+
pending = [...pending, ...claimed];
|
|
344
|
+
} else if (op.kind === "ack-all") {
|
|
345
|
+
const acked = await store.ack(pending);
|
|
346
|
+
totalResolved += acked.length;
|
|
347
|
+
pending = pending.filter(
|
|
348
|
+
(p) => !acked.find((a) => a.stream === p.stream)
|
|
349
|
+
);
|
|
350
|
+
} else {
|
|
351
|
+
const blocked = await store.block(
|
|
352
|
+
pending.map((p) => ({ ...p, error: "test" }))
|
|
353
|
+
);
|
|
354
|
+
totalResolved += blocked.length;
|
|
355
|
+
pending = pending.filter(
|
|
356
|
+
(p) => !blocked.find((b) => b.stream === p.stream)
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
expect4(totalResolved + pending.length).toBe(totalClaims);
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
test.prop(
|
|
364
|
+
[
|
|
365
|
+
fc.array(claimStreamArb, { minLength: 1, maxLength: 5 }),
|
|
366
|
+
fc.array(claimStreamArb, { minLength: 0, maxLength: 5 })
|
|
367
|
+
],
|
|
368
|
+
{ numRuns }
|
|
369
|
+
)(
|
|
370
|
+
"ack advances the watermark monotonically per stream",
|
|
371
|
+
async (commitsA, commitsB) => {
|
|
372
|
+
await reset2();
|
|
373
|
+
const all = [.../* @__PURE__ */ new Set([...commitsA, ...commitsB, "a", "b", "c"])];
|
|
374
|
+
await store.subscribe(all.map((stream) => ({ stream })));
|
|
375
|
+
for (const stream of commitsA) {
|
|
376
|
+
await store.commit(
|
|
377
|
+
stream,
|
|
378
|
+
[inc(1)],
|
|
379
|
+
make_meta({ stream })
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
const acked1 = await store.ack(
|
|
383
|
+
await store.claim(10, 10, "worker", 6e4)
|
|
384
|
+
);
|
|
385
|
+
const watermark1 = new Map(all.map((stream) => [stream, -1]));
|
|
386
|
+
for (const l of acked1) watermark1.set(l.stream, l.at);
|
|
387
|
+
for (const stream of commitsB) {
|
|
388
|
+
await store.commit(
|
|
389
|
+
stream,
|
|
390
|
+
[inc(1)],
|
|
391
|
+
make_meta({ stream })
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
const acked2 = await store.ack(
|
|
395
|
+
await store.claim(10, 10, "worker", 6e4)
|
|
396
|
+
);
|
|
397
|
+
for (const lease of acked2) {
|
|
398
|
+
expect4(lease.at).toBeGreaterThanOrEqual(
|
|
399
|
+
watermark1.get(lease.stream)
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
test.prop([fc.array(claimStreamArb, { minLength: 1, maxLength: 5 })], {
|
|
405
|
+
numRuns
|
|
406
|
+
})("blocked streams cannot be claimed again", async (commits) => {
|
|
407
|
+
await reset2();
|
|
408
|
+
const streams = [...new Set(commits)];
|
|
409
|
+
await store.subscribe(streams.map((stream) => ({ stream })));
|
|
410
|
+
for (const stream of commits) {
|
|
411
|
+
await store.commit(
|
|
412
|
+
stream,
|
|
413
|
+
[inc(1)],
|
|
414
|
+
make_meta({ stream })
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const claimed = await store.claim(10, 10, "worker", 6e4);
|
|
418
|
+
await store.block(claimed.map((l) => ({ ...l, error: "test" })));
|
|
419
|
+
const blockedSet = new Set(claimed.map((l) => l.stream));
|
|
420
|
+
await store.subscribe([{ stream: "ctrl" }]);
|
|
421
|
+
await store.commit(
|
|
422
|
+
"ctrl",
|
|
423
|
+
[inc(1)],
|
|
424
|
+
make_meta({ stream: "ctrl" })
|
|
425
|
+
);
|
|
426
|
+
const reclaim = await store.claim(10, 10, "worker2", 6e4);
|
|
427
|
+
for (const l of reclaim) expect4(blockedSet.has(l.stream)).toBe(false);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
};
|
|
432
|
+
|
|
231
433
|
// src/store-tck.ts
|
|
232
434
|
import {
|
|
233
435
|
act,
|
|
@@ -236,20 +438,20 @@ import {
|
|
|
236
438
|
SNAP_EVENT,
|
|
237
439
|
TOMBSTONE_EVENT
|
|
238
440
|
} from "@rotorsoft/act";
|
|
239
|
-
import { afterAll, beforeAll, describe as
|
|
441
|
+
import { afterAll as afterAll2, beforeAll as beforeAll2, describe as describe5, expect as expect5, it as it4 } from "vitest";
|
|
240
442
|
var runStoreTck = (options) => {
|
|
241
|
-
|
|
443
|
+
describe5(`TCK / Store / ${options.name}`, () => {
|
|
242
444
|
let store;
|
|
243
445
|
const caps = { ...options.capabilities };
|
|
244
|
-
|
|
446
|
+
beforeAll2(async () => {
|
|
245
447
|
store = await options.factory();
|
|
246
448
|
await store.drop();
|
|
247
449
|
await store.seed();
|
|
248
450
|
});
|
|
249
|
-
|
|
451
|
+
afterAll2(async () => {
|
|
250
452
|
await store.dispose();
|
|
251
453
|
});
|
|
252
|
-
|
|
454
|
+
describe5("commit", () => {
|
|
253
455
|
it4("returns committed events with sequenced ids and versions", async () => {
|
|
254
456
|
const s = `commit-seq-${uid()}`;
|
|
255
457
|
const committed = await store.commit(
|
|
@@ -257,14 +459,14 @@ var runStoreTck = (options) => {
|
|
|
257
459
|
[inc(1), inc(2), dec(3)],
|
|
258
460
|
make_meta({ stream: s })
|
|
259
461
|
);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
462
|
+
expect5(committed).toHaveLength(3);
|
|
463
|
+
expect5(committed[0].version).toBe(0);
|
|
464
|
+
expect5(committed[1].version).toBe(1);
|
|
465
|
+
expect5(committed[2].version).toBe(2);
|
|
466
|
+
expect5(committed[0].name).toBe("Incremented");
|
|
467
|
+
expect5(committed[2].data).toEqual({ amount: 3 });
|
|
266
468
|
for (let i = 1; i < committed.length; i++) {
|
|
267
|
-
|
|
469
|
+
expect5(committed[i].id).toBeGreaterThan(committed[i - 1].id);
|
|
268
470
|
}
|
|
269
471
|
});
|
|
270
472
|
it4("attaches correlation and stream metadata", async () => {
|
|
@@ -275,8 +477,8 @@ var runStoreTck = (options) => {
|
|
|
275
477
|
[inc(1)],
|
|
276
478
|
make_meta({ stream: s, correlation })
|
|
277
479
|
);
|
|
278
|
-
|
|
279
|
-
|
|
480
|
+
expect5(committed[0].stream).toBe(s);
|
|
481
|
+
expect5(committed[0].meta.correlation).toBe(correlation);
|
|
280
482
|
});
|
|
281
483
|
it4("throws ConcurrencyError when expectedVersion is wrong", async () => {
|
|
282
484
|
const s = `commit-cc-${uid()}`;
|
|
@@ -291,7 +493,7 @@ var runStoreTck = (options) => {
|
|
|
291
493
|
make_meta({ stream: s }),
|
|
292
494
|
0
|
|
293
495
|
);
|
|
294
|
-
await
|
|
496
|
+
await expect5(
|
|
295
497
|
store.commit(s, [inc(1)], make_meta({ stream: s }), 0)
|
|
296
498
|
).rejects.toBeInstanceOf(ConcurrencyError);
|
|
297
499
|
});
|
|
@@ -302,14 +504,14 @@ var runStoreTck = (options) => {
|
|
|
302
504
|
[inc(1), inc(2)],
|
|
303
505
|
make_meta({ stream: s })
|
|
304
506
|
);
|
|
305
|
-
await
|
|
507
|
+
await expect5(
|
|
306
508
|
store.commit(s, [inc(3)], make_meta({ stream: s }), 0)
|
|
307
509
|
).rejects.toBeInstanceOf(ConcurrencyError);
|
|
308
510
|
const found = await collect(store, { stream: s, stream_exact: true });
|
|
309
|
-
|
|
511
|
+
expect5(found).toHaveLength(2);
|
|
310
512
|
});
|
|
311
513
|
});
|
|
312
|
-
|
|
514
|
+
describe5("query", () => {
|
|
313
515
|
it4("filters by stream, names, correlation, limit, with_snaps", async () => {
|
|
314
516
|
const s1 = `q-s1-${uid()}`;
|
|
315
517
|
const s2 = `q-s2-${uid()}`;
|
|
@@ -328,21 +530,21 @@ var runStoreTck = (options) => {
|
|
|
328
530
|
stream: s1,
|
|
329
531
|
stream_exact: true
|
|
330
532
|
});
|
|
331
|
-
|
|
533
|
+
expect5(by_stream).toHaveLength(2);
|
|
332
534
|
const by_name = await collect(store, {
|
|
333
535
|
stream: s2,
|
|
334
536
|
stream_exact: true,
|
|
335
537
|
names: ["Reset"]
|
|
336
538
|
});
|
|
337
|
-
|
|
338
|
-
|
|
539
|
+
expect5(by_name).toHaveLength(1);
|
|
540
|
+
expect5(by_name[0].name).toBe("Reset");
|
|
339
541
|
const by_correlation = await collect(store, { correlation: cor });
|
|
340
|
-
|
|
542
|
+
expect5(by_correlation).toHaveLength(5);
|
|
341
543
|
const limited = await collect(store, {
|
|
342
544
|
correlation: cor,
|
|
343
545
|
limit: 2
|
|
344
546
|
});
|
|
345
|
-
|
|
547
|
+
expect5(limited).toHaveLength(2);
|
|
346
548
|
});
|
|
347
549
|
it4("supports backward traversal", async () => {
|
|
348
550
|
const s = `q-back-${uid()}`;
|
|
@@ -357,8 +559,8 @@ var runStoreTck = (options) => {
|
|
|
357
559
|
stream_exact: true,
|
|
358
560
|
backward: true
|
|
359
561
|
});
|
|
360
|
-
|
|
361
|
-
|
|
562
|
+
expect5(forward.map((e) => e.id)).toEqual(committed.map((c) => c.id));
|
|
563
|
+
expect5(backward.map((e) => e.id)).toEqual(
|
|
362
564
|
[...committed].reverse().map((c) => c.id)
|
|
363
565
|
);
|
|
364
566
|
const latest = await collect(store, {
|
|
@@ -367,8 +569,8 @@ var runStoreTck = (options) => {
|
|
|
367
569
|
backward: true,
|
|
368
570
|
limit: 1
|
|
369
571
|
});
|
|
370
|
-
|
|
371
|
-
|
|
572
|
+
expect5(latest).toHaveLength(1);
|
|
573
|
+
expect5(latest[0].id).toBe(committed.at(-1).id);
|
|
372
574
|
});
|
|
373
575
|
it4("after/before bound the id range", async () => {
|
|
374
576
|
const s = `q-bounds-${uid()}`;
|
|
@@ -382,7 +584,7 @@ var runStoreTck = (options) => {
|
|
|
382
584
|
stream_exact: true,
|
|
383
585
|
after: committed[0].id
|
|
384
586
|
});
|
|
385
|
-
|
|
587
|
+
expect5(after_first.map((e) => e.id)).toEqual(
|
|
386
588
|
committed.slice(1).map((c) => c.id)
|
|
387
589
|
);
|
|
388
590
|
const before_last = await collect(store, {
|
|
@@ -390,7 +592,7 @@ var runStoreTck = (options) => {
|
|
|
390
592
|
stream_exact: true,
|
|
391
593
|
before: committed[committed.length - 1].id
|
|
392
594
|
});
|
|
393
|
-
|
|
595
|
+
expect5(before_last.map((e) => e.id)).toEqual(
|
|
394
596
|
committed.slice(0, -1).map((c) => c.id)
|
|
395
597
|
);
|
|
396
598
|
});
|
|
@@ -409,13 +611,13 @@ var runStoreTck = (options) => {
|
|
|
409
611
|
created_after: before,
|
|
410
612
|
created_before: future
|
|
411
613
|
});
|
|
412
|
-
|
|
614
|
+
expect5(in_window.length).toBe(1);
|
|
413
615
|
const out_of_window = await collect(store, {
|
|
414
616
|
stream: s,
|
|
415
617
|
stream_exact: true,
|
|
416
618
|
created_after: future
|
|
417
619
|
});
|
|
418
|
-
|
|
620
|
+
expect5(out_of_window.length).toBe(0);
|
|
419
621
|
});
|
|
420
622
|
it4("backward traversal short-circuits at `after` id boundary", async () => {
|
|
421
623
|
const s = `q-back-after-${uid()}`;
|
|
@@ -430,7 +632,7 @@ var runStoreTck = (options) => {
|
|
|
430
632
|
backward: true,
|
|
431
633
|
after: committed[0].id
|
|
432
634
|
});
|
|
433
|
-
|
|
635
|
+
expect5(got.map((e) => e.id)).toEqual([
|
|
434
636
|
committed[2].id,
|
|
435
637
|
committed[1].id
|
|
436
638
|
]);
|
|
@@ -449,7 +651,7 @@ var runStoreTck = (options) => {
|
|
|
449
651
|
backward: true,
|
|
450
652
|
created_after: future
|
|
451
653
|
});
|
|
452
|
-
|
|
654
|
+
expect5(got).toHaveLength(0);
|
|
453
655
|
});
|
|
454
656
|
it4("backward traversal honors created_before by skipping newer events", async () => {
|
|
455
657
|
const s = `q-back-ts-${uid()}`;
|
|
@@ -461,7 +663,7 @@ var runStoreTck = (options) => {
|
|
|
461
663
|
backward: true,
|
|
462
664
|
created_before: past
|
|
463
665
|
});
|
|
464
|
-
|
|
666
|
+
expect5(got).toHaveLength(0);
|
|
465
667
|
});
|
|
466
668
|
it4("stream_exact disables regex matching", async () => {
|
|
467
669
|
const tag = uid();
|
|
@@ -478,8 +680,8 @@ var runStoreTck = (options) => {
|
|
|
478
680
|
make_meta({ stream: b })
|
|
479
681
|
);
|
|
480
682
|
const exact = await collect(store, { stream: a, stream_exact: true });
|
|
481
|
-
|
|
482
|
-
|
|
683
|
+
expect5(exact).toHaveLength(1);
|
|
684
|
+
expect5(exact[0].data).toEqual({ amount: 1 });
|
|
483
685
|
});
|
|
484
686
|
it4("plain regex without anchors is a substring match", async () => {
|
|
485
687
|
const tag = uid();
|
|
@@ -496,7 +698,7 @@ var runStoreTck = (options) => {
|
|
|
496
698
|
make_meta({ stream: longer })
|
|
497
699
|
);
|
|
498
700
|
const got = await collect(store, { stream: `qr-${tag}-inner` });
|
|
499
|
-
|
|
701
|
+
expect5(got.map((e) => e.stream).sort()).toEqual([inner, longer].sort());
|
|
500
702
|
});
|
|
501
703
|
it4("caller-anchored `^name$` matches only the whole string", async () => {
|
|
502
704
|
const tag = uid();
|
|
@@ -513,8 +715,8 @@ var runStoreTck = (options) => {
|
|
|
513
715
|
make_meta({ stream: longer })
|
|
514
716
|
);
|
|
515
717
|
const got = await collect(store, { stream: `^qr-${tag}-anchor$` });
|
|
516
|
-
|
|
517
|
-
|
|
718
|
+
expect5(got).toHaveLength(1);
|
|
719
|
+
expect5(got[0].stream).toBe(inner);
|
|
518
720
|
});
|
|
519
721
|
it4("caller-anchored `^prefix` matches by prefix", async () => {
|
|
520
722
|
const tag = uid();
|
|
@@ -537,16 +739,16 @@ var runStoreTck = (options) => {
|
|
|
537
739
|
make_meta({ stream: other })
|
|
538
740
|
);
|
|
539
741
|
const got = await collect(store, { stream: `^qr-${tag}-pfx-` });
|
|
540
|
-
|
|
742
|
+
expect5(got.map((e) => e.stream).sort()).toEqual([a, b].sort());
|
|
541
743
|
});
|
|
542
744
|
});
|
|
543
|
-
|
|
745
|
+
describe5("subscribe + claim + ack", () => {
|
|
544
746
|
it4("subscribes new streams and is idempotent on repeat", async () => {
|
|
545
747
|
const s = `sub-${uid()}`;
|
|
546
748
|
const first = await store.subscribe([{ stream: s }]);
|
|
547
|
-
|
|
749
|
+
expect5(first.subscribed).toBe(1);
|
|
548
750
|
const second = await store.subscribe([{ stream: s }]);
|
|
549
|
-
|
|
751
|
+
expect5(second.subscribed).toBe(0);
|
|
550
752
|
});
|
|
551
753
|
it4("claims a subscribed stream and ack releases the lease", async () => {
|
|
552
754
|
const s = `claim-${uid()}`;
|
|
@@ -559,8 +761,8 @@ var runStoreTck = (options) => {
|
|
|
559
761
|
const by = `worker-${uid()}`;
|
|
560
762
|
const leased = await store.claim(100, 0, by, 1e4);
|
|
561
763
|
const mine = leased.find((l) => l.stream === s);
|
|
562
|
-
|
|
563
|
-
|
|
764
|
+
expect5(mine).toBeDefined();
|
|
765
|
+
expect5(mine.by).toBe(by);
|
|
564
766
|
await store.ack([{ ...mine, at: mine.at + 1 }]);
|
|
565
767
|
});
|
|
566
768
|
it4("does not double-claim a held lease", async () => {
|
|
@@ -574,7 +776,7 @@ var runStoreTck = (options) => {
|
|
|
574
776
|
);
|
|
575
777
|
const leasedA = await store.claim(100, 0, `wA-${uid()}`, 1e5);
|
|
576
778
|
const targetA = leasedA.find((l) => l.stream === s);
|
|
577
|
-
|
|
779
|
+
expect5(targetA).toBeDefined();
|
|
578
780
|
await store.subscribe([{ stream: other }]);
|
|
579
781
|
await store.commit(
|
|
580
782
|
other,
|
|
@@ -582,9 +784,9 @@ var runStoreTck = (options) => {
|
|
|
582
784
|
make_meta({ stream: other })
|
|
583
785
|
);
|
|
584
786
|
const leasedB = await store.claim(100, 0, `wB-${uid()}`, 1e5);
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
787
|
+
expect5(leasedB.length).toBeGreaterThan(0);
|
|
788
|
+
expect5(leasedB.find((l) => l.stream === s)).toBeUndefined();
|
|
789
|
+
expect5(leasedB.find((l) => l.stream === other)).toBeDefined();
|
|
588
790
|
});
|
|
589
791
|
it4("supports dual frontiers (lagging + leading)", async () => {
|
|
590
792
|
const s = `claim-dual-${uid()}`;
|
|
@@ -596,10 +798,10 @@ var runStoreTck = (options) => {
|
|
|
596
798
|
);
|
|
597
799
|
const first = await store.claim(100, 0, `w-${uid()}`, 1);
|
|
598
800
|
const mine = first.find((l) => l.stream === s);
|
|
599
|
-
|
|
801
|
+
expect5(mine).toBeDefined();
|
|
600
802
|
await store.ack([{ ...mine, at: mine.at + 1 }]);
|
|
601
803
|
const second = await store.claim(0, 100, `w-${uid()}`, 1);
|
|
602
|
-
|
|
804
|
+
expect5(second.find((l) => l.stream === s)).toBeDefined();
|
|
603
805
|
});
|
|
604
806
|
it4("dedupes when both frontiers would return the same stream", async () => {
|
|
605
807
|
const s = `claim-dedup-${uid()}`;
|
|
@@ -611,7 +813,7 @@ var runStoreTck = (options) => {
|
|
|
611
813
|
);
|
|
612
814
|
const claimed = await store.claim(100, 100, `w-${uid()}`, 1e5);
|
|
613
815
|
const matches = claimed.filter((l) => l.stream === s);
|
|
614
|
-
|
|
816
|
+
expect5(matches).toHaveLength(1);
|
|
615
817
|
});
|
|
616
818
|
it4("silently ignores ack from the wrong holder", async () => {
|
|
617
819
|
const s = `ack-wrong-${uid()}`;
|
|
@@ -630,14 +832,14 @@ var runStoreTck = (options) => {
|
|
|
630
832
|
const leased = await store.claim(100, 0, `right-${uid()}`, 1e5);
|
|
631
833
|
const mine = leased.find((l) => l.stream === s);
|
|
632
834
|
const sibling_lease = leased.find((l) => l.stream === sibling);
|
|
633
|
-
|
|
634
|
-
|
|
835
|
+
expect5(mine).toBeDefined();
|
|
836
|
+
expect5(sibling_lease).toBeDefined();
|
|
635
837
|
const acked = await store.ack([
|
|
636
838
|
{ ...mine, by: "imposter" },
|
|
637
839
|
sibling_lease
|
|
638
840
|
]);
|
|
639
|
-
|
|
640
|
-
|
|
841
|
+
expect5(acked.length).toBeGreaterThan(0);
|
|
842
|
+
expect5(acked.find((l) => l.stream === s)).toBeUndefined();
|
|
641
843
|
});
|
|
642
844
|
it4("ack with a stale (lower) watermark does not throw", async () => {
|
|
643
845
|
const s = `ack-stale-${uid()}`;
|
|
@@ -645,8 +847,8 @@ var runStoreTck = (options) => {
|
|
|
645
847
|
const by = `w-${uid()}`;
|
|
646
848
|
const leased = await store.claim(100, 0, by, 1e5);
|
|
647
849
|
const mine = leased.find((l) => l.stream === s);
|
|
648
|
-
|
|
649
|
-
await
|
|
850
|
+
expect5(mine).toBeDefined();
|
|
851
|
+
await expect5(
|
|
650
852
|
store.ack([{ ...mine, at: -5 }])
|
|
651
853
|
).resolves.toBeDefined();
|
|
652
854
|
});
|
|
@@ -656,13 +858,13 @@ var runStoreTck = (options) => {
|
|
|
656
858
|
await fresh.drop();
|
|
657
859
|
await fresh.seed();
|
|
658
860
|
const claimed = await fresh.claim(1, 1, `w-${uid()}`, 1e3);
|
|
659
|
-
|
|
861
|
+
expect5(claimed).toEqual([]);
|
|
660
862
|
} finally {
|
|
661
863
|
await fresh.dispose();
|
|
662
864
|
}
|
|
663
865
|
});
|
|
664
866
|
});
|
|
665
|
-
|
|
867
|
+
describe5("lease semantics", () => {
|
|
666
868
|
it4("returns retry=0 on first claim and increments on re-claim without ack", async () => {
|
|
667
869
|
const fresh = await options.factory();
|
|
668
870
|
try {
|
|
@@ -677,12 +879,12 @@ var runStoreTck = (options) => {
|
|
|
677
879
|
);
|
|
678
880
|
const first = await fresh.claim(1, 0, `w-${uid()}`, 0);
|
|
679
881
|
const f = first.find((l) => l.stream === s);
|
|
680
|
-
|
|
681
|
-
|
|
882
|
+
expect5(f).toBeDefined();
|
|
883
|
+
expect5(f.retry).toBe(0);
|
|
682
884
|
const second = await fresh.claim(1, 0, `w-${uid()}`, 1e5);
|
|
683
885
|
const sec = second.find((l) => l.stream === s);
|
|
684
|
-
|
|
685
|
-
|
|
886
|
+
expect5(sec).toBeDefined();
|
|
887
|
+
expect5(sec.retry).toBe(1);
|
|
686
888
|
} finally {
|
|
687
889
|
await fresh.dispose();
|
|
688
890
|
}
|
|
@@ -700,15 +902,46 @@ var runStoreTck = (options) => {
|
|
|
700
902
|
make_meta({ stream: s })
|
|
701
903
|
);
|
|
702
904
|
const lag = await fresh.claim(1, 0, `w-${uid()}`, 0);
|
|
703
|
-
|
|
905
|
+
expect5(lag.find((l) => l.stream === s)?.lagging).toBe(true);
|
|
704
906
|
const lead = await fresh.claim(0, 1, `w-${uid()}`, 1e5);
|
|
705
|
-
|
|
907
|
+
expect5(lead.find((l) => l.stream === s)?.lagging).toBe(false);
|
|
908
|
+
} finally {
|
|
909
|
+
await fresh.dispose();
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
});
|
|
913
|
+
describe5.skipIf(!caps.concurrent_claim)("concurrency (capability)", () => {
|
|
914
|
+
it4("never double-leases a stream across concurrent claimers", async () => {
|
|
915
|
+
const fresh = await options.factory();
|
|
916
|
+
try {
|
|
917
|
+
await fresh.drop();
|
|
918
|
+
await fresh.seed();
|
|
919
|
+
const streams = Array.from(
|
|
920
|
+
{ length: 8 },
|
|
921
|
+
() => `concurrent-${uid()}`
|
|
922
|
+
);
|
|
923
|
+
await fresh.subscribe(streams.map((stream) => ({ stream })));
|
|
924
|
+
for (const stream of streams) {
|
|
925
|
+
await fresh.commit(
|
|
926
|
+
stream,
|
|
927
|
+
[inc(1)],
|
|
928
|
+
make_meta({ stream })
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
const owned = new Set(streams);
|
|
932
|
+
const [a, b] = await Promise.all([
|
|
933
|
+
fresh.claim(100, 100, `wA-${uid()}`, 6e4),
|
|
934
|
+
fresh.claim(100, 100, `wB-${uid()}`, 6e4)
|
|
935
|
+
]);
|
|
936
|
+
const claimed = [...a, ...b].map((l) => l.stream).filter((stream) => owned.has(stream));
|
|
937
|
+
expect5(new Set(claimed).size).toBe(claimed.length);
|
|
938
|
+
expect5(claimed.length).toBe(owned.size);
|
|
706
939
|
} finally {
|
|
707
940
|
await fresh.dispose();
|
|
708
941
|
}
|
|
709
942
|
});
|
|
710
943
|
});
|
|
711
|
-
|
|
944
|
+
describe5("block", () => {
|
|
712
945
|
it4("hides blocked streams from claim", async () => {
|
|
713
946
|
const s = `block-${uid()}`;
|
|
714
947
|
await store.subscribe([{ stream: s }]);
|
|
@@ -719,16 +952,16 @@ var runStoreTck = (options) => {
|
|
|
719
952
|
);
|
|
720
953
|
const leased = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
721
954
|
const mine = leased.find((l) => l.stream === s);
|
|
722
|
-
|
|
955
|
+
expect5(mine).toBeDefined();
|
|
723
956
|
const others = leased.filter((l) => l.stream !== s);
|
|
724
957
|
await store.ack(others);
|
|
725
958
|
const blocked = await store.block([
|
|
726
959
|
{ ...mine, error: "boom" }
|
|
727
960
|
]);
|
|
728
|
-
|
|
729
|
-
|
|
961
|
+
expect5(blocked).toHaveLength(1);
|
|
962
|
+
expect5(blocked[0].error).toBe("boom");
|
|
730
963
|
const again = await store.claim(100, 100, `w2-${uid()}`, 1e5);
|
|
731
|
-
|
|
964
|
+
expect5(again.find((l) => l.stream === s)).toBeUndefined();
|
|
732
965
|
});
|
|
733
966
|
it4("rejects block calls from a different holder", async () => {
|
|
734
967
|
const s = `block-wrong-${uid()}`;
|
|
@@ -740,16 +973,16 @@ var runStoreTck = (options) => {
|
|
|
740
973
|
);
|
|
741
974
|
const leased = await store.claim(100, 0, `right-${uid()}`, 1e5);
|
|
742
975
|
const mine = leased.find((l) => l.stream === s);
|
|
743
|
-
|
|
976
|
+
expect5(mine).toBeDefined();
|
|
744
977
|
const others = leased.filter((l) => l.stream !== s);
|
|
745
978
|
await store.ack(others);
|
|
746
979
|
const blocked = await store.block([
|
|
747
980
|
{ ...mine, by: "imposter", error: "no" }
|
|
748
981
|
]);
|
|
749
|
-
|
|
982
|
+
expect5(blocked).toHaveLength(0);
|
|
750
983
|
});
|
|
751
984
|
});
|
|
752
|
-
|
|
985
|
+
describe5("reset", () => {
|
|
753
986
|
it4("rewinds a stream watermark to -1", async () => {
|
|
754
987
|
const s = `reset-${uid()}`;
|
|
755
988
|
await store.subscribe([{ stream: s }]);
|
|
@@ -760,13 +993,13 @@ var runStoreTck = (options) => {
|
|
|
760
993
|
);
|
|
761
994
|
const leased = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
762
995
|
const mine = leased.find((l) => l.stream === s);
|
|
763
|
-
|
|
996
|
+
expect5(mine).toBeDefined();
|
|
764
997
|
await store.ack([{ ...mine, at: 99 }]);
|
|
765
|
-
|
|
998
|
+
expect5(await store.reset([s])).toBe(1);
|
|
766
999
|
const after = await store.claim(100, 0, `w2-${uid()}`, 1e5);
|
|
767
1000
|
const back = after.find((l) => l.stream === s);
|
|
768
|
-
|
|
769
|
-
|
|
1001
|
+
expect5(back).toBeDefined();
|
|
1002
|
+
expect5(back.at).toBe(-1);
|
|
770
1003
|
});
|
|
771
1004
|
it4("clears blocked status when resetting", async () => {
|
|
772
1005
|
const s = `reset-blk-${uid()}`;
|
|
@@ -781,16 +1014,16 @@ var runStoreTck = (options) => {
|
|
|
781
1014
|
const others = leased.filter((l) => l.stream !== s);
|
|
782
1015
|
await store.ack(others);
|
|
783
1016
|
await store.block([{ ...mine, error: "boom" }]);
|
|
784
|
-
|
|
1017
|
+
expect5(await store.reset([s])).toBe(1);
|
|
785
1018
|
const after = await store.claim(100, 0, `w2-${uid()}`, 1e5);
|
|
786
|
-
|
|
1019
|
+
expect5(after.find((l) => l.stream === s)).toBeDefined();
|
|
787
1020
|
});
|
|
788
1021
|
it4("returns 0 for unknown streams and empty input", async () => {
|
|
789
|
-
|
|
790
|
-
|
|
1022
|
+
expect5(await store.reset([`missing-${uid()}`])).toBe(0);
|
|
1023
|
+
expect5(await store.reset([])).toBe(0);
|
|
791
1024
|
});
|
|
792
1025
|
});
|
|
793
|
-
|
|
1026
|
+
describe5("unblock", () => {
|
|
794
1027
|
it4("clears blocked flag and preserves the watermark", async () => {
|
|
795
1028
|
const s = `unblock-${uid()}`;
|
|
796
1029
|
await store.subscribe([{ stream: s }]);
|
|
@@ -809,7 +1042,7 @@ var runStoreTck = (options) => {
|
|
|
809
1042
|
await store.ack([{ ...m1, at: m1.at }]);
|
|
810
1043
|
const before_block = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
811
1044
|
const m2 = before_block.find((l) => l.stream === s);
|
|
812
|
-
|
|
1045
|
+
expect5(m2).toBeDefined();
|
|
813
1046
|
const watermark_before = m2.at;
|
|
814
1047
|
await store.block([{ ...m2, error: "permanent" }]);
|
|
815
1048
|
let blocked_flag;
|
|
@@ -819,13 +1052,13 @@ var runStoreTck = (options) => {
|
|
|
819
1052
|
},
|
|
820
1053
|
{ stream: s, stream_exact: true, limit: 1 }
|
|
821
1054
|
);
|
|
822
|
-
|
|
823
|
-
|
|
1055
|
+
expect5(blocked_flag).toBe(true);
|
|
1056
|
+
expect5(await store.unblock([s])).toBe(1);
|
|
824
1057
|
const after = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
825
1058
|
const back = after.find((l) => l.stream === s);
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1059
|
+
expect5(back).toBeDefined();
|
|
1060
|
+
expect5(back.at).toBe(watermark_before);
|
|
1061
|
+
expect5(back.retry).toBe(0);
|
|
829
1062
|
});
|
|
830
1063
|
it4("returns 0 when the stream is not blocked", async () => {
|
|
831
1064
|
const s = `unblock-noop-${uid()}`;
|
|
@@ -835,11 +1068,11 @@ var runStoreTck = (options) => {
|
|
|
835
1068
|
[inc(1)],
|
|
836
1069
|
make_meta({ stream: s })
|
|
837
1070
|
);
|
|
838
|
-
|
|
1071
|
+
expect5(await store.unblock([s])).toBe(0);
|
|
839
1072
|
});
|
|
840
1073
|
it4("returns 0 for unknown streams and empty input", async () => {
|
|
841
|
-
|
|
842
|
-
|
|
1074
|
+
expect5(await store.unblock([`missing-${uid()}`])).toBe(0);
|
|
1075
|
+
expect5(await store.unblock([])).toBe(0);
|
|
843
1076
|
});
|
|
844
1077
|
it4("only counts streams that were actually blocked", async () => {
|
|
845
1078
|
const s1 = `unblock-mix-a-${uid()}`;
|
|
@@ -860,7 +1093,7 @@ var runStoreTck = (options) => {
|
|
|
860
1093
|
const others = leased.filter((l) => l.stream !== s1);
|
|
861
1094
|
await store.ack(others);
|
|
862
1095
|
await store.block([{ ...m1, error: "boom" }]);
|
|
863
|
-
|
|
1096
|
+
expect5(await store.unblock([s1, s2])).toBe(1);
|
|
864
1097
|
});
|
|
865
1098
|
it4("filter form: unblocks by stream pattern", async () => {
|
|
866
1099
|
const tag = uid();
|
|
@@ -894,11 +1127,11 @@ var runStoreTck = (options) => {
|
|
|
894
1127
|
const count = await store.unblock({
|
|
895
1128
|
stream: `^unblock-filter-${tag}-`
|
|
896
1129
|
});
|
|
897
|
-
|
|
1130
|
+
expect5(count).toBe(2);
|
|
898
1131
|
const after = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1132
|
+
expect5(after.find((l) => l.stream === s3)).toBeUndefined();
|
|
1133
|
+
expect5(after.find((l) => l.stream === s1)).toBeDefined();
|
|
1134
|
+
expect5(after.find((l) => l.stream === s2)).toBeDefined();
|
|
902
1135
|
});
|
|
903
1136
|
it4("filter form: empty filter unblocks every blocked stream", async () => {
|
|
904
1137
|
const tag = uid();
|
|
@@ -924,7 +1157,7 @@ var runStoreTck = (options) => {
|
|
|
924
1157
|
const count = await store.unblock({
|
|
925
1158
|
stream: `^unblock-empty-${tag}-`
|
|
926
1159
|
});
|
|
927
|
-
|
|
1160
|
+
expect5(count).toBe(2);
|
|
928
1161
|
});
|
|
929
1162
|
it4("filter form: explicit blocked:false matches nothing", async () => {
|
|
930
1163
|
const tag = uid();
|
|
@@ -935,7 +1168,7 @@ var runStoreTck = (options) => {
|
|
|
935
1168
|
[inc(1)],
|
|
936
1169
|
make_meta({ stream: s })
|
|
937
1170
|
);
|
|
938
|
-
|
|
1171
|
+
expect5(
|
|
939
1172
|
await store.unblock({
|
|
940
1173
|
stream: `^unblock-blocked-false-${tag}`,
|
|
941
1174
|
blocked: false
|
|
@@ -943,7 +1176,7 @@ var runStoreTck = (options) => {
|
|
|
943
1176
|
).toBe(0);
|
|
944
1177
|
});
|
|
945
1178
|
});
|
|
946
|
-
|
|
1179
|
+
describe5("reset filter form", () => {
|
|
947
1180
|
it4("resets streams matching a stream pattern", async () => {
|
|
948
1181
|
const tag = uid();
|
|
949
1182
|
const s1 = `reset-filter-${tag}-a`;
|
|
@@ -975,7 +1208,7 @@ var runStoreTck = (options) => {
|
|
|
975
1208
|
);
|
|
976
1209
|
await store.ack(mine.map((l) => ({ ...l, at: l.at + 100 })));
|
|
977
1210
|
const count = await store.reset({ stream: `^reset-filter-${tag}-` });
|
|
978
|
-
|
|
1211
|
+
expect5(count).toBe(2);
|
|
979
1212
|
const position_for = async (name) => {
|
|
980
1213
|
let at = null;
|
|
981
1214
|
await store.query_streams(
|
|
@@ -986,9 +1219,9 @@ var runStoreTck = (options) => {
|
|
|
986
1219
|
);
|
|
987
1220
|
return at;
|
|
988
1221
|
};
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1222
|
+
expect5(await position_for(s1)).toBe(-1);
|
|
1223
|
+
expect5(await position_for(s2)).toBe(-1);
|
|
1224
|
+
expect5(await position_for(other)).toBeGreaterThan(-1);
|
|
992
1225
|
});
|
|
993
1226
|
it4("filter form: resets only blocked streams when blocked:true", async () => {
|
|
994
1227
|
const tag = uid();
|
|
@@ -1013,10 +1246,10 @@ var runStoreTck = (options) => {
|
|
|
1013
1246
|
stream: `^reset-blocked-${tag}-`,
|
|
1014
1247
|
blocked: true
|
|
1015
1248
|
});
|
|
1016
|
-
|
|
1249
|
+
expect5(count).toBe(1);
|
|
1017
1250
|
});
|
|
1018
1251
|
});
|
|
1019
|
-
|
|
1252
|
+
describe5("prioritize", () => {
|
|
1020
1253
|
it4("sets priority directly, overriding subscribe's max() rule", async () => {
|
|
1021
1254
|
const tag = uid();
|
|
1022
1255
|
const s1 = `pri-${tag}-a`;
|
|
@@ -1029,7 +1262,7 @@ var runStoreTck = (options) => {
|
|
|
1029
1262
|
{ stream: s1, stream_exact: true },
|
|
1030
1263
|
3
|
|
1031
1264
|
);
|
|
1032
|
-
|
|
1265
|
+
expect5(updated).toBe(1);
|
|
1033
1266
|
const got1 = {};
|
|
1034
1267
|
const got2 = {};
|
|
1035
1268
|
await store.query_streams(
|
|
@@ -1039,11 +1272,11 @@ var runStoreTck = (options) => {
|
|
|
1039
1272
|
},
|
|
1040
1273
|
{ stream: `pri-${tag}-.*`, limit: 100 }
|
|
1041
1274
|
);
|
|
1042
|
-
|
|
1043
|
-
|
|
1275
|
+
expect5(got1.priority).toBe(3);
|
|
1276
|
+
expect5(got2.priority).toBe(5);
|
|
1044
1277
|
});
|
|
1045
1278
|
});
|
|
1046
|
-
|
|
1279
|
+
describe5("lanes", () => {
|
|
1047
1280
|
it4("subscribe defaults lane to 'default' when omitted", async () => {
|
|
1048
1281
|
const s = `lane-default-${uid()}`;
|
|
1049
1282
|
await store.subscribe([{ stream: s }]);
|
|
@@ -1052,7 +1285,7 @@ var runStoreTck = (options) => {
|
|
|
1052
1285
|
stream: s,
|
|
1053
1286
|
stream_exact: true
|
|
1054
1287
|
});
|
|
1055
|
-
|
|
1288
|
+
expect5(seen).toEqual(["default"]);
|
|
1056
1289
|
});
|
|
1057
1290
|
it4("subscribe records the lane passed in", async () => {
|
|
1058
1291
|
const s = `lane-set-${uid()}`;
|
|
@@ -1062,7 +1295,7 @@ var runStoreTck = (options) => {
|
|
|
1062
1295
|
stream: s,
|
|
1063
1296
|
stream_exact: true
|
|
1064
1297
|
});
|
|
1065
|
-
|
|
1298
|
+
expect5(seen).toEqual(["slow"]);
|
|
1066
1299
|
});
|
|
1067
1300
|
it4("subscribe re-lanes existing streams on subsequent calls", async () => {
|
|
1068
1301
|
const s = `lane-upsert-${uid()}`;
|
|
@@ -1073,7 +1306,7 @@ var runStoreTck = (options) => {
|
|
|
1073
1306
|
stream: s,
|
|
1074
1307
|
stream_exact: true
|
|
1075
1308
|
});
|
|
1076
|
-
|
|
1309
|
+
expect5(seen).toEqual(["fast"]);
|
|
1077
1310
|
});
|
|
1078
1311
|
it4("claim() filters by lane when supplied and returns lane on the Lease", async () => {
|
|
1079
1312
|
const tag = uid();
|
|
@@ -1099,13 +1332,13 @@ var runStoreTck = (options) => {
|
|
|
1099
1332
|
const slow_mine = slow.filter(
|
|
1100
1333
|
(l) => l.stream === sub_default || l.stream === sub_slow
|
|
1101
1334
|
);
|
|
1102
|
-
|
|
1103
|
-
|
|
1335
|
+
expect5(slow_mine.map((l) => l.stream)).toEqual([sub_slow]);
|
|
1336
|
+
expect5(slow_mine[0]?.lane).toBe("slow");
|
|
1104
1337
|
await store.ack(slow_mine.map((l) => ({ ...l, at: l.at + 1 })));
|
|
1105
1338
|
const all = await store.claim(50, 0, `w-all-${tag}`, 1e3);
|
|
1106
1339
|
const all_mine = all.filter((l) => l.stream === sub_default || l.stream === sub_slow).map((l) => ({ stream: l.stream, lane: l.lane }));
|
|
1107
|
-
|
|
1108
|
-
|
|
1340
|
+
expect5(all_mine).toEqual(
|
|
1341
|
+
expect5.arrayContaining([
|
|
1109
1342
|
{ stream: sub_default, lane: "default" },
|
|
1110
1343
|
{ stream: sub_slow, lane: "slow" }
|
|
1111
1344
|
])
|
|
@@ -1127,7 +1360,7 @@ var runStoreTck = (options) => {
|
|
|
1127
1360
|
stream: `lane-q-.*-${tag}`,
|
|
1128
1361
|
limit: 100
|
|
1129
1362
|
});
|
|
1130
|
-
|
|
1363
|
+
expect5(seen.sort()).toEqual([a, c]);
|
|
1131
1364
|
});
|
|
1132
1365
|
it4("prioritize filters by lane", async () => {
|
|
1133
1366
|
const tag = uid();
|
|
@@ -1138,14 +1371,14 @@ var runStoreTck = (options) => {
|
|
|
1138
1371
|
{ stream: b, lane: `pfast-${tag}` }
|
|
1139
1372
|
]);
|
|
1140
1373
|
const updated = await store.prioritize({ lane: `pslow-${tag}` }, 7);
|
|
1141
|
-
|
|
1374
|
+
expect5(updated).toBe(1);
|
|
1142
1375
|
const seen = /* @__PURE__ */ new Map();
|
|
1143
1376
|
await store.query_streams((p) => seen.set(p.stream, p.priority), {
|
|
1144
1377
|
stream: `lane-pri-.*-${tag}`,
|
|
1145
1378
|
limit: 100
|
|
1146
1379
|
});
|
|
1147
|
-
|
|
1148
|
-
|
|
1380
|
+
expect5(seen.get(a)).toBe(7);
|
|
1381
|
+
expect5(seen.get(b)).toBe(0);
|
|
1149
1382
|
});
|
|
1150
1383
|
it4("reset filters by lane", async () => {
|
|
1151
1384
|
const tag = uid();
|
|
@@ -1165,7 +1398,7 @@ var runStoreTck = (options) => {
|
|
|
1165
1398
|
const mine = leases.filter((l) => l.stream === a || l.stream === b);
|
|
1166
1399
|
await store.ack(mine.map((l) => ({ ...l, at: l.at + 1 })));
|
|
1167
1400
|
const count = await store.reset({ lane: `rslow-${tag}` });
|
|
1168
|
-
|
|
1401
|
+
expect5(count).toBe(1);
|
|
1169
1402
|
const ats = /* @__PURE__ */ new Map();
|
|
1170
1403
|
for (const name of [a, b]) {
|
|
1171
1404
|
await store.query_streams((p) => ats.set(p.stream, p.at), {
|
|
@@ -1173,8 +1406,8 @@ var runStoreTck = (options) => {
|
|
|
1173
1406
|
stream_exact: true
|
|
1174
1407
|
});
|
|
1175
1408
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1409
|
+
expect5(ats.get(a)).toBe(-1);
|
|
1410
|
+
expect5(ats.get(b)).toBeGreaterThanOrEqual(0);
|
|
1178
1411
|
});
|
|
1179
1412
|
it4("unblock filters by lane", async () => {
|
|
1180
1413
|
const tag = uid();
|
|
@@ -1194,7 +1427,7 @@ var runStoreTck = (options) => {
|
|
|
1194
1427
|
const mine = leases.filter((l) => l.stream === a || l.stream === b);
|
|
1195
1428
|
await store.block(mine.map((l) => ({ ...l, error: "boom" })));
|
|
1196
1429
|
const count = await store.unblock({ lane: `uslow-${tag}` });
|
|
1197
|
-
|
|
1430
|
+
expect5(count).toBe(1);
|
|
1198
1431
|
const blocked = /* @__PURE__ */ new Map();
|
|
1199
1432
|
for (const name of [a, b]) {
|
|
1200
1433
|
await store.query_streams((p) => blocked.set(p.stream, p.blocked), {
|
|
@@ -1202,11 +1435,11 @@ var runStoreTck = (options) => {
|
|
|
1202
1435
|
stream_exact: true
|
|
1203
1436
|
});
|
|
1204
1437
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1438
|
+
expect5(blocked.get(a)).toBe(false);
|
|
1439
|
+
expect5(blocked.get(b)).toBe(true);
|
|
1207
1440
|
});
|
|
1208
1441
|
});
|
|
1209
|
-
|
|
1442
|
+
describe5("truncate", () => {
|
|
1210
1443
|
it4("seeds a tombstone when no snapshot is provided", async () => {
|
|
1211
1444
|
const s = `trunc-tomb-${uid()}`;
|
|
1212
1445
|
await store.commit(
|
|
@@ -1215,7 +1448,7 @@ var runStoreTck = (options) => {
|
|
|
1215
1448
|
make_meta({ stream: s })
|
|
1216
1449
|
);
|
|
1217
1450
|
const result = await store.truncate([{ stream: s }]);
|
|
1218
|
-
|
|
1451
|
+
expect5(result.get(s)?.deleted).toBe(2);
|
|
1219
1452
|
const remaining = [];
|
|
1220
1453
|
await store.query(
|
|
1221
1454
|
(e) => {
|
|
@@ -1223,8 +1456,8 @@ var runStoreTck = (options) => {
|
|
|
1223
1456
|
},
|
|
1224
1457
|
{ stream: s, stream_exact: true }
|
|
1225
1458
|
);
|
|
1226
|
-
|
|
1227
|
-
|
|
1459
|
+
expect5(remaining).toHaveLength(1);
|
|
1460
|
+
expect5(remaining[0].name).toBe(
|
|
1228
1461
|
"__tombstone__"
|
|
1229
1462
|
);
|
|
1230
1463
|
});
|
|
@@ -1238,7 +1471,7 @@ var runStoreTck = (options) => {
|
|
|
1238
1471
|
const result = await store.truncate([
|
|
1239
1472
|
{ stream: s, snapshot: { count: 7 } }
|
|
1240
1473
|
]);
|
|
1241
|
-
|
|
1474
|
+
expect5(result.get(s)?.deleted).toBe(1);
|
|
1242
1475
|
const remaining = [];
|
|
1243
1476
|
await store.query(
|
|
1244
1477
|
(e) => {
|
|
@@ -1246,23 +1479,23 @@ var runStoreTck = (options) => {
|
|
|
1246
1479
|
},
|
|
1247
1480
|
{ stream: s, stream_exact: true, with_snaps: true }
|
|
1248
1481
|
);
|
|
1249
|
-
|
|
1250
|
-
|
|
1482
|
+
expect5(remaining).toHaveLength(1);
|
|
1483
|
+
expect5(remaining[0].name).toBe(
|
|
1251
1484
|
"__snapshot__"
|
|
1252
1485
|
);
|
|
1253
|
-
|
|
1486
|
+
expect5(remaining[0].data).toEqual({ count: 7 });
|
|
1254
1487
|
});
|
|
1255
1488
|
it4("returns an empty map for empty input", async () => {
|
|
1256
1489
|
const result = await store.truncate([]);
|
|
1257
|
-
|
|
1490
|
+
expect5(result.size).toBe(0);
|
|
1258
1491
|
});
|
|
1259
1492
|
it4("returns 0 deleted for streams that don't exist", async () => {
|
|
1260
1493
|
const s = `trunc-missing-${uid()}`;
|
|
1261
1494
|
const result = await store.truncate([{ stream: s }]);
|
|
1262
|
-
|
|
1495
|
+
expect5(result.get(s)?.deleted).toBe(0);
|
|
1263
1496
|
});
|
|
1264
1497
|
});
|
|
1265
|
-
|
|
1498
|
+
describe5("query_streams", () => {
|
|
1266
1499
|
it4("returns positions filtered by stream regex, exact, source, and source_exact", async () => {
|
|
1267
1500
|
const tag = uid();
|
|
1268
1501
|
const proj1 = `qs-${tag}-projection-tickets`;
|
|
@@ -1282,35 +1515,35 @@ var runStoreTck = (options) => {
|
|
|
1282
1515
|
(p) => all.push({ stream: p.stream, source: p.source }),
|
|
1283
1516
|
{ stream: `qs-${tag}-.*` }
|
|
1284
1517
|
);
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1518
|
+
expect5(all_result.count).toBe(4);
|
|
1519
|
+
expect5(all_result.maxEventId).toBeGreaterThanOrEqual(-1);
|
|
1520
|
+
expect5(all.map((p) => p.stream).sort()).toEqual(
|
|
1288
1521
|
[proj1, proj2, dyn1, dyn2].sort()
|
|
1289
1522
|
);
|
|
1290
1523
|
const projections = [];
|
|
1291
1524
|
await store.query_streams((p) => projections.push(p.stream), {
|
|
1292
1525
|
stream: `qs-${tag}-projection-.*`
|
|
1293
1526
|
});
|
|
1294
|
-
|
|
1527
|
+
expect5(projections.sort()).toEqual([proj1, proj2].sort());
|
|
1295
1528
|
const exact = [];
|
|
1296
1529
|
await store.query_streams((p) => exact.push(p.stream), {
|
|
1297
1530
|
stream: dyn1,
|
|
1298
1531
|
stream_exact: true
|
|
1299
1532
|
});
|
|
1300
|
-
|
|
1533
|
+
expect5(exact).toEqual([dyn1]);
|
|
1301
1534
|
const by_source = [];
|
|
1302
1535
|
await store.query_streams((p) => by_source.push(p.stream), {
|
|
1303
1536
|
stream: `qs-${tag}-.*`,
|
|
1304
1537
|
source: `qs-${tag}-src-.*`
|
|
1305
1538
|
});
|
|
1306
|
-
|
|
1539
|
+
expect5(by_source.sort()).toEqual([dyn1, dyn2].sort());
|
|
1307
1540
|
const exact_source = [];
|
|
1308
1541
|
await store.query_streams((p) => exact_source.push(p.stream), {
|
|
1309
1542
|
stream: `qs-${tag}-.*`,
|
|
1310
1543
|
source: src2,
|
|
1311
1544
|
source_exact: true
|
|
1312
1545
|
});
|
|
1313
|
-
|
|
1546
|
+
expect5(exact_source).toEqual([dyn2]);
|
|
1314
1547
|
});
|
|
1315
1548
|
it4("paginates with limit + after (keyset)", async () => {
|
|
1316
1549
|
const tag = uid();
|
|
@@ -1326,15 +1559,15 @@ var runStoreTck = (options) => {
|
|
|
1326
1559
|
stream: `qp-${tag}-.*`,
|
|
1327
1560
|
limit: 2
|
|
1328
1561
|
});
|
|
1329
|
-
|
|
1562
|
+
expect5(page1).toHaveLength(2);
|
|
1330
1563
|
const page2 = [];
|
|
1331
1564
|
await store.query_streams((p) => page2.push(p.stream), {
|
|
1332
1565
|
stream: `qp-${tag}-.*`,
|
|
1333
1566
|
limit: 2,
|
|
1334
1567
|
after: page1.at(-1)
|
|
1335
1568
|
});
|
|
1336
|
-
|
|
1337
|
-
|
|
1569
|
+
expect5(page2).toHaveLength(2);
|
|
1570
|
+
expect5([...page1, ...page2].sort()).toEqual([...streams].sort());
|
|
1338
1571
|
});
|
|
1339
1572
|
it4("filters by blocked status", async () => {
|
|
1340
1573
|
const tag = uid();
|
|
@@ -1356,17 +1589,17 @@ var runStoreTck = (options) => {
|
|
|
1356
1589
|
(p) => blocked.push({ stream: p.stream, error: p.error }),
|
|
1357
1590
|
{ stream: `qb-${tag}.*`, blocked: true }
|
|
1358
1591
|
);
|
|
1359
|
-
|
|
1360
|
-
|
|
1592
|
+
expect5(blocked).toHaveLength(1);
|
|
1593
|
+
expect5(blocked[0].error).toBe("boom");
|
|
1361
1594
|
const unblocked = [];
|
|
1362
1595
|
await store.query_streams((p) => unblocked.push(p.stream), {
|
|
1363
1596
|
stream: `qb-${tag}.*`,
|
|
1364
1597
|
blocked: false
|
|
1365
1598
|
});
|
|
1366
|
-
|
|
1599
|
+
expect5(unblocked).toEqual([sibling]);
|
|
1367
1600
|
});
|
|
1368
1601
|
});
|
|
1369
|
-
|
|
1602
|
+
describe5("query_stats", () => {
|
|
1370
1603
|
it4("array input \u2014 returns head per stream, absent when not in input", async () => {
|
|
1371
1604
|
const tag = uid();
|
|
1372
1605
|
const sA = `qst-${tag}-a`;
|
|
@@ -1388,16 +1621,16 @@ var runStoreTck = (options) => {
|
|
|
1388
1621
|
make_meta({ stream: sUnasked })
|
|
1389
1622
|
);
|
|
1390
1623
|
const stats = await store.query_stats([sA, sB]);
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1624
|
+
expect5(stats.size).toBe(2);
|
|
1625
|
+
expect5(stats.get(sA)?.head.name).toBe("Incremented");
|
|
1626
|
+
expect5((stats.get(sA)?.head.data).amount).toBe(2);
|
|
1627
|
+
expect5(stats.get(sB)?.head.name).toBe("Decremented");
|
|
1628
|
+
expect5((stats.get(sB)?.head.data).amount).toBe(5);
|
|
1629
|
+
expect5(stats.has(sUnasked)).toBe(false);
|
|
1397
1630
|
const empty = await store.query_stats([]);
|
|
1398
|
-
|
|
1631
|
+
expect5(empty.size).toBe(0);
|
|
1399
1632
|
const unknown = await store.query_stats([`qst-${tag}-missing`]);
|
|
1400
|
-
|
|
1633
|
+
expect5(unknown.size).toBe(0);
|
|
1401
1634
|
});
|
|
1402
1635
|
it4("tail returns the earliest event per stream", async () => {
|
|
1403
1636
|
const tag = uid();
|
|
@@ -1421,10 +1654,10 @@ var runStoreTck = (options) => {
|
|
|
1421
1654
|
tail: true
|
|
1422
1655
|
});
|
|
1423
1656
|
const r = stats.get(s);
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1657
|
+
expect5(r?.head.name).toBe("Incremented");
|
|
1658
|
+
expect5((r?.head.data).amount).toBe(3);
|
|
1659
|
+
expect5(r?.tail?.name).toBe("Incremented");
|
|
1660
|
+
expect5((r?.tail?.data).amount).toBe(1);
|
|
1428
1661
|
});
|
|
1429
1662
|
it4("count + names \u2014 full aggregates including framework markers", async () => {
|
|
1430
1663
|
const tag = uid();
|
|
@@ -1445,11 +1678,11 @@ var runStoreTck = (options) => {
|
|
|
1445
1678
|
names: true
|
|
1446
1679
|
});
|
|
1447
1680
|
const r = stats.get(s);
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1681
|
+
expect5(r?.count).toBe(4);
|
|
1682
|
+
expect5(r?.names?.[SNAP_EVENT]).toBe(1);
|
|
1683
|
+
expect5(r?.names?.Incremented).toBe(2);
|
|
1684
|
+
expect5(r?.names?.Decremented).toBe(1);
|
|
1685
|
+
expect5(r?.names?.[SNAP_EVENT]).toBe(1);
|
|
1453
1686
|
});
|
|
1454
1687
|
it4("exclude shifts head past filtered events; stream absent when all filtered", async () => {
|
|
1455
1688
|
const tag = uid();
|
|
@@ -1466,21 +1699,21 @@ var runStoreTck = (options) => {
|
|
|
1466
1699
|
make_meta({ stream: sAllOut })
|
|
1467
1700
|
);
|
|
1468
1701
|
const all = await store.query_stats([s]);
|
|
1469
|
-
|
|
1470
|
-
|
|
1702
|
+
expect5(all.get(s)?.head.name).toBe("Incremented");
|
|
1703
|
+
expect5((all.get(s)?.head.data).amount).toBe(3);
|
|
1471
1704
|
const excl = await store.query_stats([s], {
|
|
1472
1705
|
exclude: ["Incremented"]
|
|
1473
1706
|
});
|
|
1474
|
-
|
|
1475
|
-
|
|
1707
|
+
expect5(excl.get(s)?.head.name).toBe("Decremented");
|
|
1708
|
+
expect5((excl.get(s)?.head.data).amount).toBe(2);
|
|
1476
1709
|
const wipe = await store.query_stats([sAllOut], {
|
|
1477
1710
|
exclude: ["Incremented", "Decremented", "Reset"]
|
|
1478
1711
|
});
|
|
1479
|
-
|
|
1712
|
+
expect5(wipe.has(sAllOut)).toBe(false);
|
|
1480
1713
|
const no_tomb = await store.query_stats([s], {
|
|
1481
1714
|
exclude: [TOMBSTONE_EVENT]
|
|
1482
1715
|
});
|
|
1483
|
-
|
|
1716
|
+
expect5(no_tomb.get(s)?.head.name).toBe("Incremented");
|
|
1484
1717
|
});
|
|
1485
1718
|
it4("before \u2014 time travel narrows head/tail/count", async () => {
|
|
1486
1719
|
const tag = uid();
|
|
@@ -1507,13 +1740,13 @@ var runStoreTck = (options) => {
|
|
|
1507
1740
|
before
|
|
1508
1741
|
});
|
|
1509
1742
|
const r = stats.get(s);
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1743
|
+
expect5(r?.count).toBe(1);
|
|
1744
|
+
expect5(r?.head.id).toBe(c1[0].id);
|
|
1745
|
+
expect5(r?.tail?.id).toBe(c1[0].id);
|
|
1513
1746
|
const empty = await store.query_stats([s], {
|
|
1514
1747
|
before: 0
|
|
1515
1748
|
});
|
|
1516
|
-
|
|
1749
|
+
expect5(empty.has(s)).toBe(false);
|
|
1517
1750
|
});
|
|
1518
1751
|
it4("filter form \u2014 stream regex, stream_exact, empty {} match", async () => {
|
|
1519
1752
|
const tag = uid();
|
|
@@ -1538,16 +1771,16 @@ var runStoreTck = (options) => {
|
|
|
1538
1771
|
const orders = await store.query_stats({
|
|
1539
1772
|
stream: `^qsf-${tag}-orders-`
|
|
1540
1773
|
});
|
|
1541
|
-
|
|
1774
|
+
expect5([...orders.keys()].sort()).toEqual([sA, sB].sort());
|
|
1542
1775
|
const exact = await store.query_stats({
|
|
1543
1776
|
stream: sA,
|
|
1544
1777
|
stream_exact: true
|
|
1545
1778
|
});
|
|
1546
|
-
|
|
1779
|
+
expect5([...exact.keys()]).toEqual([sA]);
|
|
1547
1780
|
const all = await store.query_stats({
|
|
1548
1781
|
stream: `^qsf-${tag}-`
|
|
1549
1782
|
});
|
|
1550
|
-
|
|
1783
|
+
expect5([...all.keys()].sort()).toEqual([sA, sB, sOther].sort());
|
|
1551
1784
|
});
|
|
1552
1785
|
it4("compose with query_streams for subscription-level filters", async () => {
|
|
1553
1786
|
const tag = uid();
|
|
@@ -1566,7 +1799,7 @@ var runStoreTck = (options) => {
|
|
|
1566
1799
|
);
|
|
1567
1800
|
const leased = await store.claim(100, 0, `w-${uid()}`, 1e5);
|
|
1568
1801
|
const mine = leased.find((l) => l.stream === a);
|
|
1569
|
-
|
|
1802
|
+
expect5(mine).toBeDefined();
|
|
1570
1803
|
const others = leased.filter((l) => l.stream !== a);
|
|
1571
1804
|
await store.ack(others);
|
|
1572
1805
|
await store.block([{ ...mine, error: "boom" }]);
|
|
@@ -1575,10 +1808,10 @@ var runStoreTck = (options) => {
|
|
|
1575
1808
|
stream: `^qsc-${tag}-`,
|
|
1576
1809
|
blocked: true
|
|
1577
1810
|
});
|
|
1578
|
-
|
|
1811
|
+
expect5(blocked_names).toEqual([a]);
|
|
1579
1812
|
const stats = await store.query_stats(blocked_names);
|
|
1580
|
-
|
|
1581
|
-
|
|
1813
|
+
expect5(stats.get(a)?.head.name).toBe("Incremented");
|
|
1814
|
+
expect5(stats.has(b)).toBe(false);
|
|
1582
1815
|
});
|
|
1583
1816
|
it4("empty filter {} \u2014 matches every event-bearing stream", async () => {
|
|
1584
1817
|
const tag = uid();
|
|
@@ -1595,8 +1828,8 @@ var runStoreTck = (options) => {
|
|
|
1595
1828
|
make_meta({ stream: b })
|
|
1596
1829
|
);
|
|
1597
1830
|
const all = await store.query_stats({});
|
|
1598
|
-
|
|
1599
|
-
|
|
1831
|
+
expect5(all.has(a)).toBe(true);
|
|
1832
|
+
expect5(all.has(b)).toBe(true);
|
|
1600
1833
|
});
|
|
1601
1834
|
it4("stat-flag combinations \u2014 count-only, names-only, tail-only", async () => {
|
|
1602
1835
|
const tag = uid();
|
|
@@ -1609,23 +1842,23 @@ var runStoreTck = (options) => {
|
|
|
1609
1842
|
const c = await store.query_stats([s], {
|
|
1610
1843
|
count: true
|
|
1611
1844
|
});
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1845
|
+
expect5(c.get(s)?.count).toBe(3);
|
|
1846
|
+
expect5(c.get(s)?.names).toBeUndefined();
|
|
1847
|
+
expect5(c.get(s)?.tail).toBeUndefined();
|
|
1615
1848
|
const n = await store.query_stats([s], {
|
|
1616
1849
|
names: true
|
|
1617
1850
|
});
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1851
|
+
expect5(n.get(s)?.names).toEqual({ Incremented: 2, Decremented: 1 });
|
|
1852
|
+
expect5(n.get(s)?.count).toBeUndefined();
|
|
1853
|
+
expect5(n.get(s)?.tail).toBeUndefined();
|
|
1621
1854
|
const t = await store.query_stats([s], { tail: true });
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1855
|
+
expect5(t.get(s)?.tail?.name).toBe("Incremented");
|
|
1856
|
+
expect5((t.get(s)?.tail?.data).amount).toBe(1);
|
|
1857
|
+
expect5(t.get(s)?.count).toBeUndefined();
|
|
1858
|
+
expect5(t.get(s)?.names).toBeUndefined();
|
|
1626
1859
|
});
|
|
1627
1860
|
});
|
|
1628
|
-
|
|
1861
|
+
describe5("query_streams anchor contract", () => {
|
|
1629
1862
|
it4("plain regex without anchors is a substring match", async () => {
|
|
1630
1863
|
const tag = uid();
|
|
1631
1864
|
const inner = `qsr-${tag}-inner`;
|
|
@@ -1640,7 +1873,7 @@ var runStoreTck = (options) => {
|
|
|
1640
1873
|
await store.query_streams((p) => seen.push(p.stream), {
|
|
1641
1874
|
stream: `qsr-${tag}-inner`
|
|
1642
1875
|
});
|
|
1643
|
-
|
|
1876
|
+
expect5(seen.sort()).toEqual([inner, longer].sort());
|
|
1644
1877
|
});
|
|
1645
1878
|
it4("caller-anchored `^name$` matches only the whole string", async () => {
|
|
1646
1879
|
const tag = uid();
|
|
@@ -1651,7 +1884,7 @@ var runStoreTck = (options) => {
|
|
|
1651
1884
|
await store.query_streams((p) => seen.push(p.stream), {
|
|
1652
1885
|
stream: `^qsr-${tag}-anchor$`
|
|
1653
1886
|
});
|
|
1654
|
-
|
|
1887
|
+
expect5(seen).toEqual([inner]);
|
|
1655
1888
|
});
|
|
1656
1889
|
it4("caller-anchored `^prefix` matches by prefix", async () => {
|
|
1657
1890
|
const tag = uid();
|
|
@@ -1667,10 +1900,10 @@ var runStoreTck = (options) => {
|
|
|
1667
1900
|
await store.query_streams((p) => seen.push(p.stream), {
|
|
1668
1901
|
stream: `^qsr-${tag}-pfx-`
|
|
1669
1902
|
});
|
|
1670
|
-
|
|
1903
|
+
expect5(seen.sort()).toEqual([a, b].sort());
|
|
1671
1904
|
});
|
|
1672
1905
|
});
|
|
1673
|
-
|
|
1906
|
+
describe5("prioritize anchor contract", () => {
|
|
1674
1907
|
it4("caller-anchored `^name$` filter matches only the whole string", async () => {
|
|
1675
1908
|
const tag = uid();
|
|
1676
1909
|
const inner = `pr-${tag}-anchor`;
|
|
@@ -1683,16 +1916,16 @@ var runStoreTck = (options) => {
|
|
|
1683
1916
|
{ stream: `^pr-${tag}-anchor$` },
|
|
1684
1917
|
7
|
|
1685
1918
|
);
|
|
1686
|
-
|
|
1919
|
+
expect5(updated).toBe(1);
|
|
1687
1920
|
const seen = /* @__PURE__ */ new Map();
|
|
1688
1921
|
await store.query_streams((p) => seen.set(p.stream, p.priority), {
|
|
1689
1922
|
stream: `pr-${tag}-anchor`
|
|
1690
1923
|
});
|
|
1691
|
-
|
|
1692
|
-
|
|
1924
|
+
expect5(seen.get(inner)).toBe(7);
|
|
1925
|
+
expect5(seen.get(longer)).toBe(0);
|
|
1693
1926
|
});
|
|
1694
1927
|
});
|
|
1695
|
-
|
|
1928
|
+
describe5("query_streams head", () => {
|
|
1696
1929
|
it4("maxEventId tracks the highest committed id", async () => {
|
|
1697
1930
|
const s = `head-${uid()}`;
|
|
1698
1931
|
await store.subscribe([{ stream: s }]);
|
|
@@ -1706,34 +1939,34 @@ var runStoreTck = (options) => {
|
|
|
1706
1939
|
(p) => positions.push(p.stream),
|
|
1707
1940
|
{ stream: s, stream_exact: true, limit: 1 }
|
|
1708
1941
|
);
|
|
1709
|
-
|
|
1710
|
-
|
|
1942
|
+
expect5(maxEventId).toBeGreaterThanOrEqual(0);
|
|
1943
|
+
expect5(positions).toEqual([s]);
|
|
1711
1944
|
});
|
|
1712
1945
|
});
|
|
1713
|
-
|
|
1946
|
+
describe5("seed_stream helper coverage", () => {
|
|
1714
1947
|
it4("commits N events with monotonically increasing ids", async () => {
|
|
1715
1948
|
const s = `seed-${uid()}`;
|
|
1716
1949
|
const committed = await seed_stream(store, s, 3);
|
|
1717
|
-
|
|
1950
|
+
expect5(committed).toHaveLength(3);
|
|
1718
1951
|
for (let i = 1; i < committed.length; i++) {
|
|
1719
|
-
|
|
1952
|
+
expect5(committed[i].id).toBeGreaterThan(committed[i - 1].id);
|
|
1720
1953
|
}
|
|
1721
1954
|
});
|
|
1722
1955
|
});
|
|
1723
|
-
|
|
1956
|
+
describe5.skipIf(!caps.restore)("restore (capability)", () => {
|
|
1724
1957
|
beforeEach(async () => {
|
|
1725
1958
|
await store.drop();
|
|
1726
1959
|
await store.seed();
|
|
1727
1960
|
});
|
|
1728
|
-
const as_source = (
|
|
1961
|
+
const as_source = (events2) => ({
|
|
1729
1962
|
async query(callback) {
|
|
1730
|
-
for (const e of
|
|
1963
|
+
for (const e of events2)
|
|
1731
1964
|
await Promise.resolve(
|
|
1732
1965
|
callback(
|
|
1733
1966
|
e
|
|
1734
1967
|
)
|
|
1735
1968
|
);
|
|
1736
|
-
return
|
|
1969
|
+
return events2.length;
|
|
1737
1970
|
},
|
|
1738
1971
|
async dispose() {
|
|
1739
1972
|
}
|
|
@@ -1759,27 +1992,27 @@ var runStoreTck = (options) => {
|
|
|
1759
1992
|
};
|
|
1760
1993
|
it4("returns kept=0 on an empty source", async () => {
|
|
1761
1994
|
const result = await restore(as_source([]));
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1995
|
+
expect5(result.kept).toBe(0);
|
|
1996
|
+
expect5(result.duration_ms).toBeGreaterThanOrEqual(0);
|
|
1997
|
+
expect5(result.dropped).toEqual({
|
|
1765
1998
|
closed_streams: 0,
|
|
1766
1999
|
snapshots: 0
|
|
1767
2000
|
});
|
|
1768
|
-
const
|
|
1769
|
-
|
|
2001
|
+
const events2 = await collect(store, { limit: 10 });
|
|
2002
|
+
expect5(events2).toHaveLength(0);
|
|
1770
2003
|
});
|
|
1771
2004
|
it4("rebuilds a single stream and preserves `created` verbatim", async () => {
|
|
1772
2005
|
const s = `restore-single-${uid()}`;
|
|
1773
2006
|
const t0 = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
|
|
1774
2007
|
const t1 = /* @__PURE__ */ new Date("2020-01-02T00:00:00.000Z");
|
|
1775
2008
|
const t2 = /* @__PURE__ */ new Date("2020-01-03T00:00:00.000Z");
|
|
1776
|
-
const
|
|
2009
|
+
const events2 = [
|
|
1777
2010
|
event(1, s, 0, "Incremented", t0, { amount: 1 }),
|
|
1778
2011
|
event(2, s, 1, "Incremented", t1, { amount: 2 }),
|
|
1779
2012
|
event(3, s, 2, "Decremented", t2, { amount: 1 })
|
|
1780
2013
|
];
|
|
1781
|
-
const result = await restore(as_source(
|
|
1782
|
-
|
|
2014
|
+
const result = await restore(as_source(events2));
|
|
2015
|
+
expect5(result.kept).toBe(3);
|
|
1783
2016
|
const back = [];
|
|
1784
2017
|
await store.query(
|
|
1785
2018
|
(e) => {
|
|
@@ -1787,8 +2020,8 @@ var runStoreTck = (options) => {
|
|
|
1787
2020
|
},
|
|
1788
2021
|
{ stream: s, stream_exact: true }
|
|
1789
2022
|
);
|
|
1790
|
-
|
|
1791
|
-
|
|
2023
|
+
expect5(back).toHaveLength(3);
|
|
2024
|
+
expect5(
|
|
1792
2025
|
back.map((e) => ({
|
|
1793
2026
|
stream: e.stream,
|
|
1794
2027
|
version: e.version,
|
|
@@ -1824,14 +2057,14 @@ var runStoreTck = (options) => {
|
|
|
1824
2057
|
const a = `restore-multi-a-${uid()}`;
|
|
1825
2058
|
const b = `restore-multi-b-${uid()}`;
|
|
1826
2059
|
const t = /* @__PURE__ */ new Date("2020-06-01T00:00:00.000Z");
|
|
1827
|
-
const
|
|
2060
|
+
const events2 = [
|
|
1828
2061
|
event(1, a, 0, "Incremented", t, { amount: 10 }),
|
|
1829
2062
|
event(2, b, 0, "Incremented", t, { amount: 20 }),
|
|
1830
2063
|
event(3, a, 1, "Decremented", t, { amount: 5 }),
|
|
1831
2064
|
event(4, b, 1, "Incremented", t, { amount: 30 })
|
|
1832
2065
|
];
|
|
1833
|
-
const result = await restore(as_source(
|
|
1834
|
-
|
|
2066
|
+
const result = await restore(as_source(events2));
|
|
2067
|
+
expect5(result.kept).toBe(4);
|
|
1835
2068
|
const aBack = [];
|
|
1836
2069
|
const bBack = [];
|
|
1837
2070
|
await store.query(
|
|
@@ -1846,8 +2079,8 @@ var runStoreTck = (options) => {
|
|
|
1846
2079
|
},
|
|
1847
2080
|
{ stream: b, stream_exact: true }
|
|
1848
2081
|
);
|
|
1849
|
-
|
|
1850
|
-
|
|
2082
|
+
expect5(aBack.map((e) => e.version)).toEqual([0, 1]);
|
|
2083
|
+
expect5(bBack.map((e) => e.version)).toEqual([0, 1]);
|
|
1851
2084
|
});
|
|
1852
2085
|
it4("preserves Date `created` verbatim", async () => {
|
|
1853
2086
|
const s = `restore-isoc-${uid()}`;
|
|
@@ -1872,8 +2105,8 @@ var runStoreTck = (options) => {
|
|
|
1872
2105
|
},
|
|
1873
2106
|
{ stream: s, stream_exact: true }
|
|
1874
2107
|
);
|
|
1875
|
-
|
|
1876
|
-
|
|
2108
|
+
expect5(back).toHaveLength(1);
|
|
2109
|
+
expect5(back[0].created.toISOString()).toBe(iso);
|
|
1877
2110
|
});
|
|
1878
2111
|
it4("wipes pre-existing events before inserting", async () => {
|
|
1879
2112
|
const old = `restore-old-${uid()}`;
|
|
@@ -1891,12 +2124,12 @@ var runStoreTck = (options) => {
|
|
|
1891
2124
|
stream: old,
|
|
1892
2125
|
stream_exact: true
|
|
1893
2126
|
});
|
|
1894
|
-
|
|
2127
|
+
expect5(old_back).toHaveLength(0);
|
|
1895
2128
|
const fresh_back = await collect(store, {
|
|
1896
2129
|
stream: fresh,
|
|
1897
2130
|
stream_exact: true
|
|
1898
2131
|
});
|
|
1899
|
-
|
|
2132
|
+
expect5(fresh_back).toHaveLength(1);
|
|
1900
2133
|
});
|
|
1901
2134
|
it4("clears subscription/stream-position metadata", async () => {
|
|
1902
2135
|
const sub = `restore-sub-${uid()}`;
|
|
@@ -1909,10 +2142,10 @@ var runStoreTck = (options) => {
|
|
|
1909
2142
|
return out;
|
|
1910
2143
|
};
|
|
1911
2144
|
const before = await collect_streams();
|
|
1912
|
-
|
|
2145
|
+
expect5(before.includes(sub)).toBe(true);
|
|
1913
2146
|
await restore(as_source([]));
|
|
1914
2147
|
const after = await collect_streams();
|
|
1915
|
-
|
|
2148
|
+
expect5(after.includes(sub)).toBe(false);
|
|
1916
2149
|
});
|
|
1917
2150
|
it4("preserves snapshot events through restore", async () => {
|
|
1918
2151
|
const s = `restore-snap-${uid()}`;
|
|
@@ -1935,13 +2168,13 @@ var runStoreTck = (options) => {
|
|
|
1935
2168
|
stream_exact: true,
|
|
1936
2169
|
with_snaps: true
|
|
1937
2170
|
});
|
|
1938
|
-
|
|
1939
|
-
|
|
2171
|
+
expect5(back).toHaveLength(1);
|
|
2172
|
+
expect5(back[0].name).toBe(SNAP_EVENT);
|
|
1940
2173
|
});
|
|
1941
2174
|
it4("rewrites causation refs through the old\u2192new id map", async () => {
|
|
1942
2175
|
const s = `restore-caus-${uid()}`;
|
|
1943
2176
|
const t = /* @__PURE__ */ new Date("2020-08-01T00:00:00.000Z");
|
|
1944
|
-
const
|
|
2177
|
+
const events2 = [
|
|
1945
2178
|
{
|
|
1946
2179
|
id: 5,
|
|
1947
2180
|
stream: s,
|
|
@@ -1980,7 +2213,7 @@ var runStoreTck = (options) => {
|
|
|
1980
2213
|
}
|
|
1981
2214
|
}
|
|
1982
2215
|
];
|
|
1983
|
-
await restore(as_source(
|
|
2216
|
+
await restore(as_source(events2));
|
|
1984
2217
|
const back = [];
|
|
1985
2218
|
await store.query(
|
|
1986
2219
|
(e) => {
|
|
@@ -1988,10 +2221,10 @@ var runStoreTck = (options) => {
|
|
|
1988
2221
|
},
|
|
1989
2222
|
{ stream: s, stream_exact: true }
|
|
1990
2223
|
);
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2224
|
+
expect5(back).toHaveLength(3);
|
|
2225
|
+
expect5(back[0].meta.causation.event).toBeUndefined();
|
|
2226
|
+
expect5(back[1].meta.causation.event?.id).toBe(back[0].id);
|
|
2227
|
+
expect5(back[2].meta.causation.event?.id).toBe(back[1].id);
|
|
1995
2228
|
});
|
|
1996
2229
|
it4("leaves causation refs unmapped when the target isn't in the source", async () => {
|
|
1997
2230
|
const s = `restore-orphan-${uid()}`;
|
|
@@ -2021,7 +2254,7 @@ var runStoreTck = (options) => {
|
|
|
2021
2254
|
},
|
|
2022
2255
|
{ stream: s, stream_exact: true }
|
|
2023
2256
|
);
|
|
2024
|
-
|
|
2257
|
+
expect5(back[0].meta.causation.event?.id).toBe(999);
|
|
2025
2258
|
});
|
|
2026
2259
|
it4("rolls back atomically when the source throws mid-iteration", async () => {
|
|
2027
2260
|
const original = `restore-pre-${uid()}`;
|
|
@@ -2049,7 +2282,7 @@ var runStoreTck = (options) => {
|
|
|
2049
2282
|
async dispose() {
|
|
2050
2283
|
}
|
|
2051
2284
|
};
|
|
2052
|
-
await
|
|
2285
|
+
await expect5(restore(explosive)).rejects.toThrow("boom");
|
|
2053
2286
|
const back = [];
|
|
2054
2287
|
await store.query(
|
|
2055
2288
|
(e) => {
|
|
@@ -2057,8 +2290,8 @@ var runStoreTck = (options) => {
|
|
|
2057
2290
|
},
|
|
2058
2291
|
{ stream: original, stream_exact: true }
|
|
2059
2292
|
);
|
|
2060
|
-
|
|
2061
|
-
|
|
2293
|
+
expect5(back).toHaveLength(3);
|
|
2294
|
+
expect5(back.map((e) => e.id)).toEqual(committed.map((c) => c.id));
|
|
2062
2295
|
});
|
|
2063
2296
|
it4("drop_snapshots: skips SNAP_EVENT rows and counts them", async () => {
|
|
2064
2297
|
const s = `restore-drop-snap-${uid()}`;
|
|
@@ -2079,15 +2312,15 @@ var runStoreTck = (options) => {
|
|
|
2079
2312
|
]),
|
|
2080
2313
|
{ drop_snapshots: true }
|
|
2081
2314
|
);
|
|
2082
|
-
|
|
2083
|
-
|
|
2315
|
+
expect5(result.kept).toBe(2);
|
|
2316
|
+
expect5(result.dropped.snapshots).toBe(1);
|
|
2084
2317
|
const back = await collect(store, {
|
|
2085
2318
|
stream: s,
|
|
2086
2319
|
stream_exact: true,
|
|
2087
2320
|
with_snaps: true
|
|
2088
2321
|
});
|
|
2089
|
-
|
|
2090
|
-
|
|
2322
|
+
expect5(back).toHaveLength(2);
|
|
2323
|
+
expect5(
|
|
2091
2324
|
back.every((e) => e.name !== SNAP_EVENT)
|
|
2092
2325
|
).toBe(true);
|
|
2093
2326
|
});
|
|
@@ -2102,10 +2335,10 @@ var runStoreTck = (options) => {
|
|
|
2102
2335
|
]),
|
|
2103
2336
|
{ on_progress: (p) => calls.push(p.processed) }
|
|
2104
2337
|
);
|
|
2105
|
-
|
|
2338
|
+
expect5(calls).toEqual([1, 2]);
|
|
2106
2339
|
});
|
|
2107
2340
|
});
|
|
2108
|
-
|
|
2341
|
+
describe5.skipIf(!caps.pii_isolation)("pii_isolation (capability)", () => {
|
|
2109
2342
|
it4("commits and loads pii alongside data", async () => {
|
|
2110
2343
|
const s = `pii-roundtrip-${uid()}`;
|
|
2111
2344
|
const committed = await store.commit(
|
|
@@ -2119,8 +2352,8 @@ var runStoreTck = (options) => {
|
|
|
2119
2352
|
],
|
|
2120
2353
|
make_meta({ stream: s })
|
|
2121
2354
|
);
|
|
2122
|
-
|
|
2123
|
-
|
|
2355
|
+
expect5(committed).toHaveLength(1);
|
|
2356
|
+
expect5(committed[0].pii).toEqual({
|
|
2124
2357
|
email: "u@example.com",
|
|
2125
2358
|
name: "Ursula"
|
|
2126
2359
|
});
|
|
@@ -2131,9 +2364,9 @@ var runStoreTck = (options) => {
|
|
|
2131
2364
|
},
|
|
2132
2365
|
{ stream: s, stream_exact: true }
|
|
2133
2366
|
);
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2367
|
+
expect5(seen).toHaveLength(1);
|
|
2368
|
+
expect5(seen[0].pii).toEqual({ email: "u@example.com", name: "Ursula" });
|
|
2369
|
+
expect5(seen[0].data).toEqual({ amount: 1 });
|
|
2137
2370
|
});
|
|
2138
2371
|
it4("passes through events without pii (pii is null or undefined on load)", async () => {
|
|
2139
2372
|
const s = `pii-none-${uid()}`;
|
|
@@ -2149,8 +2382,8 @@ var runStoreTck = (options) => {
|
|
|
2149
2382
|
},
|
|
2150
2383
|
{ stream: s, stream_exact: true }
|
|
2151
2384
|
);
|
|
2152
|
-
|
|
2153
|
-
|
|
2385
|
+
expect5(seen).toHaveLength(1);
|
|
2386
|
+
expect5(seen[0].pii == null).toBe(true);
|
|
2154
2387
|
});
|
|
2155
2388
|
it4("wipes pii for every event on the stream via forget_pii", async () => {
|
|
2156
2389
|
const s = `pii-forget-${uid()}`;
|
|
@@ -2171,9 +2404,9 @@ var runStoreTck = (options) => {
|
|
|
2171
2404
|
make_meta({ stream: s })
|
|
2172
2405
|
);
|
|
2173
2406
|
const forget = store.forget_pii;
|
|
2174
|
-
|
|
2407
|
+
expect5(forget).toBeDefined();
|
|
2175
2408
|
const wiped = await forget.call(store, s);
|
|
2176
|
-
|
|
2409
|
+
expect5(wiped).toBe(2);
|
|
2177
2410
|
const seen = [];
|
|
2178
2411
|
await store.query(
|
|
2179
2412
|
(e) => {
|
|
@@ -2181,10 +2414,10 @@ var runStoreTck = (options) => {
|
|
|
2181
2414
|
},
|
|
2182
2415
|
{ stream: s, stream_exact: true }
|
|
2183
2416
|
);
|
|
2184
|
-
|
|
2417
|
+
expect5(seen).toHaveLength(2);
|
|
2185
2418
|
for (const e of seen) {
|
|
2186
|
-
|
|
2187
|
-
|
|
2419
|
+
expect5(e.pii == null).toBe(true);
|
|
2420
|
+
expect5(e.data).toBeDefined();
|
|
2188
2421
|
}
|
|
2189
2422
|
});
|
|
2190
2423
|
it4("is idempotent \u2014 second forget_pii returns 0, no error", async () => {
|
|
@@ -2202,9 +2435,9 @@ var runStoreTck = (options) => {
|
|
|
2202
2435
|
);
|
|
2203
2436
|
const forget = store.forget_pii;
|
|
2204
2437
|
const first = await forget.call(store, s);
|
|
2205
|
-
|
|
2438
|
+
expect5(first).toBe(1);
|
|
2206
2439
|
const second = await forget.call(store, s);
|
|
2207
|
-
|
|
2440
|
+
expect5(second).toBe(0);
|
|
2208
2441
|
});
|
|
2209
2442
|
it4("only wipes the targeted stream \u2014 siblings untouched", async () => {
|
|
2210
2443
|
const sA = `pii-iso-a-${uid()}`;
|
|
@@ -2239,7 +2472,7 @@ var runStoreTck = (options) => {
|
|
|
2239
2472
|
},
|
|
2240
2473
|
{ stream: sA, stream_exact: true }
|
|
2241
2474
|
);
|
|
2242
|
-
|
|
2475
|
+
expect5(a[0].pii == null).toBe(true);
|
|
2243
2476
|
const b = [];
|
|
2244
2477
|
await store.query(
|
|
2245
2478
|
(e) => {
|
|
@@ -2247,7 +2480,7 @@ var runStoreTck = (options) => {
|
|
|
2247
2480
|
},
|
|
2248
2481
|
{ stream: sB, stream_exact: true }
|
|
2249
2482
|
);
|
|
2250
|
-
|
|
2483
|
+
expect5(b[0].pii).toEqual({ email: "bob@example.com" });
|
|
2251
2484
|
});
|
|
2252
2485
|
it4("forget_pii on a stream with no pii events returns 0", async () => {
|
|
2253
2486
|
const s = `pii-forget-empty-${uid()}`;
|
|
@@ -2257,14 +2490,14 @@ var runStoreTck = (options) => {
|
|
|
2257
2490
|
make_meta({ stream: s })
|
|
2258
2491
|
);
|
|
2259
2492
|
const wiped = await store.forget_pii.call(store, s);
|
|
2260
|
-
|
|
2493
|
+
expect5(wiped).toBe(0);
|
|
2261
2494
|
});
|
|
2262
2495
|
});
|
|
2263
2496
|
if (caps.notify) {
|
|
2264
|
-
|
|
2497
|
+
describe5("notify (capability)", () => {
|
|
2265
2498
|
it4("delivers a notification when a different instance commits", async () => {
|
|
2266
2499
|
const notify = store.notify;
|
|
2267
|
-
|
|
2500
|
+
expect5(notify).toBeDefined();
|
|
2268
2501
|
const received = [];
|
|
2269
2502
|
let resolve_arrived;
|
|
2270
2503
|
const arrived = new Promise((res) => {
|
|
@@ -2283,9 +2516,9 @@ var runStoreTck = (options) => {
|
|
|
2283
2516
|
make_meta({ stream })
|
|
2284
2517
|
);
|
|
2285
2518
|
await arrived;
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2519
|
+
expect5(received.length).toBeGreaterThanOrEqual(1);
|
|
2520
|
+
expect5(received[0].stream).toBe(stream);
|
|
2521
|
+
expect5(received[0].events.length).toBeGreaterThanOrEqual(1);
|
|
2289
2522
|
} finally {
|
|
2290
2523
|
await writer.dispose();
|
|
2291
2524
|
await Promise.resolve(disposer());
|
|
@@ -2309,6 +2542,7 @@ export {
|
|
|
2309
2542
|
runCacheTck,
|
|
2310
2543
|
runLoggerTck,
|
|
2311
2544
|
runStabilityTck,
|
|
2545
|
+
runStorePropertyTck,
|
|
2312
2546
|
runStoreTck,
|
|
2313
2547
|
uid
|
|
2314
2548
|
};
|