@dudousxd/nestjs-durable-testing 0.17.0 → 0.19.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/index.d.ts +16 -1
- package/dist/index.js +1007 -327
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -187,7 +187,7 @@ async function assertOutput(store, runId, expected) {
|
|
|
187
187
|
}
|
|
188
188
|
__name(assertOutput, "assertOutput");
|
|
189
189
|
async function recordedSteps(store, runId) {
|
|
190
|
-
return (await store.listCheckpoints(runId)).map((
|
|
190
|
+
return (await store.listCheckpoints(runId)).map((cp2) => cp2.name);
|
|
191
191
|
}
|
|
192
192
|
__name(recordedSteps, "recordedSteps");
|
|
193
193
|
async function assertStepsRan(store, runId, names) {
|
|
@@ -200,10 +200,10 @@ async function assertStepsRan(store, runId, names) {
|
|
|
200
200
|
}
|
|
201
201
|
__name(assertStepsRan, "assertStepsRan");
|
|
202
202
|
async function assertStepAttempts(store, runId, name, attempts) {
|
|
203
|
-
const
|
|
204
|
-
if (!
|
|
205
|
-
if (
|
|
206
|
-
throw new Error(`expected step "${name}" to take ${attempts} attempt(s), took ${
|
|
203
|
+
const cp2 = (await store.listCheckpoints(runId)).find((c) => c.name === name);
|
|
204
|
+
if (!cp2) throw new Error(`expected step "${name}" to have run, but it did not`);
|
|
205
|
+
if (cp2.attempts !== attempts) {
|
|
206
|
+
throw new Error(`expected step "${name}" to take ${attempts} attempt(s), took ${cp2.attempts}`);
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
__name(assertStepAttempts, "assertStepAttempts");
|
|
@@ -341,11 +341,11 @@ var MutableClock = class {
|
|
|
341
341
|
this.current = current;
|
|
342
342
|
}
|
|
343
343
|
now = /* @__PURE__ */ __name(() => this.current, "now");
|
|
344
|
-
set(
|
|
345
|
-
this.current =
|
|
344
|
+
set(ms2) {
|
|
345
|
+
this.current = ms2;
|
|
346
346
|
}
|
|
347
|
-
advance(
|
|
348
|
-
this.current +=
|
|
347
|
+
advance(ms2) {
|
|
348
|
+
this.current += ms2;
|
|
349
349
|
}
|
|
350
350
|
};
|
|
351
351
|
function createTestEngine() {
|
|
@@ -362,8 +362,8 @@ function createTestEngine() {
|
|
|
362
362
|
store,
|
|
363
363
|
transport,
|
|
364
364
|
clock,
|
|
365
|
-
async tick(
|
|
366
|
-
clock.advance(
|
|
365
|
+
async tick(ms2) {
|
|
366
|
+
clock.advance(ms2);
|
|
367
367
|
return engine.resumeDueTimers(clock.now());
|
|
368
368
|
},
|
|
369
369
|
async run(workflow, input, runId, opts) {
|
|
@@ -384,7 +384,7 @@ async function assertReplayable(register, history) {
|
|
|
384
384
|
lockedBy: void 0,
|
|
385
385
|
lockedUntil: void 0
|
|
386
386
|
});
|
|
387
|
-
for (const
|
|
387
|
+
for (const cp2 of history.checkpoints) await store.saveCheckpoint(cp2);
|
|
388
388
|
const engine = new WorkflowEngine2({
|
|
389
389
|
store
|
|
390
390
|
});
|
|
@@ -400,7 +400,352 @@ __name(assertReplayable, "assertReplayable");
|
|
|
400
400
|
|
|
401
401
|
// src/state-store-conformance.ts
|
|
402
402
|
import { SINGLETON_ADMITTED_TAG, WorkflowEngine as WorkflowEngine3 } from "@dudousxd/nestjs-durable-core";
|
|
403
|
-
import { beforeEach, describe as describe2, expect as
|
|
403
|
+
import { beforeEach, describe as describe2, expect as expect3, it as it2 } from "vitest";
|
|
404
|
+
|
|
405
|
+
// src/checkpoint-aggregation-contract.ts
|
|
406
|
+
import { SUMMARY_CHILD_LIMIT, aggregatesCheckpoints, checkpointStatsOf, childIdsOf, pageFan, pageFanFromStore, queryCheckpointsOf, summarizeRunFromStore, summarizeTimeline } from "@dudousxd/nestjs-durable-core";
|
|
407
|
+
import { expect as expect2 } from "vitest";
|
|
408
|
+
var base = Date.parse("2026-06-11T00:00:00.000Z");
|
|
409
|
+
var ms = /* @__PURE__ */ __name((n) => new Date(base + n), "ms");
|
|
410
|
+
function cp(seq, over = {}) {
|
|
411
|
+
return {
|
|
412
|
+
runId: "r1",
|
|
413
|
+
seq,
|
|
414
|
+
name: "Lines.process",
|
|
415
|
+
kind: "remote",
|
|
416
|
+
stepId: `r1:${seq}`,
|
|
417
|
+
status: "completed",
|
|
418
|
+
output: {
|
|
419
|
+
n: seq
|
|
420
|
+
},
|
|
421
|
+
attempts: 1,
|
|
422
|
+
enqueuedAt: ms(seq * 10),
|
|
423
|
+
startedAt: ms(seq * 10 + 3),
|
|
424
|
+
finishedAt: ms(seq * 10 + 7),
|
|
425
|
+
...over
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
__name(cp, "cp");
|
|
429
|
+
var child = /* @__PURE__ */ __name((seq, group, over = {}) => cp(seq, {
|
|
430
|
+
name: `signal:child:r1.all.${group.split(":")[1]}.${seq}`,
|
|
431
|
+
kind: "signal",
|
|
432
|
+
parallelGroup: group,
|
|
433
|
+
output: {
|
|
434
|
+
ok: true,
|
|
435
|
+
value: seq
|
|
436
|
+
},
|
|
437
|
+
...over
|
|
438
|
+
}), "child");
|
|
439
|
+
var folded = /* @__PURE__ */ __name((seq, group, wave, from, n) => cp(seq, {
|
|
440
|
+
name: `fan:r1.all.${group.split(":")[1]}.w${wave}.`,
|
|
441
|
+
kind: "signal",
|
|
442
|
+
parallelGroup: group,
|
|
443
|
+
output: {
|
|
444
|
+
from,
|
|
445
|
+
values: Array.from({
|
|
446
|
+
length: n
|
|
447
|
+
}, (_, i) => i)
|
|
448
|
+
}
|
|
449
|
+
}), "folded");
|
|
450
|
+
function aggregationFixture() {
|
|
451
|
+
const rows = [
|
|
452
|
+
cp(0, {
|
|
453
|
+
name: "Lines.load",
|
|
454
|
+
kind: "local"
|
|
455
|
+
})
|
|
456
|
+
];
|
|
457
|
+
rows.push(cp(1, {
|
|
458
|
+
name: "spawn:audit-1",
|
|
459
|
+
kind: "local",
|
|
460
|
+
output: "audit-1"
|
|
461
|
+
}));
|
|
462
|
+
for (let i = 0; i < 120; i++) {
|
|
463
|
+
const seq = 2 + i;
|
|
464
|
+
rows.push(cp(seq, {
|
|
465
|
+
parallelGroup: "steps:2:120",
|
|
466
|
+
...i % 4 === 0 && i < 100 ? {
|
|
467
|
+
status: "failed",
|
|
468
|
+
attempts: 3,
|
|
469
|
+
error: {
|
|
470
|
+
message: `bad ${i}`
|
|
471
|
+
}
|
|
472
|
+
} : i >= 100 && i < 120 && i !== 105 && i !== 106 ? {
|
|
473
|
+
status: "pending",
|
|
474
|
+
output: void 0
|
|
475
|
+
} : i === 105 || i === 106 ? {
|
|
476
|
+
status: "running",
|
|
477
|
+
output: void 0
|
|
478
|
+
} : {}
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
for (let i = 0; i < 5; i++) rows.push(cp(122 + i, {
|
|
482
|
+
parallelGroup: "steps:122:5"
|
|
483
|
+
}));
|
|
484
|
+
for (let i = 0; i < 30; i++) {
|
|
485
|
+
rows.push(cp(127 + i, {
|
|
486
|
+
parallelGroup: "steps:127:40000",
|
|
487
|
+
name: "Rows.score",
|
|
488
|
+
...i >= 12 ? {
|
|
489
|
+
status: "pending",
|
|
490
|
+
output: void 0
|
|
491
|
+
} : {}
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
for (let i = 0; i < 30; i++) {
|
|
495
|
+
rows.push(cp(157 + i, {
|
|
496
|
+
parallelGroup: "steps:157:60:failFast",
|
|
497
|
+
name: i % 2 === 0 ? "Check.a" : "Check.b",
|
|
498
|
+
...i === 7 ? {
|
|
499
|
+
status: "failed",
|
|
500
|
+
error: {
|
|
501
|
+
message: "stop"
|
|
502
|
+
}
|
|
503
|
+
} : {}
|
|
504
|
+
}));
|
|
505
|
+
}
|
|
506
|
+
for (let i = 0; i < 60; i++) {
|
|
507
|
+
const seq = 187 + i;
|
|
508
|
+
rows.push(child(seq, "all:187:n80", {
|
|
509
|
+
...i >= 50 ? {
|
|
510
|
+
status: "running",
|
|
511
|
+
output: void 0
|
|
512
|
+
} : i % 3 === 0 ? {
|
|
513
|
+
output: {
|
|
514
|
+
ok: false,
|
|
515
|
+
error: `child ${i}`
|
|
516
|
+
}
|
|
517
|
+
} : i % 7 === 1 ? {
|
|
518
|
+
status: "failed",
|
|
519
|
+
error: {
|
|
520
|
+
message: "lost"
|
|
521
|
+
}
|
|
522
|
+
} : {}
|
|
523
|
+
}));
|
|
524
|
+
}
|
|
525
|
+
rows.push(folded(247, "all:187:n80", 9, 60, 20));
|
|
526
|
+
rows.push(folded(300, "all:300:n3000:failFast", 0, 0, 1e3));
|
|
527
|
+
rows.push(folded(301, "all:300:n3000:failFast", 1, 1e3, 1e3));
|
|
528
|
+
rows.push(folded(302, "all:300:n3000:failFast", 2, 2e3, 0));
|
|
529
|
+
for (let i = 0; i < 4; i++) {
|
|
530
|
+
rows.push(child(303 + i, "all:300:n3000:failFast", {
|
|
531
|
+
status: "running",
|
|
532
|
+
output: void 0
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
rows.push(child(400, "all:400:n3"));
|
|
536
|
+
rows.push(child(401, "all:400:n3", {
|
|
537
|
+
output: {
|
|
538
|
+
ok: false,
|
|
539
|
+
error: "narrow"
|
|
540
|
+
}
|
|
541
|
+
}));
|
|
542
|
+
rows.push(child(402, "all:400:n3", {
|
|
543
|
+
status: "running",
|
|
544
|
+
output: void 0
|
|
545
|
+
}));
|
|
546
|
+
for (let i = 0; i < 55; i++) {
|
|
547
|
+
rows.push(child(450 + i, "all:450", i === 54 ? {
|
|
548
|
+
output: {
|
|
549
|
+
ok: false
|
|
550
|
+
}
|
|
551
|
+
} : {}));
|
|
552
|
+
}
|
|
553
|
+
rows.push(cp(600, {
|
|
554
|
+
name: "Lines.report",
|
|
555
|
+
kind: "local",
|
|
556
|
+
status: "failed"
|
|
557
|
+
}));
|
|
558
|
+
return rows;
|
|
559
|
+
}
|
|
560
|
+
__name(aggregationFixture, "aggregationFixture");
|
|
561
|
+
var GROUPS = [
|
|
562
|
+
"steps:2:120",
|
|
563
|
+
"steps:122:5",
|
|
564
|
+
"steps:127:40000",
|
|
565
|
+
"steps:157:60:failFast",
|
|
566
|
+
"all:187:n80",
|
|
567
|
+
"all:300:n3000:failFast",
|
|
568
|
+
"all:400:n3",
|
|
569
|
+
"all:450",
|
|
570
|
+
"absent"
|
|
571
|
+
];
|
|
572
|
+
var QUERIES = [
|
|
573
|
+
{},
|
|
574
|
+
{
|
|
575
|
+
offset: 10,
|
|
576
|
+
limit: 7
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
offset: 0,
|
|
580
|
+
limit: 500
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
offset: 118,
|
|
584
|
+
limit: 50
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
offset: 5e3
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
failedOnly: true
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
failedOnly: true,
|
|
594
|
+
offset: 3,
|
|
595
|
+
limit: 4
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
failedOnly: true,
|
|
599
|
+
offset: 15,
|
|
600
|
+
limit: 10
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
failedOnly: true,
|
|
604
|
+
offset: 1e3
|
|
605
|
+
}
|
|
606
|
+
];
|
|
607
|
+
function comparable(stats) {
|
|
608
|
+
return stats.map(({ minName, maxName, ...rest }) => ({
|
|
609
|
+
...rest,
|
|
610
|
+
uniform: minName === maxName
|
|
611
|
+
})).sort((a, b) => `${a.group}|${a.status}|${a.nameKind}`.localeCompare(`${b.group}|${b.status}|${b.nameKind}`));
|
|
612
|
+
}
|
|
613
|
+
__name(comparable, "comparable");
|
|
614
|
+
async function seed(store, run2) {
|
|
615
|
+
await store.createRun(run2());
|
|
616
|
+
for (const row of aggregationFixture()) await store.saveCheckpoint(row);
|
|
617
|
+
await store.createRun(run2({
|
|
618
|
+
id: "r2"
|
|
619
|
+
}));
|
|
620
|
+
for (let i = 0; i < 3; i++) {
|
|
621
|
+
await store.saveCheckpoint(cp(2 + i, {
|
|
622
|
+
runId: "r2",
|
|
623
|
+
stepId: `r2:${i}`,
|
|
624
|
+
parallelGroup: "steps:2:120",
|
|
625
|
+
status: "failed"
|
|
626
|
+
}));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
__name(seed, "seed");
|
|
630
|
+
function registerCheckpointAggregationCases(t, getStore, run2) {
|
|
631
|
+
t("checkpointStats and queryCheckpoints agree with the in-memory reference", async () => {
|
|
632
|
+
const store = getStore();
|
|
633
|
+
expect2(store.checkpointStats).toBeDefined();
|
|
634
|
+
expect2(store.queryCheckpoints).toBeDefined();
|
|
635
|
+
if (!aggregatesCheckpoints(store)) return;
|
|
636
|
+
await seed(store, run2);
|
|
637
|
+
const full = await store.listCheckpoints("r1");
|
|
638
|
+
expect2(comparable(await store.checkpointStats("r1"))).toEqual(comparable(checkpointStatsOf(full)));
|
|
639
|
+
for (const group of GROUPS) {
|
|
640
|
+
expect2(comparable(await store.checkpointStats("r1", {
|
|
641
|
+
group
|
|
642
|
+
}))).toEqual(comparable(checkpointStatsOf(full, {
|
|
643
|
+
group
|
|
644
|
+
})));
|
|
645
|
+
}
|
|
646
|
+
expect2(await store.checkpointStats("nope")).toEqual([]);
|
|
647
|
+
const queries = [
|
|
648
|
+
{},
|
|
649
|
+
{
|
|
650
|
+
group: "steps:2:120"
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
group: "steps:2:120",
|
|
654
|
+
statuses: [
|
|
655
|
+
"failed"
|
|
656
|
+
],
|
|
657
|
+
limit: 20
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
group: "steps:2:120",
|
|
661
|
+
offset: 30,
|
|
662
|
+
limit: 5
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
group: "all:187:n80",
|
|
666
|
+
statuses: [
|
|
667
|
+
"completed"
|
|
668
|
+
],
|
|
669
|
+
namePrefixes: [
|
|
670
|
+
"signal:child:"
|
|
671
|
+
]
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
excludeGroups: [
|
|
675
|
+
"steps:2:120",
|
|
676
|
+
"all:187:n80",
|
|
677
|
+
"all:450"
|
|
678
|
+
]
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
excludeGroups: []
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
namePrefixes: [
|
|
685
|
+
"signal:child:",
|
|
686
|
+
"fan:",
|
|
687
|
+
"spawn:"
|
|
688
|
+
],
|
|
689
|
+
offset: 200,
|
|
690
|
+
limit: 200
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
namePrefixes: [
|
|
694
|
+
"fan:"
|
|
695
|
+
]
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
statuses: []
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
namePrefixes: []
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
statuses: [
|
|
705
|
+
"pending",
|
|
706
|
+
"running"
|
|
707
|
+
],
|
|
708
|
+
offset: 3
|
|
709
|
+
}
|
|
710
|
+
];
|
|
711
|
+
for (const query of queries) {
|
|
712
|
+
expect2(await store.queryCheckpoints("r1", query)).toEqual(queryCheckpointsOf(full, query));
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
t("a run detail read through the aggregates is the one summarizeTimeline builds", async () => {
|
|
716
|
+
const store = getStore();
|
|
717
|
+
if (!aggregatesCheckpoints(store)) return;
|
|
718
|
+
await seed(store, run2);
|
|
719
|
+
const full = await store.listCheckpoints("r1");
|
|
720
|
+
for (const over of [
|
|
721
|
+
50,
|
|
722
|
+
0,
|
|
723
|
+
1e3
|
|
724
|
+
]) {
|
|
725
|
+
const ids = childIdsOf(full);
|
|
726
|
+
const expected = {
|
|
727
|
+
...summarizeTimeline(full, over),
|
|
728
|
+
children: ids.slice(0, SUMMARY_CHILD_LIMIT),
|
|
729
|
+
childCount: ids.length
|
|
730
|
+
};
|
|
731
|
+
expect2(await summarizeRunFromStore(store, "r1", over)).toEqual(expected);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
t("a fan page read through the aggregates is the one pageFan builds", async () => {
|
|
735
|
+
const store = getStore();
|
|
736
|
+
if (!aggregatesCheckpoints(store)) return;
|
|
737
|
+
await seed(store, run2);
|
|
738
|
+
const full = await store.listCheckpoints("r1");
|
|
739
|
+
for (const group of GROUPS) {
|
|
740
|
+
for (const query of QUERIES) {
|
|
741
|
+
expect2(await pageFanFromStore(store, "r1", group, query)).toEqual(pageFan(full, group, query));
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
__name(registerCheckpointAggregationCases, "registerCheckpointAggregationCases");
|
|
747
|
+
|
|
748
|
+
// src/state-store-conformance.ts
|
|
404
749
|
var StateStoreUnavailableError = class extends Error {
|
|
405
750
|
static {
|
|
406
751
|
__name(this, "StateStoreUnavailableError");
|
|
@@ -486,7 +831,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
486
831
|
});
|
|
487
832
|
}, "t");
|
|
488
833
|
t("atomically admits holders without displacement by later same-millisecond IDs", async () => {
|
|
489
|
-
|
|
834
|
+
expect3(store.tryAdmitSingleton).toBeTypeOf("function");
|
|
490
835
|
const claim = /* @__PURE__ */ __name((id, limit = 1) => store.tryAdmitSingleton?.(id, "singleton:k", "checkout", limit), "claim");
|
|
491
836
|
await store.createRun(run({
|
|
492
837
|
id: "z-holder",
|
|
@@ -495,12 +840,12 @@ function runStateStoreContract(name, makeStore) {
|
|
|
495
840
|
"user-tag"
|
|
496
841
|
]
|
|
497
842
|
}));
|
|
498
|
-
|
|
843
|
+
expect3(await claim("z-holder")).toBe(true);
|
|
499
844
|
await store.updateRun("z-holder", {
|
|
500
845
|
status: "suspended",
|
|
501
846
|
wakeAt: 1e4
|
|
502
847
|
});
|
|
503
|
-
|
|
848
|
+
expect3(await claim("z-holder")).toBe(true);
|
|
504
849
|
await store.updateRun("z-holder", {
|
|
505
850
|
status: "blocked",
|
|
506
851
|
lockedBy: "expired",
|
|
@@ -512,25 +857,25 @@ function runStateStoreContract(name, makeStore) {
|
|
|
512
857
|
"singleton:k"
|
|
513
858
|
]
|
|
514
859
|
}));
|
|
515
|
-
|
|
516
|
-
|
|
860
|
+
expect3(await claim("a-waiter")).toBe(false);
|
|
861
|
+
expect3(await claim("z-holder")).toBe(true);
|
|
517
862
|
await store.updateRun("z-holder", {
|
|
518
863
|
status: "cancelling"
|
|
519
864
|
});
|
|
520
|
-
|
|
865
|
+
expect3(await claim("a-waiter")).toBe(false);
|
|
521
866
|
await store.updateRun("z-holder", {
|
|
522
867
|
status: "completed"
|
|
523
868
|
});
|
|
524
|
-
|
|
869
|
+
expect3((await store.getRun("z-holder"))?.tags).toEqual([
|
|
525
870
|
"singleton:k",
|
|
526
871
|
"user-tag"
|
|
527
872
|
]);
|
|
528
|
-
|
|
873
|
+
expect3(await claim("a-waiter")).toBe(true);
|
|
529
874
|
await store.updateRun("z-holder", {
|
|
530
875
|
status: "running"
|
|
531
876
|
});
|
|
532
|
-
|
|
533
|
-
|
|
877
|
+
expect3(await claim("z-holder")).toBe(false);
|
|
878
|
+
expect3(await claim("missing")).toBe(false);
|
|
534
879
|
});
|
|
535
880
|
t("singleton denial atomically parks waiters and preserves cancelling/terminal state", async () => {
|
|
536
881
|
await store.createRun(run({
|
|
@@ -539,15 +884,15 @@ function runStateStoreContract(name, makeStore) {
|
|
|
539
884
|
"singleton:k"
|
|
540
885
|
]
|
|
541
886
|
}));
|
|
542
|
-
|
|
887
|
+
expect3(await store.tryAdmitSingleton?.("holder", "singleton:k", "checkout", 1)).toBe(true);
|
|
543
888
|
await store.createRun(run({
|
|
544
889
|
id: "waiter",
|
|
545
890
|
tags: [
|
|
546
891
|
"singleton:k"
|
|
547
892
|
]
|
|
548
893
|
}));
|
|
549
|
-
|
|
550
|
-
|
|
894
|
+
expect3(await peerStore.tryAdmitSingleton?.("waiter", "singleton:k", "checkout", 1, 12e3)).toBe(false);
|
|
895
|
+
expect3(await store.getRun("waiter")).toMatchObject({
|
|
551
896
|
status: "suspended",
|
|
552
897
|
wakeAt: 12e3
|
|
553
898
|
});
|
|
@@ -555,16 +900,16 @@ function runStateStoreContract(name, makeStore) {
|
|
|
555
900
|
status: "cancelling",
|
|
556
901
|
wakeAt: void 0
|
|
557
902
|
});
|
|
558
|
-
|
|
559
|
-
|
|
903
|
+
expect3(await peerStore.tryAdmitSingleton?.("waiter", "singleton:k", "checkout", 1, 14e3)).toBe(false);
|
|
904
|
+
expect3(await store.getRun("waiter")).toMatchObject({
|
|
560
905
|
status: "cancelling"
|
|
561
906
|
});
|
|
562
|
-
|
|
907
|
+
expect3((await store.getRun("waiter"))?.wakeAt).toBeUndefined();
|
|
563
908
|
await store.updateRun("waiter", {
|
|
564
909
|
status: "cancelled"
|
|
565
910
|
});
|
|
566
|
-
|
|
567
|
-
|
|
911
|
+
expect3(await peerStore.tryAdmitSingleton?.("waiter", "singleton:k", "checkout", 1, 16e3)).toBe(false);
|
|
912
|
+
expect3((await store.getRun("waiter"))?.status).toBe("cancelled");
|
|
568
913
|
await store.updateRun("holder", {
|
|
569
914
|
status: "completed"
|
|
570
915
|
});
|
|
@@ -575,8 +920,8 @@ function runStateStoreContract(name, makeStore) {
|
|
|
575
920
|
"singleton:k"
|
|
576
921
|
]
|
|
577
922
|
}));
|
|
578
|
-
|
|
579
|
-
|
|
923
|
+
expect3(await store.tryAdmitSingleton?.("cancelling", "singleton:k", "checkout", 1, 18e3)).toBe(false);
|
|
924
|
+
expect3((await store.getRun("cancelling"))?.tags).not.toContain(SINGLETON_ADMITTED_TAG);
|
|
580
925
|
});
|
|
581
926
|
t("competing store instances atomically claim an empty singleton slot", async () => {
|
|
582
927
|
for (const id of [
|
|
@@ -596,13 +941,13 @@ function runStateStoreContract(name, makeStore) {
|
|
|
596
941
|
store.tryAdmitSingleton?.("a-first", "singleton:k", "checkout", 1),
|
|
597
942
|
peerStore.tryAdmitSingleton?.("c-third", "singleton:k", "checkout", 1)
|
|
598
943
|
]);
|
|
599
|
-
|
|
944
|
+
expect3(results).toEqual([
|
|
600
945
|
false,
|
|
601
946
|
true,
|
|
602
947
|
false
|
|
603
948
|
]);
|
|
604
|
-
|
|
605
|
-
|
|
949
|
+
expect3(await peerStore.tryAdmitSingleton?.("a-first", "singleton:k", "checkout", 1)).toBe(true);
|
|
950
|
+
expect3((await store.getRun("a-first"))?.tags).toContain(SINGLETON_ADMITTED_TAG);
|
|
606
951
|
});
|
|
607
952
|
t("concurrent singleton claims honor limits and independent keys/workflows", async () => {
|
|
608
953
|
const claim = /* @__PURE__ */ __name((id, tag = "singleton:k", workflow = "checkout") => store.tryAdmitSingleton?.(id, tag, workflow, 2), "claim");
|
|
@@ -616,7 +961,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
616
961
|
"singleton:k"
|
|
617
962
|
]
|
|
618
963
|
}));
|
|
619
|
-
|
|
964
|
+
expect3(await claim(id)).toBe(true);
|
|
620
965
|
}
|
|
621
966
|
for (const id of [
|
|
622
967
|
"a-waiter",
|
|
@@ -631,7 +976,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
631
976
|
]
|
|
632
977
|
}));
|
|
633
978
|
}
|
|
634
|
-
|
|
979
|
+
expect3(await Promise.all([
|
|
635
980
|
"a-waiter",
|
|
636
981
|
"b-waiter",
|
|
637
982
|
"c-waiter"
|
|
@@ -643,7 +988,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
643
988
|
await store.updateRun("z-holder", {
|
|
644
989
|
status: "failed"
|
|
645
990
|
});
|
|
646
|
-
|
|
991
|
+
expect3(await Promise.all([
|
|
647
992
|
"c-waiter",
|
|
648
993
|
"b-waiter",
|
|
649
994
|
"a-waiter"
|
|
@@ -659,14 +1004,14 @@ function runStateStoreContract(name, makeStore) {
|
|
|
659
1004
|
"pending"
|
|
660
1005
|
]
|
|
661
1006
|
});
|
|
662
|
-
|
|
1007
|
+
expect3(active.filter((r) => r.tags?.includes(SINGLETON_ADMITTED_TAG))).toHaveLength(2);
|
|
663
1008
|
await store.createRun(run({
|
|
664
1009
|
id: "other-key",
|
|
665
1010
|
tags: [
|
|
666
1011
|
"singleton:other"
|
|
667
1012
|
]
|
|
668
1013
|
}));
|
|
669
|
-
|
|
1014
|
+
expect3(await claim("other-key", "singleton:other")).toBe(true);
|
|
670
1015
|
await store.createRun(run({
|
|
671
1016
|
id: "other-workflow",
|
|
672
1017
|
workflow: "other",
|
|
@@ -674,18 +1019,18 @@ function runStateStoreContract(name, makeStore) {
|
|
|
674
1019
|
"singleton:k"
|
|
675
1020
|
]
|
|
676
1021
|
}));
|
|
677
|
-
|
|
1022
|
+
expect3(await claim("other-workflow", "singleton:k", "other")).toBe(true);
|
|
678
1023
|
});
|
|
679
1024
|
t("creates a run and reads it back with its JSON input", async () => {
|
|
680
1025
|
await store.createRun(run());
|
|
681
1026
|
const loaded = await store.getRun("r1");
|
|
682
|
-
|
|
683
|
-
|
|
1027
|
+
expect3(loaded?.workflow).toBe("checkout");
|
|
1028
|
+
expect3(loaded?.input).toEqual({
|
|
684
1029
|
orderId: "o1"
|
|
685
1030
|
});
|
|
686
1031
|
});
|
|
687
1032
|
t("returns null for a missing run", async () => {
|
|
688
|
-
|
|
1033
|
+
expect3(await store.getRun("nope")).toBeNull();
|
|
689
1034
|
});
|
|
690
1035
|
t("updates a run (status/output) and round-trips recoveryAttempts + dead status", async () => {
|
|
691
1036
|
await store.createRun(run({
|
|
@@ -699,18 +1044,18 @@ function runStateStoreContract(name, makeStore) {
|
|
|
699
1044
|
updatedAt: at
|
|
700
1045
|
});
|
|
701
1046
|
const done = await store.getRun("r1");
|
|
702
|
-
|
|
703
|
-
|
|
1047
|
+
expect3(done?.status).toBe("completed");
|
|
1048
|
+
expect3(done?.output).toEqual({
|
|
704
1049
|
total: 42
|
|
705
1050
|
});
|
|
706
|
-
|
|
1051
|
+
expect3(done?.recoveryAttempts).toBe(3);
|
|
707
1052
|
await store.updateRun("r1", {
|
|
708
1053
|
status: "dead",
|
|
709
1054
|
recoveryAttempts: 4
|
|
710
1055
|
});
|
|
711
1056
|
const dead = await store.getRun("r1");
|
|
712
|
-
|
|
713
|
-
|
|
1057
|
+
expect3(dead?.status).toBe("dead");
|
|
1058
|
+
expect3(dead?.recoveryAttempts).toBe(4);
|
|
714
1059
|
});
|
|
715
1060
|
t("round-trips a run dispatch priority", async () => {
|
|
716
1061
|
await store.createRun(run({
|
|
@@ -720,8 +1065,8 @@ function runStateStoreContract(name, makeStore) {
|
|
|
720
1065
|
await store.createRun(run({
|
|
721
1066
|
id: "noprio"
|
|
722
1067
|
}));
|
|
723
|
-
|
|
724
|
-
|
|
1068
|
+
expect3((await store.getRun("withprio"))?.priority).toBe(7);
|
|
1069
|
+
expect3((await store.getRun("noprio"))?.priority).toBeUndefined();
|
|
725
1070
|
});
|
|
726
1071
|
t("deleteRun removes the run and all its rows (checkpoints, waiters, attributes)", async () => {
|
|
727
1072
|
await store.createRun(run({
|
|
@@ -736,10 +1081,10 @@ function runStateStoreContract(name, makeStore) {
|
|
|
736
1081
|
seq: 0
|
|
737
1082
|
});
|
|
738
1083
|
await store.deleteRun("r1");
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1084
|
+
expect3(await store.getRun("r1")).toBeNull();
|
|
1085
|
+
expect3(await store.listCheckpoints("r1")).toEqual([]);
|
|
1086
|
+
expect3(await store.listSignalWaiters("approve-")).toEqual([]);
|
|
1087
|
+
expect3(await store.listRuns({
|
|
743
1088
|
attributes: [
|
|
744
1089
|
{
|
|
745
1090
|
key: "tier",
|
|
@@ -750,7 +1095,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
750
1095
|
})).toEqual([]);
|
|
751
1096
|
});
|
|
752
1097
|
t("deleteRun is a no-op for a missing run", async () => {
|
|
753
|
-
await
|
|
1098
|
+
await expect3(store.deleteRun("nope")).resolves.toBeUndefined();
|
|
754
1099
|
});
|
|
755
1100
|
t("deleteRuns removes every listed run with its rows and ignores missing ids", async () => {
|
|
756
1101
|
await store.createRun(run({
|
|
@@ -774,18 +1119,18 @@ function runStateStoreContract(name, makeStore) {
|
|
|
774
1119
|
runId: "d2",
|
|
775
1120
|
seq: 0
|
|
776
1121
|
});
|
|
777
|
-
|
|
1122
|
+
expect3(typeof store.deleteRuns).toBe("function");
|
|
778
1123
|
await store.deleteRuns?.([
|
|
779
1124
|
"d1",
|
|
780
1125
|
"d2",
|
|
781
1126
|
"ghost"
|
|
782
1127
|
]);
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1128
|
+
expect3(await store.getRun("d1")).toBeNull();
|
|
1129
|
+
expect3(await store.getRun("d2")).toBeNull();
|
|
1130
|
+
expect3(await store.getRun("keep")).not.toBeNull();
|
|
1131
|
+
expect3(await store.listCheckpoints("d1")).toEqual([]);
|
|
1132
|
+
expect3(await store.listSignalWaiters("approve-")).toEqual([]);
|
|
1133
|
+
expect3(await store.listRuns({
|
|
789
1134
|
attributes: [
|
|
790
1135
|
{
|
|
791
1136
|
key: "tier",
|
|
@@ -794,7 +1139,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
794
1139
|
}
|
|
795
1140
|
]
|
|
796
1141
|
})).toEqual([]);
|
|
797
|
-
await
|
|
1142
|
+
await expect3(store.deleteRuns?.([])).resolves.toBeUndefined();
|
|
798
1143
|
});
|
|
799
1144
|
const aged = /* @__PURE__ */ __name((id, ageDays, over = {}) => run({
|
|
800
1145
|
id,
|
|
@@ -817,7 +1162,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
817
1162
|
runId: "old-done",
|
|
818
1163
|
stepId: "old-done:0"
|
|
819
1164
|
}));
|
|
820
|
-
|
|
1165
|
+
expect3(typeof store.pruneTerminalRuns).toBe("function");
|
|
821
1166
|
const deleted = await store.pruneTerminalRuns?.({
|
|
822
1167
|
statuses: [
|
|
823
1168
|
"completed",
|
|
@@ -826,12 +1171,12 @@ function runStateStoreContract(name, makeStore) {
|
|
|
826
1171
|
],
|
|
827
1172
|
maxAge: "7d"
|
|
828
1173
|
}, NOW, 100);
|
|
829
|
-
|
|
830
|
-
|
|
1174
|
+
expect3(deleted).toBe(2);
|
|
1175
|
+
expect3(await ids()).toEqual([
|
|
831
1176
|
"fresh-done",
|
|
832
1177
|
"old-live"
|
|
833
1178
|
]);
|
|
834
|
-
|
|
1179
|
+
expect3(await store.listCheckpoints("old-done")).toEqual([]);
|
|
835
1180
|
});
|
|
836
1181
|
t("pruneTerminalRuns keeps only the newest maxCount and drains in batches of limit", async () => {
|
|
837
1182
|
for (let i = 1; i <= 5; i++) await store.createRun(aged(`c${i}`, i));
|
|
@@ -841,10 +1186,10 @@ function runStateStoreContract(name, makeStore) {
|
|
|
841
1186
|
],
|
|
842
1187
|
maxCount: 2
|
|
843
1188
|
};
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1189
|
+
expect3(await store.pruneTerminalRuns?.(policy, NOW, 2)).toBe(2);
|
|
1190
|
+
expect3(await store.pruneTerminalRuns?.(policy, NOW, 2)).toBe(1);
|
|
1191
|
+
expect3(await store.pruneTerminalRuns?.(policy, NOW, 2)).toBe(0);
|
|
1192
|
+
expect3(await ids()).toEqual([
|
|
848
1193
|
"c1",
|
|
849
1194
|
"c2"
|
|
850
1195
|
]);
|
|
@@ -871,7 +1216,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
871
1216
|
namespace: "acme"
|
|
872
1217
|
}
|
|
873
1218
|
}, NOW, 100);
|
|
874
|
-
|
|
1219
|
+
expect3(await ids()).toEqual([
|
|
875
1220
|
"acme-new",
|
|
876
1221
|
"beta-old",
|
|
877
1222
|
"report-old"
|
|
@@ -887,7 +1232,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
887
1232
|
]
|
|
888
1233
|
}
|
|
889
1234
|
}, NOW, 100);
|
|
890
|
-
|
|
1235
|
+
expect3(await ids()).toEqual([
|
|
891
1236
|
"acme-new",
|
|
892
1237
|
"beta-old"
|
|
893
1238
|
]);
|
|
@@ -903,7 +1248,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
903
1248
|
namespace: "acme"
|
|
904
1249
|
}
|
|
905
1250
|
}, NOW, 100);
|
|
906
|
-
|
|
1251
|
+
expect3(await ids()).toEqual([
|
|
907
1252
|
"acme-new",
|
|
908
1253
|
"beta-old"
|
|
909
1254
|
]);
|
|
@@ -942,7 +1287,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
942
1287
|
]
|
|
943
1288
|
}
|
|
944
1289
|
}, NOW, 100);
|
|
945
|
-
|
|
1290
|
+
expect3(await ids()).toEqual([
|
|
946
1291
|
"attr-old",
|
|
947
1292
|
"attr-other",
|
|
948
1293
|
"chatty-old"
|
|
@@ -962,7 +1307,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
962
1307
|
]
|
|
963
1308
|
}
|
|
964
1309
|
}, NOW, 100);
|
|
965
|
-
|
|
1310
|
+
expect3(await ids()).toEqual([
|
|
966
1311
|
"attr-other",
|
|
967
1312
|
"chatty-old"
|
|
968
1313
|
]);
|
|
@@ -979,21 +1324,24 @@ function runStateStoreContract(name, makeStore) {
|
|
|
979
1324
|
engine.register("p-other", "1", async () => "done");
|
|
980
1325
|
await engine.start("p-root", {}, "pr");
|
|
981
1326
|
await engine.start("p-other", {}, "po");
|
|
982
|
-
|
|
1327
|
+
expect3((await engine.waitForRun("pr", {
|
|
983
1328
|
timeoutMs: 2e4
|
|
984
1329
|
})).status).toBe("suspended");
|
|
985
|
-
|
|
1330
|
+
expect3((await engine.waitForRun("pc", {
|
|
1331
|
+
timeoutMs: 2e4
|
|
1332
|
+
})).status).toBe("suspended");
|
|
1333
|
+
expect3((await engine.waitForRun("po", {
|
|
986
1334
|
timeoutMs: 2e4
|
|
987
1335
|
})).status).toBe("completed");
|
|
988
|
-
await
|
|
989
|
-
|
|
1336
|
+
await expect3(engine.purgeRuns({})).rejects.toThrow(/empty scope/);
|
|
1337
|
+
expect3(await engine.purgeRuns({
|
|
990
1338
|
workflows: [
|
|
991
1339
|
"p-root"
|
|
992
1340
|
]
|
|
993
1341
|
})).toBe(2);
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1342
|
+
expect3(await store.getRun("pr")).toBeNull();
|
|
1343
|
+
expect3(await store.getRun("pc")).toBeNull();
|
|
1344
|
+
expect3(await store.getRun("po")).not.toBeNull();
|
|
997
1345
|
});
|
|
998
1346
|
t("updateRun maps every patchable field \u2014 clears error and patches tags/lockedBy/input/timers", async () => {
|
|
999
1347
|
await store.createRun(run({
|
|
@@ -1025,29 +1373,29 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1025
1373
|
updatedAt: at
|
|
1026
1374
|
});
|
|
1027
1375
|
const r = await store.getRun("r1");
|
|
1028
|
-
|
|
1029
|
-
|
|
1376
|
+
expect3(r?.status).toBe("completed");
|
|
1377
|
+
expect3(r?.output).toEqual({
|
|
1030
1378
|
ok: true
|
|
1031
1379
|
});
|
|
1032
|
-
|
|
1033
|
-
|
|
1380
|
+
expect3(r?.error).toBeUndefined();
|
|
1381
|
+
expect3(r?.input).toEqual({
|
|
1034
1382
|
orderId: "o2"
|
|
1035
1383
|
});
|
|
1036
|
-
|
|
1384
|
+
expect3(r?.tags).toEqual([
|
|
1037
1385
|
"x",
|
|
1038
1386
|
"y"
|
|
1039
1387
|
]);
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1388
|
+
expect3(r?.lockedBy).toBe("owner-2");
|
|
1389
|
+
expect3(r?.wakeAt).toBe(123456);
|
|
1390
|
+
expect3(r?.lockedUntil).toBe(222222);
|
|
1043
1391
|
await store.updateRun("r1", {
|
|
1044
1392
|
awaitingDecisionTaskId: "turn-7"
|
|
1045
1393
|
});
|
|
1046
|
-
|
|
1394
|
+
expect3((await store.getRun("r1"))?.awaitingDecisionTaskId).toBe("turn-7");
|
|
1047
1395
|
await store.updateRun("r1", {
|
|
1048
1396
|
awaitingDecisionTaskId: void 0
|
|
1049
1397
|
});
|
|
1050
|
-
|
|
1398
|
+
expect3((await store.getRun("r1"))?.awaitingDecisionTaskId).toBeUndefined();
|
|
1051
1399
|
});
|
|
1052
1400
|
t("listDueTimers returns the most overdue first, capped at limit", async () => {
|
|
1053
1401
|
for (const [id, wakeAt] of [
|
|
@@ -1074,18 +1422,18 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1074
1422
|
wakeAt
|
|
1075
1423
|
}));
|
|
1076
1424
|
}
|
|
1077
|
-
|
|
1425
|
+
expect3((await store.listDueTimers(5e3)).map((r) => r.id)).toEqual([
|
|
1078
1426
|
"earliest",
|
|
1079
1427
|
"middle",
|
|
1080
1428
|
"late"
|
|
1081
1429
|
]);
|
|
1082
|
-
|
|
1430
|
+
expect3((await store.listDueTimers(5e3, void 0, 2)).map((r) => r.id)).toEqual([
|
|
1083
1431
|
"earliest",
|
|
1084
1432
|
"middle"
|
|
1085
1433
|
]);
|
|
1086
1434
|
});
|
|
1087
1435
|
t("countCheckpoints counts a seq range by status", async () => {
|
|
1088
|
-
|
|
1436
|
+
expect3(store.countCheckpoints).toBeDefined();
|
|
1089
1437
|
if (!store.countCheckpoints) return;
|
|
1090
1438
|
await store.createRun(run());
|
|
1091
1439
|
for (const [seq, status] of [
|
|
@@ -1125,32 +1473,175 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1125
1473
|
status: "pending",
|
|
1126
1474
|
stepId: "other:2"
|
|
1127
1475
|
}));
|
|
1128
|
-
|
|
1476
|
+
expect3(await store.countCheckpoints("r1", 1, 3, [
|
|
1129
1477
|
"pending"
|
|
1130
1478
|
])).toBe(2);
|
|
1131
|
-
|
|
1479
|
+
expect3(await store.countCheckpoints("r1", 0, 4, [
|
|
1132
1480
|
"pending",
|
|
1133
1481
|
"failed"
|
|
1134
1482
|
])).toBe(4);
|
|
1135
|
-
|
|
1483
|
+
expect3(await store.countCheckpoints("r1", 2, 2, [
|
|
1136
1484
|
"pending"
|
|
1137
1485
|
])).toBe(1);
|
|
1138
|
-
|
|
1486
|
+
expect3(await store.countCheckpoints("r1", 1, 4, [
|
|
1139
1487
|
"pending"
|
|
1140
1488
|
])).toBe(3);
|
|
1141
|
-
|
|
1489
|
+
expect3(await store.countCheckpoints("r1", 0, 0, [
|
|
1142
1490
|
"completed"
|
|
1143
1491
|
])).toBe(1);
|
|
1144
|
-
|
|
1492
|
+
expect3(await store.countCheckpoints("other", 0, 4, [
|
|
1145
1493
|
"pending"
|
|
1146
1494
|
])).toBe(1);
|
|
1147
|
-
|
|
1495
|
+
expect3(await store.countCheckpoints("r1", 5, 9, [
|
|
1148
1496
|
"pending"
|
|
1149
1497
|
])).toBe(0);
|
|
1150
|
-
|
|
1498
|
+
expect3(await store.countCheckpoints("r1", 0, 4, [])).toBe(0);
|
|
1499
|
+
});
|
|
1500
|
+
registerCheckpointAggregationCases(t, () => store, run);
|
|
1501
|
+
t("listOrphanedRuns returns incomplete runs with a free or expired lease, oldest first", async () => {
|
|
1502
|
+
expect3(store.listOrphanedRuns).toBeDefined();
|
|
1503
|
+
if (!store.listOrphanedRuns) return;
|
|
1504
|
+
const created = /* @__PURE__ */ __name((offset) => new Date(at.getTime() + offset * 1e3), "created");
|
|
1505
|
+
await store.createRun(run({
|
|
1506
|
+
id: "held",
|
|
1507
|
+
createdAt: created(1)
|
|
1508
|
+
}));
|
|
1509
|
+
expect3(await store.tryLockRun("held", "live", NOW + 3e4, NOW)).toBe(true);
|
|
1510
|
+
await store.createRun(run({
|
|
1511
|
+
id: "expired",
|
|
1512
|
+
createdAt: created(3)
|
|
1513
|
+
}));
|
|
1514
|
+
expect3(await store.tryLockRun("expired", "dead", NOW, NOW - 1)).toBe(true);
|
|
1515
|
+
await store.createRun(run({
|
|
1516
|
+
id: "free",
|
|
1517
|
+
createdAt: created(2)
|
|
1518
|
+
}));
|
|
1519
|
+
await store.createRun(run({
|
|
1520
|
+
id: "compensating",
|
|
1521
|
+
status: "cancelling",
|
|
1522
|
+
createdAt: created(4)
|
|
1523
|
+
}));
|
|
1524
|
+
await store.createRun(run({
|
|
1525
|
+
id: "parked",
|
|
1526
|
+
status: "suspended",
|
|
1527
|
+
createdAt: created(0)
|
|
1528
|
+
}));
|
|
1529
|
+
await store.createRun(run({
|
|
1530
|
+
id: "done",
|
|
1531
|
+
status: "completed",
|
|
1532
|
+
createdAt: created(0)
|
|
1533
|
+
}));
|
|
1534
|
+
await store.createRun(run({
|
|
1535
|
+
id: "tenant",
|
|
1536
|
+
namespace: "beta",
|
|
1537
|
+
createdAt: created(5)
|
|
1538
|
+
}));
|
|
1539
|
+
expect3((await store.listOrphanedRuns(NOW, 10)).map((r) => r.id)).toEqual([
|
|
1540
|
+
"free",
|
|
1541
|
+
"expired",
|
|
1542
|
+
"compensating",
|
|
1543
|
+
"tenant"
|
|
1544
|
+
]);
|
|
1545
|
+
expect3((await store.listOrphanedRuns(NOW, 2)).map((r) => r.id)).toEqual([
|
|
1546
|
+
"free",
|
|
1547
|
+
"expired"
|
|
1548
|
+
]);
|
|
1549
|
+
expect3((await store.listOrphanedRuns(NOW, 10, "beta")).map((r) => r.id)).toEqual([
|
|
1550
|
+
"tenant"
|
|
1551
|
+
]);
|
|
1552
|
+
expect3((await store.listOrphanedRuns(NOW + 3e4, 10)).map((r) => r.id)).toContain("held");
|
|
1553
|
+
expect3(await store.listOrphanedRuns(NOW, 0)).toEqual([]);
|
|
1554
|
+
});
|
|
1555
|
+
t("updateRunIf writes only over an expected status", async () => {
|
|
1556
|
+
expect3(store.updateRunIf).toBeDefined();
|
|
1557
|
+
if (!store.updateRunIf) return;
|
|
1558
|
+
await store.createRun(run({
|
|
1559
|
+
error: {
|
|
1560
|
+
message: "earlier attempt"
|
|
1561
|
+
}
|
|
1562
|
+
}));
|
|
1563
|
+
expect3(await store.updateRunIf("r1", [
|
|
1564
|
+
"suspended",
|
|
1565
|
+
"blocked"
|
|
1566
|
+
], {
|
|
1567
|
+
status: "completed",
|
|
1568
|
+
output: {
|
|
1569
|
+
total: 1
|
|
1570
|
+
}
|
|
1571
|
+
})).toBe(false);
|
|
1572
|
+
let current = await store.getRun("r1");
|
|
1573
|
+
expect3(current?.status).toBe("running");
|
|
1574
|
+
expect3(current?.output).toBeUndefined();
|
|
1575
|
+
expect3(await store.updateRunIf("r1", [
|
|
1576
|
+
"pending",
|
|
1577
|
+
"running"
|
|
1578
|
+
], {
|
|
1579
|
+
status: "suspended",
|
|
1580
|
+
wakeAt: 7e3,
|
|
1581
|
+
error: void 0,
|
|
1582
|
+
updatedAt: new Date(at.getTime() + 1e3)
|
|
1583
|
+
})).toBe(true);
|
|
1584
|
+
current = await store.getRun("r1");
|
|
1585
|
+
expect3(current?.status).toBe("suspended");
|
|
1586
|
+
expect3(current?.wakeAt).toBe(7e3);
|
|
1587
|
+
expect3(current?.error).toBeUndefined();
|
|
1588
|
+
expect3(current?.updatedAt.getTime()).toBe(at.getTime() + 1e3);
|
|
1589
|
+
await store.updateRun("r1", {
|
|
1590
|
+
status: "cancelled",
|
|
1591
|
+
error: {
|
|
1592
|
+
message: "cancelled"
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
expect3(await store.updateRunIf("r1", [
|
|
1596
|
+
"running",
|
|
1597
|
+
"suspended"
|
|
1598
|
+
], {
|
|
1599
|
+
status: "completed",
|
|
1600
|
+
output: {
|
|
1601
|
+
total: 2
|
|
1602
|
+
}
|
|
1603
|
+
})).toBe(false);
|
|
1604
|
+
current = await store.getRun("r1");
|
|
1605
|
+
expect3(current?.status).toBe("cancelled");
|
|
1606
|
+
expect3(current?.error).toEqual({
|
|
1607
|
+
message: "cancelled"
|
|
1608
|
+
});
|
|
1609
|
+
expect3(current?.output).toBeUndefined();
|
|
1610
|
+
expect3(await store.updateRunIf("missing", [
|
|
1611
|
+
"running"
|
|
1612
|
+
], {
|
|
1613
|
+
status: "completed"
|
|
1614
|
+
})).toBe(false);
|
|
1615
|
+
expect3(await store.getRun("missing")).toBeNull();
|
|
1616
|
+
});
|
|
1617
|
+
t("updateRunIf settling a run terminal drops its singleton admission like updateRun", async () => {
|
|
1618
|
+
expect3(store.updateRunIf).toBeDefined();
|
|
1619
|
+
if (!store.updateRunIf) return;
|
|
1620
|
+
await store.createRun(run({
|
|
1621
|
+
tags: [
|
|
1622
|
+
"singleton:k",
|
|
1623
|
+
SINGLETON_ADMITTED_TAG
|
|
1624
|
+
]
|
|
1625
|
+
}));
|
|
1626
|
+
expect3(await store.updateRunIf("r1", [
|
|
1627
|
+
"running"
|
|
1628
|
+
], {
|
|
1629
|
+
status: "completed",
|
|
1630
|
+
output: {
|
|
1631
|
+
ok: true
|
|
1632
|
+
}
|
|
1633
|
+
})).toBe(true);
|
|
1634
|
+
const settled = await store.getRun("r1");
|
|
1635
|
+
expect3(settled?.status).toBe("completed");
|
|
1636
|
+
expect3(settled?.output).toEqual({
|
|
1637
|
+
ok: true
|
|
1638
|
+
});
|
|
1639
|
+
expect3(settled?.tags).toEqual([
|
|
1640
|
+
"singleton:k"
|
|
1641
|
+
]);
|
|
1151
1642
|
});
|
|
1152
1643
|
t("saveCheckpointIfPending writes over a pending row only", async () => {
|
|
1153
|
-
|
|
1644
|
+
expect3(store.saveCheckpointIfPending).toBeDefined();
|
|
1154
1645
|
if (!store.saveCheckpointIfPending) return;
|
|
1155
1646
|
await store.createRun(run());
|
|
1156
1647
|
const pending = checkpoint({
|
|
@@ -1158,11 +1649,11 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1158
1649
|
output: void 0
|
|
1159
1650
|
});
|
|
1160
1651
|
await store.saveCheckpoint(pending);
|
|
1161
|
-
|
|
1652
|
+
expect3(await store.saveCheckpointIfPending({
|
|
1162
1653
|
...pending,
|
|
1163
1654
|
wakeAt: 5e3
|
|
1164
1655
|
})).toBe(true);
|
|
1165
|
-
|
|
1656
|
+
expect3((await store.getCheckpoint("r1", 0))?.wakeAt).toBe(5e3);
|
|
1166
1657
|
const done = {
|
|
1167
1658
|
...pending,
|
|
1168
1659
|
status: "completed",
|
|
@@ -1170,33 +1661,190 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1170
1661
|
ok: true
|
|
1171
1662
|
}
|
|
1172
1663
|
};
|
|
1173
|
-
|
|
1174
|
-
|
|
1664
|
+
expect3(await store.saveCheckpointIfPending(done)).toBe(true);
|
|
1665
|
+
expect3(await store.saveCheckpointIfPending({
|
|
1175
1666
|
...pending,
|
|
1176
1667
|
wakeAt: 9e3
|
|
1177
1668
|
})).toBe(false);
|
|
1178
1669
|
const after = await store.getCheckpoint("r1", 0);
|
|
1179
|
-
|
|
1180
|
-
|
|
1670
|
+
expect3(after?.status).toBe("completed");
|
|
1671
|
+
expect3(after?.output).toEqual({
|
|
1181
1672
|
ok: true
|
|
1182
1673
|
});
|
|
1183
|
-
|
|
1674
|
+
expect3(after?.wakeAt).toBeUndefined();
|
|
1184
1675
|
await store.saveCheckpoint(checkpoint({
|
|
1185
1676
|
seq: 1,
|
|
1186
1677
|
status: "failed",
|
|
1187
1678
|
stepId: "r1:1"
|
|
1188
1679
|
}));
|
|
1189
|
-
|
|
1680
|
+
expect3(await store.saveCheckpointIfPending({
|
|
1190
1681
|
...pending,
|
|
1191
1682
|
seq: 1,
|
|
1192
1683
|
stepId: "r1:1"
|
|
1193
1684
|
})).toBe(false);
|
|
1194
|
-
|
|
1195
|
-
|
|
1685
|
+
expect3((await store.getCheckpoint("r1", 1))?.status).toBe("failed");
|
|
1686
|
+
expect3(await store.saveCheckpointIfPending({
|
|
1196
1687
|
...pending,
|
|
1197
1688
|
seq: 9
|
|
1198
1689
|
})).toBe(false);
|
|
1199
|
-
|
|
1690
|
+
expect3(await store.getCheckpoint("r1", 9)).toBeNull();
|
|
1691
|
+
});
|
|
1692
|
+
t("saveCheckpointsIfPending settles a batch row by row, in order", async () => {
|
|
1693
|
+
expect3(store.saveCheckpointsIfPending).toBeDefined();
|
|
1694
|
+
if (!store.saveCheckpointsIfPending) return;
|
|
1695
|
+
expect3(await store.saveCheckpointsIfPending([])).toEqual([]);
|
|
1696
|
+
await store.createRun(run());
|
|
1697
|
+
await store.createRun(run({
|
|
1698
|
+
id: "r2"
|
|
1699
|
+
}));
|
|
1700
|
+
const pending = /* @__PURE__ */ __name((runId, seq) => checkpoint({
|
|
1701
|
+
runId,
|
|
1702
|
+
seq,
|
|
1703
|
+
stepId: `${runId}:${seq}`,
|
|
1704
|
+
status: "pending",
|
|
1705
|
+
output: void 0,
|
|
1706
|
+
wakeAt: 5e3
|
|
1707
|
+
}), "pending");
|
|
1708
|
+
for (const seq of [
|
|
1709
|
+
0,
|
|
1710
|
+
1,
|
|
1711
|
+
2,
|
|
1712
|
+
3
|
|
1713
|
+
]) await store.saveCheckpoint(pending("r1", seq));
|
|
1714
|
+
await store.saveCheckpoint(pending("r2", 0));
|
|
1715
|
+
await store.saveCheckpoint({
|
|
1716
|
+
...pending("r1", 1),
|
|
1717
|
+
status: "completed",
|
|
1718
|
+
output: {
|
|
1719
|
+
first: 1
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
const done = /* @__PURE__ */ __name((runId, seq, output) => ({
|
|
1723
|
+
...pending(runId, seq),
|
|
1724
|
+
status: "completed",
|
|
1725
|
+
output,
|
|
1726
|
+
wakeAt: void 0
|
|
1727
|
+
}), "done");
|
|
1728
|
+
const wrote = await store.saveCheckpointsIfPending([
|
|
1729
|
+
done("r1", 0, {
|
|
1730
|
+
n: 0
|
|
1731
|
+
}),
|
|
1732
|
+
done("r1", 1, {
|
|
1733
|
+
n: 1
|
|
1734
|
+
}),
|
|
1735
|
+
// A second copy of the same row: compared against the first copy's write, so refused.
|
|
1736
|
+
done("r1", 0, {
|
|
1737
|
+
n: "dup"
|
|
1738
|
+
}),
|
|
1739
|
+
{
|
|
1740
|
+
...pending("r1", 2),
|
|
1741
|
+
status: "failed",
|
|
1742
|
+
error: {
|
|
1743
|
+
message: "boom"
|
|
1744
|
+
},
|
|
1745
|
+
wakeAt: void 0
|
|
1746
|
+
},
|
|
1747
|
+
// No such row: nothing is written.
|
|
1748
|
+
done("r1", 9, {
|
|
1749
|
+
n: 9
|
|
1750
|
+
}),
|
|
1751
|
+
// Another run's row, same seq as one above.
|
|
1752
|
+
done("r2", 0, {
|
|
1753
|
+
n: "r2"
|
|
1754
|
+
})
|
|
1755
|
+
]);
|
|
1756
|
+
expect3(wrote).toEqual([
|
|
1757
|
+
true,
|
|
1758
|
+
false,
|
|
1759
|
+
false,
|
|
1760
|
+
true,
|
|
1761
|
+
false,
|
|
1762
|
+
true
|
|
1763
|
+
]);
|
|
1764
|
+
const r1 = await store.listCheckpoints("r1");
|
|
1765
|
+
expect3(r1.map((c) => [
|
|
1766
|
+
c.seq,
|
|
1767
|
+
c.status,
|
|
1768
|
+
c.output
|
|
1769
|
+
])).toEqual([
|
|
1770
|
+
[
|
|
1771
|
+
0,
|
|
1772
|
+
"completed",
|
|
1773
|
+
{
|
|
1774
|
+
n: 0
|
|
1775
|
+
}
|
|
1776
|
+
],
|
|
1777
|
+
[
|
|
1778
|
+
1,
|
|
1779
|
+
"completed",
|
|
1780
|
+
{
|
|
1781
|
+
first: 1
|
|
1782
|
+
}
|
|
1783
|
+
],
|
|
1784
|
+
[
|
|
1785
|
+
2,
|
|
1786
|
+
"failed",
|
|
1787
|
+
void 0
|
|
1788
|
+
],
|
|
1789
|
+
[
|
|
1790
|
+
3,
|
|
1791
|
+
"pending",
|
|
1792
|
+
void 0
|
|
1793
|
+
]
|
|
1794
|
+
]);
|
|
1795
|
+
expect3(r1[2]?.error).toEqual({
|
|
1796
|
+
message: "boom"
|
|
1797
|
+
});
|
|
1798
|
+
expect3(r1[0]?.wakeAt).toBeUndefined();
|
|
1799
|
+
expect3(r1[3]?.wakeAt).toBe(5e3);
|
|
1800
|
+
expect3(await store.getCheckpoint("r1", 9)).toBeNull();
|
|
1801
|
+
expect3((await store.getCheckpoint("r2", 0))?.output).toEqual({
|
|
1802
|
+
n: "r2"
|
|
1803
|
+
});
|
|
1804
|
+
expect3(await store.saveCheckpointsIfPending([
|
|
1805
|
+
{
|
|
1806
|
+
...pending("r1", 3),
|
|
1807
|
+
wakeAt: 9e3
|
|
1808
|
+
},
|
|
1809
|
+
done("r1", 3, {
|
|
1810
|
+
n: 3
|
|
1811
|
+
})
|
|
1812
|
+
])).toEqual([
|
|
1813
|
+
true,
|
|
1814
|
+
true
|
|
1815
|
+
]);
|
|
1816
|
+
expect3((await store.getCheckpoint("r1", 3))?.output).toEqual({
|
|
1817
|
+
n: 3
|
|
1818
|
+
});
|
|
1819
|
+
});
|
|
1820
|
+
t("saveCheckpointsIfPending writes a wide batch", async () => {
|
|
1821
|
+
if (!store.saveCheckpointsIfPending) return;
|
|
1822
|
+
await store.createRun(run());
|
|
1823
|
+
const n = 600;
|
|
1824
|
+
const rows = Array.from({
|
|
1825
|
+
length: n
|
|
1826
|
+
}, (_, seq) => checkpoint({
|
|
1827
|
+
seq,
|
|
1828
|
+
stepId: `r1:${seq}`,
|
|
1829
|
+
status: "pending",
|
|
1830
|
+
output: void 0
|
|
1831
|
+
}));
|
|
1832
|
+
for (const row of rows) await store.saveCheckpoint(row);
|
|
1833
|
+
const wrote = await store.saveCheckpointsIfPending(rows.map((row) => ({
|
|
1834
|
+
...row,
|
|
1835
|
+
status: "completed",
|
|
1836
|
+
output: {
|
|
1837
|
+
seq: row.seq
|
|
1838
|
+
}
|
|
1839
|
+
})));
|
|
1840
|
+
expect3(wrote.every((w) => w === true)).toBe(true);
|
|
1841
|
+
expect3(wrote).toHaveLength(n);
|
|
1842
|
+
expect3(await store.countCheckpoints?.("r1", 0, n - 1, [
|
|
1843
|
+
"completed"
|
|
1844
|
+
])).toBe(n);
|
|
1845
|
+
expect3((await store.getCheckpoint("r1", n - 1))?.output).toEqual({
|
|
1846
|
+
seq: n - 1
|
|
1847
|
+
});
|
|
1200
1848
|
});
|
|
1201
1849
|
t("upserts checkpoints, reads them by (runId, seq), and lists them ordered by seq", async () => {
|
|
1202
1850
|
await store.createRun(run());
|
|
@@ -1214,13 +1862,13 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1214
1862
|
again: true
|
|
1215
1863
|
}
|
|
1216
1864
|
}));
|
|
1217
|
-
|
|
1865
|
+
expect3((await store.getCheckpoint("r1", 0))?.output).toEqual({
|
|
1218
1866
|
ok: true,
|
|
1219
1867
|
again: true
|
|
1220
1868
|
});
|
|
1221
|
-
|
|
1869
|
+
expect3((await store.getCheckpoint("r1", 1))?.name).toBe("charge");
|
|
1222
1870
|
const list = await store.listCheckpoints("r1");
|
|
1223
|
-
|
|
1871
|
+
expect3(list.map((c) => c.seq)).toEqual([
|
|
1224
1872
|
0,
|
|
1225
1873
|
1
|
|
1226
1874
|
]);
|
|
@@ -1256,14 +1904,14 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1256
1904
|
}
|
|
1257
1905
|
}));
|
|
1258
1906
|
const getLatestCheckpointByName = store.getLatestCheckpointByName;
|
|
1259
|
-
|
|
1907
|
+
expect3(getLatestCheckpointByName).toBeDefined();
|
|
1260
1908
|
if (!getLatestCheckpointByName) return;
|
|
1261
1909
|
const latest = await getLatestCheckpointByName.call(store, "r1", "event:progress");
|
|
1262
|
-
|
|
1263
|
-
|
|
1910
|
+
expect3(latest?.seq).toBe(3);
|
|
1911
|
+
expect3(latest?.output).toEqual({
|
|
1264
1912
|
pct: 90
|
|
1265
1913
|
});
|
|
1266
|
-
|
|
1914
|
+
expect3(await getLatestCheckpointByName.call(store, "r1", "event:missing")).toBeUndefined();
|
|
1267
1915
|
});
|
|
1268
1916
|
t("listCheckpointsByNamePrefix returns prefix matches ordered by seq (empty => none)", async () => {
|
|
1269
1917
|
await store.createRun(run());
|
|
@@ -1290,18 +1938,48 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1290
1938
|
output: "child-b"
|
|
1291
1939
|
}));
|
|
1292
1940
|
const listCheckpointsByNamePrefix = store.listCheckpointsByNamePrefix;
|
|
1293
|
-
|
|
1941
|
+
expect3(listCheckpointsByNamePrefix).toBeDefined();
|
|
1294
1942
|
if (!listCheckpointsByNamePrefix) return;
|
|
1295
1943
|
const matches = await listCheckpointsByNamePrefix.call(store, "r1", [
|
|
1296
1944
|
"signal:child:",
|
|
1297
1945
|
"spawn:"
|
|
1298
1946
|
]);
|
|
1299
|
-
|
|
1947
|
+
expect3(matches.map((c) => c.name)).toEqual([
|
|
1300
1948
|
"spawn:0",
|
|
1301
1949
|
"signal:child:c2",
|
|
1302
1950
|
"spawn:1"
|
|
1303
1951
|
]);
|
|
1304
|
-
|
|
1952
|
+
expect3(await listCheckpointsByNamePrefix.call(store, "r1", [])).toEqual([]);
|
|
1953
|
+
});
|
|
1954
|
+
t("a name or token prefix matches literally \u2014 `_` and `%` are not wildcards", async () => {
|
|
1955
|
+
await store.createRun(run());
|
|
1956
|
+
await store.saveCheckpoint(checkpoint({
|
|
1957
|
+
seq: 0,
|
|
1958
|
+
name: "signal:child:job_1.onError.0"
|
|
1959
|
+
}));
|
|
1960
|
+
await store.saveCheckpoint(checkpoint({
|
|
1961
|
+
seq: 1,
|
|
1962
|
+
name: "signal:child:jobX1.onError.0"
|
|
1963
|
+
}));
|
|
1964
|
+
await store.putSignalWaiter({
|
|
1965
|
+
token: "child:job_1.child.0",
|
|
1966
|
+
runId: "r1",
|
|
1967
|
+
seq: 2
|
|
1968
|
+
});
|
|
1969
|
+
await store.putSignalWaiter({
|
|
1970
|
+
token: "child:jobX1.child.0",
|
|
1971
|
+
runId: "r1",
|
|
1972
|
+
seq: 3
|
|
1973
|
+
});
|
|
1974
|
+
const matches = await store.listCheckpointsByNamePrefix?.("r1", [
|
|
1975
|
+
"signal:child:job_1."
|
|
1976
|
+
]);
|
|
1977
|
+
expect3(matches?.map((c) => c.name)).toEqual([
|
|
1978
|
+
"signal:child:job_1.onError.0"
|
|
1979
|
+
]);
|
|
1980
|
+
expect3((await store.listSignalWaiters("child:job_1.")).map((w) => w.token)).toEqual([
|
|
1981
|
+
"child:job_1.child.0"
|
|
1982
|
+
]);
|
|
1305
1983
|
});
|
|
1306
1984
|
t("round-trips checkpoint events and reads back a missing checkpoint as null", async () => {
|
|
1307
1985
|
await store.createRun(run());
|
|
@@ -1316,7 +1994,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1316
1994
|
}
|
|
1317
1995
|
]
|
|
1318
1996
|
}));
|
|
1319
|
-
|
|
1997
|
+
expect3((await store.getCheckpoint("r1", 0))?.events).toEqual([
|
|
1320
1998
|
{
|
|
1321
1999
|
at: 1,
|
|
1322
2000
|
level: "error",
|
|
@@ -1325,7 +2003,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1325
2003
|
status: "failed"
|
|
1326
2004
|
}
|
|
1327
2005
|
]);
|
|
1328
|
-
|
|
2006
|
+
expect3(await store.getCheckpoint("r1", 99)).toBeNull();
|
|
1329
2007
|
});
|
|
1330
2008
|
t("lists incomplete runs, pending runs (FIFO), and due timers", async () => {
|
|
1331
2009
|
await store.createRun(run({
|
|
@@ -1351,20 +2029,20 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1351
2029
|
status: "pending",
|
|
1352
2030
|
createdAt: /* @__PURE__ */ new Date("2026-06-11T00:00:01.000Z")
|
|
1353
2031
|
}));
|
|
1354
|
-
|
|
2032
|
+
expect3((await store.listIncompleteRuns()).map((r) => r.id)).toEqual([
|
|
1355
2033
|
"running1"
|
|
1356
2034
|
]);
|
|
1357
|
-
|
|
2035
|
+
expect3((await store.listPendingRuns(10)).map((r) => r.id)).toEqual([
|
|
1358
2036
|
"pending1",
|
|
1359
2037
|
"pending2"
|
|
1360
2038
|
]);
|
|
1361
|
-
|
|
2039
|
+
expect3((await store.listPendingRuns(1)).map((r) => r.id)).toEqual([
|
|
1362
2040
|
"pending1"
|
|
1363
2041
|
]);
|
|
1364
|
-
|
|
2042
|
+
expect3((await store.listDueTimers(1e4)).map((r) => r.id)).toEqual([
|
|
1365
2043
|
"suspended1"
|
|
1366
2044
|
]);
|
|
1367
|
-
|
|
2045
|
+
expect3(await store.listDueTimers(1e3)).toHaveLength(0);
|
|
1368
2046
|
});
|
|
1369
2047
|
t("keeps a worker inside its own namespace on every path that picks work up", async () => {
|
|
1370
2048
|
await store.createRun(run({
|
|
@@ -1399,33 +2077,33 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1399
2077
|
wakeAt: 5e3,
|
|
1400
2078
|
namespace: "beta"
|
|
1401
2079
|
}));
|
|
1402
|
-
|
|
2080
|
+
expect3((await store.listPendingRuns(10, "alpha")).map((r) => r.id)).toEqual([
|
|
1403
2081
|
"a-pending"
|
|
1404
2082
|
]);
|
|
1405
|
-
|
|
2083
|
+
expect3((await store.listIncompleteRuns("alpha")).map((r) => r.id)).toEqual([
|
|
1406
2084
|
"a-running"
|
|
1407
2085
|
]);
|
|
1408
|
-
|
|
2086
|
+
expect3((await store.listDueTimers(1e4, "alpha")).map((r) => r.id)).toEqual([
|
|
1409
2087
|
"a-timer"
|
|
1410
2088
|
]);
|
|
1411
|
-
|
|
2089
|
+
expect3((await store.listRuns({
|
|
1412
2090
|
namespace: "alpha",
|
|
1413
2091
|
status: "running"
|
|
1414
2092
|
})).map((r) => r.id)).toEqual([
|
|
1415
2093
|
"a-running"
|
|
1416
2094
|
]);
|
|
1417
|
-
|
|
2095
|
+
expect3((await store.listPendingRuns(1, "alpha")).map((r) => r.id)).toEqual([
|
|
1418
2096
|
"a-pending"
|
|
1419
2097
|
]);
|
|
1420
|
-
|
|
2098
|
+
expect3((await store.listPendingRuns(10)).map((r) => r.id).sort()).toEqual([
|
|
1421
2099
|
"a-pending",
|
|
1422
2100
|
"b-pending"
|
|
1423
2101
|
]);
|
|
1424
|
-
|
|
2102
|
+
expect3((await store.listIncompleteRuns()).map((r) => r.id).sort()).toEqual([
|
|
1425
2103
|
"a-running",
|
|
1426
2104
|
"b-running"
|
|
1427
2105
|
]);
|
|
1428
|
-
|
|
2106
|
+
expect3((await store.listDueTimers(1e4)).map((r) => r.id).sort()).toEqual([
|
|
1429
2107
|
"a-timer",
|
|
1430
2108
|
"b-timer"
|
|
1431
2109
|
]);
|
|
@@ -1434,20 +2112,20 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1434
2112
|
await store.createRun(run({
|
|
1435
2113
|
id: "r1"
|
|
1436
2114
|
}));
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
2115
|
+
expect3(await store.tryLockRun("r1", "A", 2e3, 1e3)).toBe(true);
|
|
2116
|
+
expect3(await store.tryLockRun("r1", "B", 3e3, 1500)).toBe(false);
|
|
2117
|
+
expect3(await store.tryLockRun("r1", "B", 4e3, 2500)).toBe(true);
|
|
1440
2118
|
await store.releaseRunLock("r1");
|
|
1441
|
-
|
|
2119
|
+
expect3(await store.tryLockRun("r1", "C", 9e3, 2600)).toBe(true);
|
|
1442
2120
|
});
|
|
1443
2121
|
t("renewRunLock only succeeds for the current owner", async () => {
|
|
1444
2122
|
await store.createRun(run({
|
|
1445
2123
|
id: "r1"
|
|
1446
2124
|
}));
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
2125
|
+
expect3(await store.tryLockRun("r1", "A", 2e3, 1e3)).toBe(true);
|
|
2126
|
+
expect3(await store.renewRunLock("r1", "A", 5e3)).toBe(true);
|
|
2127
|
+
expect3(await store.renewRunLock("r1", "B", 9e3)).toBe(false);
|
|
2128
|
+
expect3(await store.tryLockRun("r1", "B", 6e3, 4e3)).toBe(false);
|
|
1451
2129
|
});
|
|
1452
2130
|
t("filters listRuns by workflow and by status", async () => {
|
|
1453
2131
|
await store.createRun(run({
|
|
@@ -1465,19 +2143,19 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1465
2143
|
workflow: "checkout",
|
|
1466
2144
|
status: "completed"
|
|
1467
2145
|
}));
|
|
1468
|
-
|
|
2146
|
+
expect3((await store.listRuns({
|
|
1469
2147
|
workflow: "checkout"
|
|
1470
2148
|
})).map((r) => r.id).sort()).toEqual([
|
|
1471
2149
|
"a",
|
|
1472
2150
|
"c"
|
|
1473
2151
|
]);
|
|
1474
|
-
|
|
2152
|
+
expect3((await store.listRuns({
|
|
1475
2153
|
status: "running"
|
|
1476
2154
|
})).map((r) => r.id).sort()).toEqual([
|
|
1477
2155
|
"a",
|
|
1478
2156
|
"b"
|
|
1479
2157
|
]);
|
|
1480
|
-
|
|
2158
|
+
expect3((await store.listRuns({
|
|
1481
2159
|
workflow: "checkout",
|
|
1482
2160
|
status: "running"
|
|
1483
2161
|
})).map((r) => r.id)).toEqual([
|
|
@@ -1501,7 +2179,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1501
2179
|
id: "d",
|
|
1502
2180
|
status: "pending"
|
|
1503
2181
|
}));
|
|
1504
|
-
|
|
2182
|
+
expect3((await store.listRuns({
|
|
1505
2183
|
statuses: [
|
|
1506
2184
|
"running",
|
|
1507
2185
|
"suspended"
|
|
@@ -1510,7 +2188,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1510
2188
|
"a",
|
|
1511
2189
|
"b"
|
|
1512
2190
|
]);
|
|
1513
|
-
|
|
2191
|
+
expect3((await store.listRuns({
|
|
1514
2192
|
status: "running",
|
|
1515
2193
|
statuses: [
|
|
1516
2194
|
"running",
|
|
@@ -1519,7 +2197,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1519
2197
|
})).map((r) => r.id)).toEqual([
|
|
1520
2198
|
"a"
|
|
1521
2199
|
]);
|
|
1522
|
-
|
|
2200
|
+
expect3(await store.listRuns({
|
|
1523
2201
|
statuses: []
|
|
1524
2202
|
})).toHaveLength(0);
|
|
1525
2203
|
});
|
|
@@ -1541,21 +2219,21 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1541
2219
|
await store.createRun(run({
|
|
1542
2220
|
id: "c"
|
|
1543
2221
|
}));
|
|
1544
|
-
|
|
2222
|
+
expect3((await store.getRun("a"))?.tags).toEqual([
|
|
1545
2223
|
"etl",
|
|
1546
2224
|
"critical"
|
|
1547
2225
|
]);
|
|
1548
|
-
|
|
2226
|
+
expect3((await store.listRuns({
|
|
1549
2227
|
tag: "etl"
|
|
1550
2228
|
})).map((r) => r.id)).toEqual([
|
|
1551
2229
|
"a"
|
|
1552
2230
|
]);
|
|
1553
|
-
|
|
2231
|
+
expect3((await store.listRuns({
|
|
1554
2232
|
tag: "etl-foo"
|
|
1555
2233
|
})).map((r) => r.id)).toEqual([
|
|
1556
2234
|
"b"
|
|
1557
2235
|
]);
|
|
1558
|
-
|
|
2236
|
+
expect3(await store.listRuns({
|
|
1559
2237
|
tag: "nope"
|
|
1560
2238
|
})).toHaveLength(0);
|
|
1561
2239
|
});
|
|
@@ -1571,17 +2249,17 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1571
2249
|
await store.createRun(run({
|
|
1572
2250
|
id: "c"
|
|
1573
2251
|
}));
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
2252
|
+
expect3((await store.getRun("a"))?.origin).toBe("@dudousxd/nestjs-catalog-pipeline");
|
|
2253
|
+
expect3((await store.getRun("c"))?.origin).toBeUndefined();
|
|
2254
|
+
expect3((await store.listRuns({
|
|
1577
2255
|
origin: "@dudousxd/nestjs-catalog-pipeline"
|
|
1578
2256
|
})).map((r) => r.id)).toEqual([
|
|
1579
2257
|
"a"
|
|
1580
2258
|
]);
|
|
1581
|
-
|
|
2259
|
+
expect3(await store.listRuns({
|
|
1582
2260
|
origin: "unknown"
|
|
1583
2261
|
})).toEqual([]);
|
|
1584
|
-
|
|
2262
|
+
expect3((await store.listRuns({})).map((r) => r.id).sort()).toEqual([
|
|
1585
2263
|
"a",
|
|
1586
2264
|
"b",
|
|
1587
2265
|
"c"
|
|
@@ -1598,7 +2276,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1598
2276
|
await store.createRun(run({
|
|
1599
2277
|
id: "c"
|
|
1600
2278
|
}));
|
|
1601
|
-
|
|
2279
|
+
expect3((await store.listRuns({
|
|
1602
2280
|
origin: null
|
|
1603
2281
|
})).map((r) => r.id).sort()).toEqual([
|
|
1604
2282
|
"b",
|
|
@@ -1608,7 +2286,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1608
2286
|
id: "d",
|
|
1609
2287
|
status: "dead"
|
|
1610
2288
|
}));
|
|
1611
|
-
|
|
2289
|
+
expect3((await store.listRuns({
|
|
1612
2290
|
origin: null,
|
|
1613
2291
|
status: "dead"
|
|
1614
2292
|
})).map((r) => r.id)).toEqual([
|
|
@@ -1641,22 +2319,22 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1641
2319
|
status: "failed"
|
|
1642
2320
|
}));
|
|
1643
2321
|
const facets = await store.runFacets({});
|
|
1644
|
-
|
|
2322
|
+
expect3(facets).toContainEqual({
|
|
1645
2323
|
status: "completed",
|
|
1646
2324
|
origin: "pkg-a",
|
|
1647
2325
|
count: 2
|
|
1648
2326
|
});
|
|
1649
|
-
|
|
2327
|
+
expect3(facets).toContainEqual({
|
|
1650
2328
|
status: "failed",
|
|
1651
2329
|
origin: "pkg-b",
|
|
1652
2330
|
count: 1
|
|
1653
2331
|
});
|
|
1654
|
-
|
|
2332
|
+
expect3(facets).toContainEqual({
|
|
1655
2333
|
status: "failed",
|
|
1656
2334
|
origin: null,
|
|
1657
2335
|
count: 2
|
|
1658
2336
|
});
|
|
1659
|
-
|
|
2337
|
+
expect3(facets.reduce((n, f) => n + f.count, 0)).toBe(5);
|
|
1660
2338
|
});
|
|
1661
2339
|
t("narrows runFacets on the same predicates listRuns pages", async () => {
|
|
1662
2340
|
if (!store.runFacets) return;
|
|
@@ -1670,7 +2348,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1670
2348
|
status: "failed",
|
|
1671
2349
|
namespace: "globex"
|
|
1672
2350
|
}));
|
|
1673
|
-
|
|
2351
|
+
expect3(await store.runFacets({
|
|
1674
2352
|
namespace: "acme"
|
|
1675
2353
|
})).toEqual([
|
|
1676
2354
|
{
|
|
@@ -1699,7 +2377,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1699
2377
|
namespace: "initech",
|
|
1700
2378
|
workflow: "wc"
|
|
1701
2379
|
}));
|
|
1702
|
-
|
|
2380
|
+
expect3((await store.listRuns({
|
|
1703
2381
|
statuses: [
|
|
1704
2382
|
"failed",
|
|
1705
2383
|
"dead"
|
|
@@ -1708,7 +2386,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1708
2386
|
"a",
|
|
1709
2387
|
"b"
|
|
1710
2388
|
]);
|
|
1711
|
-
|
|
2389
|
+
expect3((await store.listRuns({
|
|
1712
2390
|
namespaces: [
|
|
1713
2391
|
"acme",
|
|
1714
2392
|
"globex"
|
|
@@ -1717,7 +2395,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1717
2395
|
"a",
|
|
1718
2396
|
"b"
|
|
1719
2397
|
]);
|
|
1720
|
-
|
|
2398
|
+
expect3((await store.listRuns({
|
|
1721
2399
|
workflows: [
|
|
1722
2400
|
"wa",
|
|
1723
2401
|
"wc"
|
|
@@ -1739,7 +2417,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1739
2417
|
await store.createRun(run({
|
|
1740
2418
|
id: "c"
|
|
1741
2419
|
}));
|
|
1742
|
-
|
|
2420
|
+
expect3((await store.listRuns({
|
|
1743
2421
|
origins: [
|
|
1744
2422
|
"pkg-a",
|
|
1745
2423
|
null
|
|
@@ -1748,7 +2426,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1748
2426
|
"a",
|
|
1749
2427
|
"c"
|
|
1750
2428
|
]);
|
|
1751
|
-
|
|
2429
|
+
expect3((await store.listRuns({
|
|
1752
2430
|
origins: [
|
|
1753
2431
|
"pkg-a",
|
|
1754
2432
|
"pkg-b"
|
|
@@ -1769,7 +2447,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1769
2447
|
status: "dead",
|
|
1770
2448
|
namespace: "acme"
|
|
1771
2449
|
}));
|
|
1772
|
-
|
|
2450
|
+
expect3((await store.listRuns({
|
|
1773
2451
|
namespaces: [
|
|
1774
2452
|
"acme"
|
|
1775
2453
|
],
|
|
@@ -1779,13 +2457,13 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1779
2457
|
})).map((r) => r.id)).toEqual([
|
|
1780
2458
|
"b"
|
|
1781
2459
|
]);
|
|
1782
|
-
|
|
2460
|
+
expect3(await store.listRuns({
|
|
1783
2461
|
namespaces: []
|
|
1784
2462
|
})).toEqual([]);
|
|
1785
|
-
|
|
2463
|
+
expect3(await store.listRuns({
|
|
1786
2464
|
statuses: []
|
|
1787
2465
|
})).toEqual([]);
|
|
1788
|
-
|
|
2466
|
+
expect3(await store.listRuns({
|
|
1789
2467
|
origins: []
|
|
1790
2468
|
})).toEqual([]);
|
|
1791
2469
|
});
|
|
@@ -1810,7 +2488,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1810
2488
|
"adhoc"
|
|
1811
2489
|
]
|
|
1812
2490
|
}));
|
|
1813
|
-
|
|
2491
|
+
expect3((await store.listRuns({
|
|
1814
2492
|
tags: [
|
|
1815
2493
|
"etl",
|
|
1816
2494
|
"nightly"
|
|
@@ -1819,7 +2497,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1819
2497
|
"a",
|
|
1820
2498
|
"b"
|
|
1821
2499
|
]);
|
|
1822
|
-
|
|
2500
|
+
expect3(await store.listRuns({
|
|
1823
2501
|
tags: []
|
|
1824
2502
|
})).toEqual([]);
|
|
1825
2503
|
});
|
|
@@ -1860,7 +2538,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1860
2538
|
}
|
|
1861
2539
|
]
|
|
1862
2540
|
});
|
|
1863
|
-
|
|
2541
|
+
expect3(inSet.map((r) => r.id).sort()).toEqual([
|
|
1864
2542
|
"a",
|
|
1865
2543
|
"b"
|
|
1866
2544
|
]);
|
|
@@ -1876,10 +2554,10 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1876
2554
|
}
|
|
1877
2555
|
]
|
|
1878
2556
|
});
|
|
1879
|
-
|
|
2557
|
+
expect3(mixed.map((r) => r.id)).toEqual([
|
|
1880
2558
|
"d"
|
|
1881
2559
|
]);
|
|
1882
|
-
|
|
2560
|
+
expect3(await store.listRuns({
|
|
1883
2561
|
attributes: [
|
|
1884
2562
|
{
|
|
1885
2563
|
key: "tier",
|
|
@@ -1888,7 +2566,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1888
2566
|
}
|
|
1889
2567
|
]
|
|
1890
2568
|
})).toEqual([]);
|
|
1891
|
-
|
|
2569
|
+
expect3((await store.listRuns({
|
|
1892
2570
|
attributes: [
|
|
1893
2571
|
{
|
|
1894
2572
|
key: "tier",
|
|
@@ -1916,7 +2594,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1916
2594
|
id: "c",
|
|
1917
2595
|
namespace: "globex"
|
|
1918
2596
|
}));
|
|
1919
|
-
|
|
2597
|
+
expect3(await store.runValueFacets({
|
|
1920
2598
|
field: "namespace"
|
|
1921
2599
|
}, {})).toEqual([
|
|
1922
2600
|
{
|
|
@@ -1941,11 +2619,11 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1941
2619
|
const values = await store.runValueFacets({
|
|
1942
2620
|
field: "origin"
|
|
1943
2621
|
}, {});
|
|
1944
|
-
|
|
2622
|
+
expect3(values).toContainEqual({
|
|
1945
2623
|
value: "pkg-a",
|
|
1946
2624
|
count: 1
|
|
1947
2625
|
});
|
|
1948
|
-
|
|
2626
|
+
expect3(values).toContainEqual({
|
|
1949
2627
|
value: null,
|
|
1950
2628
|
count: 1
|
|
1951
2629
|
});
|
|
@@ -1967,7 +2645,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1967
2645
|
"nightly"
|
|
1968
2646
|
]
|
|
1969
2647
|
}));
|
|
1970
|
-
|
|
2648
|
+
expect3(await store.runValueFacets({
|
|
1971
2649
|
field: "tag"
|
|
1972
2650
|
}, {
|
|
1973
2651
|
namespace: "acme"
|
|
@@ -1999,7 +2677,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1999
2677
|
tier: "free"
|
|
2000
2678
|
}
|
|
2001
2679
|
}));
|
|
2002
|
-
|
|
2680
|
+
expect3(await store.runValueFacets({
|
|
2003
2681
|
field: "tag"
|
|
2004
2682
|
}, {})).toEqual([
|
|
2005
2683
|
{
|
|
@@ -2011,7 +2689,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2011
2689
|
count: 1
|
|
2012
2690
|
}
|
|
2013
2691
|
]);
|
|
2014
|
-
|
|
2692
|
+
expect3(await store.runValueFacets({
|
|
2015
2693
|
field: "attributeKey"
|
|
2016
2694
|
}, {})).toEqual([
|
|
2017
2695
|
{
|
|
@@ -2019,7 +2697,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2019
2697
|
count: 2
|
|
2020
2698
|
}
|
|
2021
2699
|
]);
|
|
2022
|
-
|
|
2700
|
+
expect3((await store.runValueFacets({
|
|
2023
2701
|
field: "attributeValue",
|
|
2024
2702
|
key: "tier"
|
|
2025
2703
|
}, {})).map((r) => r.value).sort()).toEqual([
|
|
@@ -2050,18 +2728,18 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2050
2728
|
}, {}, {
|
|
2051
2729
|
search: "acme"
|
|
2052
2730
|
});
|
|
2053
|
-
|
|
2731
|
+
expect3(matches.map((row) => row.value).sort()).toEqual([
|
|
2054
2732
|
"acme-prod",
|
|
2055
2733
|
"acme-staging"
|
|
2056
2734
|
]);
|
|
2057
|
-
|
|
2735
|
+
expect3((await store.runValueFacets({
|
|
2058
2736
|
field: "namespace"
|
|
2059
2737
|
}, {}, {
|
|
2060
2738
|
limit: 1
|
|
2061
2739
|
})).map((r) => r.value)).toEqual([
|
|
2062
2740
|
"globex"
|
|
2063
2741
|
]);
|
|
2064
|
-
|
|
2742
|
+
expect3((await store.runValueFacets({
|
|
2065
2743
|
field: "namespace"
|
|
2066
2744
|
}, {}, {
|
|
2067
2745
|
limit: 1,
|
|
@@ -2107,7 +2785,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2107
2785
|
limit: 2,
|
|
2108
2786
|
offset: 2
|
|
2109
2787
|
});
|
|
2110
|
-
|
|
2788
|
+
expect3(first).toEqual([
|
|
2111
2789
|
{
|
|
2112
2790
|
value: "one",
|
|
2113
2791
|
count: 3
|
|
@@ -2117,13 +2795,13 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2117
2795
|
count: 2
|
|
2118
2796
|
}
|
|
2119
2797
|
]);
|
|
2120
|
-
|
|
2798
|
+
expect3(second).toEqual([
|
|
2121
2799
|
{
|
|
2122
2800
|
value: "three",
|
|
2123
2801
|
count: 1
|
|
2124
2802
|
}
|
|
2125
2803
|
]);
|
|
2126
|
-
|
|
2804
|
+
expect3(new Set([
|
|
2127
2805
|
...first,
|
|
2128
2806
|
...second
|
|
2129
2807
|
].map((r) => r.value)).size).toBe(3);
|
|
@@ -2151,7 +2829,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2151
2829
|
"etl"
|
|
2152
2830
|
]
|
|
2153
2831
|
}));
|
|
2154
|
-
|
|
2832
|
+
expect3((await store.runValueFacets({
|
|
2155
2833
|
field: "tag"
|
|
2156
2834
|
}, {}, {
|
|
2157
2835
|
search: "type:"
|
|
@@ -2159,7 +2837,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2159
2837
|
"type:mel",
|
|
2160
2838
|
"type:mvr"
|
|
2161
2839
|
]);
|
|
2162
|
-
|
|
2840
|
+
expect3(await store.runValueFacets({
|
|
2163
2841
|
field: "tag"
|
|
2164
2842
|
}, {}, {
|
|
2165
2843
|
limit: 1
|
|
@@ -2184,7 +2862,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2184
2862
|
id: "c",
|
|
2185
2863
|
namespace: "globex"
|
|
2186
2864
|
}));
|
|
2187
|
-
|
|
2865
|
+
expect3(await store.runValueFacets({
|
|
2188
2866
|
field: "namespace"
|
|
2189
2867
|
}, {}, {
|
|
2190
2868
|
limit: 1
|
|
@@ -2196,7 +2874,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2196
2874
|
]);
|
|
2197
2875
|
});
|
|
2198
2876
|
t("countRuns counts the same set listRuns returns, without the paging", async () => {
|
|
2199
|
-
|
|
2877
|
+
expect3(typeof store.countRuns).toBe("function");
|
|
2200
2878
|
await store.createRun(run({
|
|
2201
2879
|
id: "n1",
|
|
2202
2880
|
status: "running",
|
|
@@ -2226,22 +2904,22 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2226
2904
|
"q"
|
|
2227
2905
|
]
|
|
2228
2906
|
}));
|
|
2229
|
-
|
|
2230
|
-
|
|
2907
|
+
expect3(await store.countRuns?.({})).toBe(4);
|
|
2908
|
+
expect3(await store.countRuns?.({
|
|
2231
2909
|
namespace: "a"
|
|
2232
2910
|
})).toBe(3);
|
|
2233
|
-
|
|
2911
|
+
expect3(await store.countRuns?.({
|
|
2234
2912
|
namespace: "a",
|
|
2235
2913
|
statuses: [
|
|
2236
2914
|
"pending",
|
|
2237
2915
|
"running"
|
|
2238
2916
|
]
|
|
2239
2917
|
})).toBe(2);
|
|
2240
|
-
|
|
2918
|
+
expect3(await store.countRuns?.({
|
|
2241
2919
|
statuses: []
|
|
2242
2920
|
})).toBe(0);
|
|
2243
2921
|
if (supportsTagFilter) {
|
|
2244
|
-
|
|
2922
|
+
expect3(await store.countRuns?.({
|
|
2245
2923
|
tag: "q",
|
|
2246
2924
|
statuses: [
|
|
2247
2925
|
"running",
|
|
@@ -2268,28 +2946,28 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2268
2946
|
await engine.start("q-wait", {}, "q1", {
|
|
2269
2947
|
concurrency: quota
|
|
2270
2948
|
});
|
|
2271
|
-
|
|
2949
|
+
expect3((await engine.waitForRun("q1", {
|
|
2272
2950
|
timeoutMs: 2e4
|
|
2273
2951
|
})).status).toBe("suspended");
|
|
2274
|
-
await
|
|
2952
|
+
await expect3(engine.start("q-wait", {}, "q2", {
|
|
2275
2953
|
concurrency: quota
|
|
2276
2954
|
})).rejects.toThrow(/concurrency limit/);
|
|
2277
|
-
|
|
2955
|
+
expect3(await store.getRun("q2")).toBeNull();
|
|
2278
2956
|
await engine.start("q-wait", {}, "q3", {
|
|
2279
2957
|
concurrency: {
|
|
2280
2958
|
...quota,
|
|
2281
2959
|
key: "tenant:b"
|
|
2282
2960
|
}
|
|
2283
2961
|
});
|
|
2284
|
-
|
|
2962
|
+
expect3((await engine.waitForRun("q1", {
|
|
2285
2963
|
timeoutMs: 2e4
|
|
2286
2964
|
})).status).toBe("suspended");
|
|
2287
2965
|
await engine.cancel("q1");
|
|
2288
|
-
|
|
2966
|
+
expect3((await store.getRun("q1"))?.status).toBe("cancelled");
|
|
2289
2967
|
await engine.start("q-wait", {}, "q4", {
|
|
2290
2968
|
concurrency: quota
|
|
2291
2969
|
});
|
|
2292
|
-
|
|
2970
|
+
expect3(await store.getRun("q4")).not.toBeNull();
|
|
2293
2971
|
for (const id of [
|
|
2294
2972
|
"q3",
|
|
2295
2973
|
"q4"
|
|
@@ -2328,10 +3006,10 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2328
3006
|
...over
|
|
2329
3007
|
}), "schedule");
|
|
2330
3008
|
t("saves, reads back, replaces and deletes a schedule", async () => {
|
|
2331
|
-
|
|
3009
|
+
expect3(typeof store.saveSchedule).toBe("function");
|
|
2332
3010
|
await store.saveSchedule?.(schedule());
|
|
2333
3011
|
const loaded = await store.getSchedule?.("s1");
|
|
2334
|
-
|
|
3012
|
+
expect3(loaded).toMatchObject({
|
|
2335
3013
|
id: "s1",
|
|
2336
3014
|
namespace: "default",
|
|
2337
3015
|
workflow: "digest",
|
|
@@ -2357,27 +3035,27 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2357
3035
|
window: NOW + 6e4
|
|
2358
3036
|
}
|
|
2359
3037
|
});
|
|
2360
|
-
|
|
3038
|
+
expect3(loaded?.createdAt.getTime()).toBe(at.getTime());
|
|
2361
3039
|
await store.saveSchedule?.(schedule({
|
|
2362
3040
|
workflow: "digest2",
|
|
2363
3041
|
nextFireAt: null,
|
|
2364
3042
|
tags: void 0
|
|
2365
3043
|
}));
|
|
2366
3044
|
const replaced = await store.getSchedule?.("s1");
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
3045
|
+
expect3(replaced?.workflow).toBe("digest2");
|
|
3046
|
+
expect3(replaced?.nextFireAt).toBeNull();
|
|
3047
|
+
expect3(replaced?.tags ?? []).toEqual([]);
|
|
3048
|
+
expect3(await store.getSchedule?.("nope")).toBeNull();
|
|
3049
|
+
expect3(await store.deleteSchedule?.("s1")).toBe(true);
|
|
3050
|
+
expect3(await store.deleteSchedule?.("s1")).toBe(false);
|
|
3051
|
+
expect3(await store.getSchedule?.("s1")).toBeNull();
|
|
2374
3052
|
});
|
|
2375
3053
|
t("updateSchedule patches, and compare-and-sets on nextFireAt", async () => {
|
|
2376
3054
|
await store.saveSchedule?.(schedule());
|
|
2377
|
-
|
|
3055
|
+
expect3(await store.updateSchedule?.("s1", {
|
|
2378
3056
|
paused: true
|
|
2379
3057
|
})).toBe(true);
|
|
2380
|
-
|
|
3058
|
+
expect3((await store.getSchedule?.("s1"))?.paused).toBe(true);
|
|
2381
3059
|
const next = {
|
|
2382
3060
|
nextFireAt: NOW + 12e4,
|
|
2383
3061
|
state: {
|
|
@@ -2385,24 +3063,24 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2385
3063
|
fires: 1
|
|
2386
3064
|
}
|
|
2387
3065
|
};
|
|
2388
|
-
|
|
2389
|
-
|
|
3066
|
+
expect3(await store.updateSchedule?.("s1", next, NOW + 6e4)).toBe(true);
|
|
3067
|
+
expect3(await store.updateSchedule?.("s1", next, NOW + 6e4)).toBe(false);
|
|
2390
3068
|
const after = await store.getSchedule?.("s1");
|
|
2391
|
-
|
|
2392
|
-
|
|
3069
|
+
expect3(after?.nextFireAt).toBe(NOW + 12e4);
|
|
3070
|
+
expect3(after?.state).toEqual({
|
|
2393
3071
|
window: NOW + 12e4,
|
|
2394
3072
|
fires: 1
|
|
2395
3073
|
});
|
|
2396
3074
|
await store.updateSchedule?.("s1", {
|
|
2397
3075
|
nextFireAt: null
|
|
2398
3076
|
});
|
|
2399
|
-
|
|
3077
|
+
expect3(await store.updateSchedule?.("s1", {
|
|
2400
3078
|
nextFireAt: NOW
|
|
2401
3079
|
}, NOW + 1)).toBe(false);
|
|
2402
|
-
|
|
3080
|
+
expect3(await store.updateSchedule?.("s1", {
|
|
2403
3081
|
nextFireAt: NOW
|
|
2404
3082
|
}, null)).toBe(true);
|
|
2405
|
-
|
|
3083
|
+
expect3(await store.updateSchedule?.("ghost", {
|
|
2406
3084
|
paused: true
|
|
2407
3085
|
})).toBe(false);
|
|
2408
3086
|
});
|
|
@@ -2434,47 +3112,47 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2434
3112
|
]
|
|
2435
3113
|
}));
|
|
2436
3114
|
const ids2 = /* @__PURE__ */ __name(async (q) => (await store.listSchedules?.(q) ?? []).map((r) => r.id), "ids");
|
|
2437
|
-
|
|
3115
|
+
expect3(await ids2({})).toEqual([
|
|
2438
3116
|
"c",
|
|
2439
3117
|
"b",
|
|
2440
3118
|
"a",
|
|
2441
3119
|
"d",
|
|
2442
3120
|
"e"
|
|
2443
3121
|
]);
|
|
2444
|
-
|
|
3122
|
+
expect3(await ids2({
|
|
2445
3123
|
dueBy: NOW
|
|
2446
3124
|
})).toEqual([
|
|
2447
3125
|
"b",
|
|
2448
3126
|
"a"
|
|
2449
3127
|
]);
|
|
2450
|
-
|
|
3128
|
+
expect3(await ids2({
|
|
2451
3129
|
dueBy: NOW,
|
|
2452
3130
|
namespace: "default"
|
|
2453
3131
|
})).toEqual([
|
|
2454
3132
|
"a"
|
|
2455
3133
|
]);
|
|
2456
|
-
|
|
3134
|
+
expect3(await ids2({
|
|
2457
3135
|
dueBy: NOW,
|
|
2458
3136
|
limit: 1
|
|
2459
3137
|
})).toEqual([
|
|
2460
3138
|
"b"
|
|
2461
3139
|
]);
|
|
2462
|
-
|
|
3140
|
+
expect3(await ids2({
|
|
2463
3141
|
workflow: "other"
|
|
2464
3142
|
})).toEqual([
|
|
2465
3143
|
"d"
|
|
2466
3144
|
]);
|
|
2467
|
-
|
|
3145
|
+
expect3(await ids2({
|
|
2468
3146
|
paused: true
|
|
2469
3147
|
})).toEqual([
|
|
2470
3148
|
"c"
|
|
2471
3149
|
]);
|
|
2472
|
-
if (supportsTagFilter)
|
|
3150
|
+
if (supportsTagFilter) expect3(await ids2({
|
|
2473
3151
|
tag: "tenant:b"
|
|
2474
3152
|
})).toEqual([
|
|
2475
3153
|
"e"
|
|
2476
3154
|
]);
|
|
2477
|
-
|
|
3155
|
+
expect3(await ids2({
|
|
2478
3156
|
limit: 2,
|
|
2479
3157
|
offset: 1
|
|
2480
3158
|
})).toEqual([
|
|
@@ -2495,18 +3173,18 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2495
3173
|
id: "new",
|
|
2496
3174
|
createdAt: /* @__PURE__ */ new Date("2026-06-11T00:00:02.000Z")
|
|
2497
3175
|
}));
|
|
2498
|
-
|
|
3176
|
+
expect3((await store.listRuns({})).map((r) => r.id)).toEqual([
|
|
2499
3177
|
"new",
|
|
2500
3178
|
"mid",
|
|
2501
3179
|
"old"
|
|
2502
3180
|
]);
|
|
2503
|
-
|
|
3181
|
+
expect3((await store.listRuns({
|
|
2504
3182
|
limit: 2
|
|
2505
3183
|
})).map((r) => r.id)).toEqual([
|
|
2506
3184
|
"new",
|
|
2507
3185
|
"mid"
|
|
2508
3186
|
]);
|
|
2509
|
-
|
|
3187
|
+
expect3((await store.listRuns({
|
|
2510
3188
|
limit: 2,
|
|
2511
3189
|
offset: 1
|
|
2512
3190
|
})).map((r) => r.id)).toEqual([
|
|
@@ -2536,11 +3214,11 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2536
3214
|
tier: "pro"
|
|
2537
3215
|
}
|
|
2538
3216
|
}));
|
|
2539
|
-
|
|
3217
|
+
expect3((await store.getRun("b"))?.searchAttributes).toEqual({
|
|
2540
3218
|
amount: 200,
|
|
2541
3219
|
tier: "pro"
|
|
2542
3220
|
});
|
|
2543
|
-
|
|
3221
|
+
expect3((await store.listRuns({
|
|
2544
3222
|
attributes: [
|
|
2545
3223
|
{
|
|
2546
3224
|
key: "amount",
|
|
@@ -2552,7 +3230,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2552
3230
|
"b",
|
|
2553
3231
|
"c"
|
|
2554
3232
|
]);
|
|
2555
|
-
|
|
3233
|
+
expect3((await store.listRuns({
|
|
2556
3234
|
attributes: [
|
|
2557
3235
|
{
|
|
2558
3236
|
key: "tier",
|
|
@@ -2568,7 +3246,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2568
3246
|
})).map((r) => r.id)).toEqual([
|
|
2569
3247
|
"b"
|
|
2570
3248
|
]);
|
|
2571
|
-
|
|
3249
|
+
expect3((await store.listRuns({
|
|
2572
3250
|
attributes: [
|
|
2573
3251
|
{
|
|
2574
3252
|
key: "tier",
|
|
@@ -2600,7 +3278,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2600
3278
|
other: 1
|
|
2601
3279
|
}
|
|
2602
3280
|
}));
|
|
2603
|
-
|
|
3281
|
+
expect3((await store.listRuns({
|
|
2604
3282
|
attributes: [
|
|
2605
3283
|
{
|
|
2606
3284
|
key: "vip",
|
|
@@ -2611,7 +3289,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2611
3289
|
})).map((r) => r.id)).toEqual([
|
|
2612
3290
|
"a"
|
|
2613
3291
|
]);
|
|
2614
|
-
|
|
3292
|
+
expect3((await store.listRuns({
|
|
2615
3293
|
attributes: [
|
|
2616
3294
|
{
|
|
2617
3295
|
key: "vip",
|
|
@@ -2638,7 +3316,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2638
3316
|
"lt",
|
|
2639
3317
|
"lte"
|
|
2640
3318
|
]) {
|
|
2641
|
-
|
|
3319
|
+
expect3(await store.listRuns({
|
|
2642
3320
|
attributes: [
|
|
2643
3321
|
{
|
|
2644
3322
|
key: "missing",
|
|
@@ -2657,7 +3335,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2657
3335
|
amount: 10
|
|
2658
3336
|
}
|
|
2659
3337
|
}));
|
|
2660
|
-
|
|
3338
|
+
expect3((await store.listRuns({
|
|
2661
3339
|
attributes: [
|
|
2662
3340
|
{
|
|
2663
3341
|
key: "tier",
|
|
@@ -2673,7 +3351,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2673
3351
|
tier: "pro"
|
|
2674
3352
|
}
|
|
2675
3353
|
});
|
|
2676
|
-
|
|
3354
|
+
expect3((await store.listRuns({
|
|
2677
3355
|
attributes: [
|
|
2678
3356
|
{
|
|
2679
3357
|
key: "tier",
|
|
@@ -2684,7 +3362,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2684
3362
|
})).map((r) => r.id)).toEqual([
|
|
2685
3363
|
"a"
|
|
2686
3364
|
]);
|
|
2687
|
-
|
|
3365
|
+
expect3(await store.listRuns({
|
|
2688
3366
|
attributes: [
|
|
2689
3367
|
{
|
|
2690
3368
|
key: "tier",
|
|
@@ -2693,7 +3371,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2693
3371
|
}
|
|
2694
3372
|
]
|
|
2695
3373
|
})).toHaveLength(0);
|
|
2696
|
-
|
|
3374
|
+
expect3(await store.listRuns({
|
|
2697
3375
|
attributes: [
|
|
2698
3376
|
{
|
|
2699
3377
|
key: "amount",
|
|
@@ -2736,7 +3414,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2736
3414
|
],
|
|
2737
3415
|
limit: 10
|
|
2738
3416
|
});
|
|
2739
|
-
|
|
3417
|
+
expect3(res.map((r) => r.id)).toEqual([
|
|
2740
3418
|
"a"
|
|
2741
3419
|
]);
|
|
2742
3420
|
});
|
|
@@ -2756,12 +3434,12 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2756
3434
|
runId: "r3",
|
|
2757
3435
|
seq: 5
|
|
2758
3436
|
});
|
|
2759
|
-
|
|
3437
|
+
expect3((await store.listSignalWaiters("approve-")).map((w) => w.token).sort()).toEqual([
|
|
2760
3438
|
"approve-1",
|
|
2761
3439
|
"approve-2"
|
|
2762
3440
|
]);
|
|
2763
|
-
|
|
2764
|
-
|
|
3441
|
+
expect3((await store.takeSignalWaiter("approve-1"))?.seq).toBe(3);
|
|
3442
|
+
expect3(await store.takeSignalWaiter("approve-1")).toBeNull();
|
|
2765
3443
|
});
|
|
2766
3444
|
t("round-trips a signal waiter parallelGroup (child-fan tag) through put \u2192 take \u2192 list", async () => {
|
|
2767
3445
|
await store.putSignalWaiter({
|
|
@@ -2776,13 +3454,13 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2776
3454
|
seq: 9
|
|
2777
3455
|
});
|
|
2778
3456
|
const listed = await store.listSignalWaiters("child:");
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
3457
|
+
expect3(listed.find((w) => w.token === "child:r.child.0")?.parallelGroup).toBe("gather:0");
|
|
3458
|
+
expect3(listed.find((w) => w.token === "child:r.child.9")?.parallelGroup).toBeUndefined();
|
|
3459
|
+
expect3((await store.takeSignalWaiter("child:r.child.0"))?.parallelGroup).toBe("gather:0");
|
|
3460
|
+
expect3((await store.takeSignalWaiter("child:r.child.9"))?.parallelGroup).toBeUndefined();
|
|
2783
3461
|
});
|
|
2784
3462
|
t("registerChildWaits writes every waiter and placeholder, and upserts on repeat", async () => {
|
|
2785
|
-
|
|
3463
|
+
expect3(store.registerChildWaits).toBeTypeOf("function");
|
|
2786
3464
|
await store.createRun(run({
|
|
2787
3465
|
id: "p"
|
|
2788
3466
|
}));
|
|
@@ -2808,24 +3486,24 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2808
3486
|
}, (_, i) => entry(i, "running"));
|
|
2809
3487
|
await store.registerChildWaits?.(entries);
|
|
2810
3488
|
const waiters = await store.listSignalWaiters("child:p.all.0.");
|
|
2811
|
-
|
|
2812
|
-
|
|
3489
|
+
expect3(waiters).toHaveLength(250);
|
|
3490
|
+
expect3(waiters.find((w) => w.token === "child:p.all.0.7")).toEqual(entries[7]?.waiter);
|
|
2813
3491
|
const cps = await store.listCheckpoints("p");
|
|
2814
|
-
|
|
2815
|
-
|
|
3492
|
+
expect3(cps).toHaveLength(250);
|
|
3493
|
+
expect3(cps[7]).toMatchObject({
|
|
2816
3494
|
name: "signal:child:p.all.0.7",
|
|
2817
3495
|
status: "running"
|
|
2818
3496
|
});
|
|
2819
|
-
|
|
3497
|
+
expect3(cps[7]?.parallelGroup).toBe("all:0");
|
|
2820
3498
|
await store.registerChildWaits?.([
|
|
2821
3499
|
entry(7, "completed")
|
|
2822
3500
|
]);
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
await
|
|
3501
|
+
expect3((await store.getCheckpoint("p", 7))?.status).toBe("completed");
|
|
3502
|
+
expect3(await store.listCheckpoints("p")).toHaveLength(250);
|
|
3503
|
+
await expect3(store.registerChildWaits?.([])).resolves.toBeUndefined();
|
|
2826
3504
|
});
|
|
2827
3505
|
t("compactCheckpoints replaces a seq range with one checkpoint, atomically", async () => {
|
|
2828
|
-
|
|
3506
|
+
expect3(store.compactCheckpoints).toBeTypeOf("function");
|
|
2829
3507
|
await store.createRun(run({
|
|
2830
3508
|
id: "r"
|
|
2831
3509
|
}));
|
|
@@ -2864,18 +3542,18 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2864
3542
|
});
|
|
2865
3543
|
await store.compactCheckpoints?.("r", 2, 7, into);
|
|
2866
3544
|
const left = await store.listCheckpoints("r");
|
|
2867
|
-
|
|
3545
|
+
expect3(left.map((cp2) => cp2.seq)).toEqual([
|
|
2868
3546
|
0,
|
|
2869
3547
|
1,
|
|
2870
3548
|
2,
|
|
2871
3549
|
8,
|
|
2872
3550
|
9
|
|
2873
3551
|
]);
|
|
2874
|
-
|
|
3552
|
+
expect3(left[2]).toMatchObject({
|
|
2875
3553
|
name: "fan:r.all.2.",
|
|
2876
3554
|
parallelGroup: "all:2"
|
|
2877
3555
|
});
|
|
2878
|
-
|
|
3556
|
+
expect3(left[2]?.output).toEqual({
|
|
2879
3557
|
from: 0,
|
|
2880
3558
|
values: [
|
|
2881
3559
|
{
|
|
@@ -2885,10 +3563,10 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2885
3563
|
null
|
|
2886
3564
|
]
|
|
2887
3565
|
});
|
|
2888
|
-
|
|
3566
|
+
expect3(await store.listCheckpoints("other")).toHaveLength(1);
|
|
2889
3567
|
});
|
|
2890
3568
|
t("listSignalWaitersByRun returns only the given runs' waiters", async () => {
|
|
2891
|
-
|
|
3569
|
+
expect3(store.listSignalWaitersByRun).toBeTypeOf("function");
|
|
2892
3570
|
await store.putSignalWaiter({
|
|
2893
3571
|
token: "child:a.all.0.0",
|
|
2894
3572
|
runId: "a",
|
|
@@ -2911,7 +3589,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2911
3589
|
seq: 0
|
|
2912
3590
|
});
|
|
2913
3591
|
const tokens = /* @__PURE__ */ __name(async (ids2) => (await store.listSignalWaitersByRun?.(ids2) ?? []).map((w) => w.token).sort(), "tokens");
|
|
2914
|
-
|
|
3592
|
+
expect3(await tokens([
|
|
2915
3593
|
"a",
|
|
2916
3594
|
"b"
|
|
2917
3595
|
])).toEqual([
|
|
@@ -2919,11 +3597,11 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2919
3597
|
"approve-b",
|
|
2920
3598
|
"child:a.all.0.0"
|
|
2921
3599
|
]);
|
|
2922
|
-
|
|
3600
|
+
expect3(await tokens([
|
|
2923
3601
|
"missing"
|
|
2924
3602
|
])).toEqual([]);
|
|
2925
|
-
|
|
2926
|
-
|
|
3603
|
+
expect3(await tokens([])).toEqual([]);
|
|
3604
|
+
expect3((await store.listSignalWaitersByRun?.([
|
|
2927
3605
|
"a"
|
|
2928
3606
|
]))?.find((w) => w.seq === 0)).toEqual({
|
|
2929
3607
|
token: "child:a.all.0.0",
|
|
@@ -2933,7 +3611,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2933
3611
|
});
|
|
2934
3612
|
});
|
|
2935
3613
|
t("hasSignalWaiter answers by run and exact token prefix", async () => {
|
|
2936
|
-
|
|
3614
|
+
expect3(store.hasSignalWaiter).toBeTypeOf("function");
|
|
2937
3615
|
const has = /* @__PURE__ */ __name((runId, prefix) => store.hasSignalWaiter?.(runId, prefix), "has");
|
|
2938
3616
|
await store.putSignalWaiter({
|
|
2939
3617
|
token: "child:p_1.all.3.0",
|
|
@@ -2951,13 +3629,13 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2951
3629
|
runId: "p_1",
|
|
2952
3630
|
seq: 9
|
|
2953
3631
|
});
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
3632
|
+
expect3(await has("p_1", "child:p_1.all.3.")).toBe(true);
|
|
3633
|
+
expect3(await has("p_1", "child:p_1.all.4.")).toBe(false);
|
|
3634
|
+
expect3(await has("other", "child:p_1.all.3.")).toBe(false);
|
|
3635
|
+
expect3(await has("p_1", "child:p%.all.3.")).toBe(false);
|
|
3636
|
+
expect3(await has("p_1", "CHILD:p_1.all.3.")).toBe(false);
|
|
2959
3637
|
await store.takeSignalWaiter("child:p_1.all.3.0");
|
|
2960
|
-
|
|
3638
|
+
expect3(await has("p_1", "child:p_1.all.3.")).toBe(false);
|
|
2961
3639
|
});
|
|
2962
3640
|
t("removeSignalWaiter deletes only the exact (token, runId, seq) row, never a different run's", async () => {
|
|
2963
3641
|
await store.putSignalWaiter({
|
|
@@ -2975,7 +3653,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2975
3653
|
runId: "r1",
|
|
2976
3654
|
seq: 3
|
|
2977
3655
|
});
|
|
2978
|
-
|
|
3656
|
+
expect3((await store.listSignalWaiters("approve-")).map((w) => w.runId)).toEqual([
|
|
2979
3657
|
"r2"
|
|
2980
3658
|
]);
|
|
2981
3659
|
await store.removeSignalWaiter({
|
|
@@ -2983,7 +3661,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2983
3661
|
runId: "r2",
|
|
2984
3662
|
seq: 999
|
|
2985
3663
|
});
|
|
2986
|
-
|
|
3664
|
+
expect3((await store.listSignalWaiters("approve-")).map((w) => w.runId)).toEqual([
|
|
2987
3665
|
"r2"
|
|
2988
3666
|
]);
|
|
2989
3667
|
await store.removeSignalWaiter({
|
|
@@ -2991,7 +3669,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2991
3669
|
runId: "r2",
|
|
2992
3670
|
seq: 7
|
|
2993
3671
|
});
|
|
2994
|
-
|
|
3672
|
+
expect3(await store.listSignalWaiters("approve-")).toEqual([]);
|
|
2995
3673
|
await store.removeSignalWaiter({
|
|
2996
3674
|
token: "never-registered",
|
|
2997
3675
|
runId: "r1",
|
|
@@ -3008,18 +3686,18 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3008
3686
|
await store.bufferSignal("other", {
|
|
3009
3687
|
n: 9
|
|
3010
3688
|
});
|
|
3011
|
-
|
|
3689
|
+
expect3(await store.takeBufferedSignal("sig")).toEqual({
|
|
3012
3690
|
payload: {
|
|
3013
3691
|
n: 1
|
|
3014
3692
|
}
|
|
3015
3693
|
});
|
|
3016
|
-
|
|
3694
|
+
expect3(await store.takeBufferedSignal("sig")).toEqual({
|
|
3017
3695
|
payload: {
|
|
3018
3696
|
n: 2
|
|
3019
3697
|
}
|
|
3020
3698
|
});
|
|
3021
|
-
|
|
3022
|
-
|
|
3699
|
+
expect3(await store.takeBufferedSignal("sig")).toBeNull();
|
|
3700
|
+
expect3(await store.takeBufferedSignal("other")).toEqual({
|
|
3023
3701
|
payload: {
|
|
3024
3702
|
n: 9
|
|
3025
3703
|
}
|
|
@@ -3051,7 +3729,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3051
3729
|
publishedAt: 50
|
|
3052
3730
|
});
|
|
3053
3731
|
const listed = await store.listBufferedEvents("order.paid", 10);
|
|
3054
|
-
|
|
3732
|
+
expect3(listed.map((e) => e.payload)).toEqual([
|
|
3055
3733
|
{
|
|
3056
3734
|
n: 1
|
|
3057
3735
|
},
|
|
@@ -3059,13 +3737,13 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3059
3737
|
n: 2
|
|
3060
3738
|
}
|
|
3061
3739
|
]);
|
|
3062
|
-
|
|
3740
|
+
expect3(listed.map((e) => e.id)).toEqual([
|
|
3063
3741
|
"e1",
|
|
3064
3742
|
"e2"
|
|
3065
3743
|
]);
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3744
|
+
expect3(listed[0]?.publishedAt).toBe(100);
|
|
3745
|
+
expect3(await store.listBufferedEvents("order.paid", 10)).toHaveLength(2);
|
|
3746
|
+
expect3(await store.listBufferedEvents("other.event", 10)).toEqual([
|
|
3069
3747
|
{
|
|
3070
3748
|
id: "e3",
|
|
3071
3749
|
payload: {
|
|
@@ -3094,7 +3772,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3094
3772
|
id: "c",
|
|
3095
3773
|
publishedAt: 3
|
|
3096
3774
|
});
|
|
3097
|
-
|
|
3775
|
+
expect3(await store.listBufferedEvents("evt", 2)).toHaveLength(2);
|
|
3098
3776
|
});
|
|
3099
3777
|
t("removeBufferedEvent atomically deletes by id \u2014 true iff a row existed, false on a repeat/absent id", async () => {
|
|
3100
3778
|
await store.bufferEvent({
|
|
@@ -3105,10 +3783,10 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3105
3783
|
id: "x1",
|
|
3106
3784
|
publishedAt: 1
|
|
3107
3785
|
});
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3786
|
+
expect3(await store.removeBufferedEvent("x1")).toBe(true);
|
|
3787
|
+
expect3(await store.listBufferedEvents("evt", 10)).toEqual([]);
|
|
3788
|
+
expect3(await store.removeBufferedEvent("x1")).toBe(false);
|
|
3789
|
+
expect3(await store.removeBufferedEvent("never-buffered")).toBe(false);
|
|
3112
3790
|
});
|
|
3113
3791
|
t("transaction commits the checkpoint atomically and returns the work result", async () => {
|
|
3114
3792
|
if (!store.transaction) return;
|
|
@@ -3124,10 +3802,10 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3124
3802
|
}));
|
|
3125
3803
|
return "ok";
|
|
3126
3804
|
});
|
|
3127
|
-
|
|
3128
|
-
const
|
|
3129
|
-
|
|
3130
|
-
|
|
3805
|
+
expect3(result).toBe("ok");
|
|
3806
|
+
const cp2 = await store.getCheckpoint("r1", 7);
|
|
3807
|
+
expect3(cp2?.name).toBe("tx-step");
|
|
3808
|
+
expect3(cp2?.output).toEqual({
|
|
3131
3809
|
paid: true
|
|
3132
3810
|
});
|
|
3133
3811
|
});
|
|
@@ -3154,11 +3832,11 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3154
3832
|
await engine.start("wf", {
|
|
3155
3833
|
x: 1
|
|
3156
3834
|
}, "run1");
|
|
3157
|
-
|
|
3835
|
+
expect3((await engine.waitForRun("run1")).status).toBe("failed");
|
|
3158
3836
|
const resumed = await engine.resume("run1");
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3837
|
+
expect3(resumed.status).toBe("completed");
|
|
3838
|
+
expect3(resumed.output).toBe(15);
|
|
3839
|
+
expect3(aRuns).toBe(1);
|
|
3162
3840
|
});
|
|
3163
3841
|
t("starts the version the caller pinned, and refuses one that is not registered", async () => {
|
|
3164
3842
|
const engine = new WorkflowEngine3({
|
|
@@ -3179,19 +3857,19 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3179
3857
|
const pinned = await engine.waitForRun("pinned-run", {
|
|
3180
3858
|
timeoutMs: 2e4
|
|
3181
3859
|
});
|
|
3182
|
-
|
|
3183
|
-
|
|
3860
|
+
expect3(pinned.output).toBe("v1-output");
|
|
3861
|
+
expect3(ran).toEqual([
|
|
3184
3862
|
"v1"
|
|
3185
3863
|
]);
|
|
3186
|
-
|
|
3864
|
+
expect3((await store.getRun("pinned-run"))?.workflowVersion).toBe("1");
|
|
3187
3865
|
await engine.start("vwf", {}, "newest-run");
|
|
3188
|
-
|
|
3866
|
+
expect3((await engine.waitForRun("newest-run", {
|
|
3189
3867
|
timeoutMs: 2e4
|
|
3190
3868
|
})).output).toBe("v2-output");
|
|
3191
|
-
await
|
|
3869
|
+
await expect3(engine.start("vwf", {}, "ghost-run", {
|
|
3192
3870
|
version: "9"
|
|
3193
3871
|
})).rejects.toThrow(/vwf@9 is not registered/);
|
|
3194
|
-
|
|
3872
|
+
expect3(await store.getRun("ghost-run")).toBeNull();
|
|
3195
3873
|
});
|
|
3196
3874
|
t("cancels a timed-out run\u2019s live child subtree, not just the run itself", async () => {
|
|
3197
3875
|
const engine = new WorkflowEngine3({
|
|
@@ -3214,16 +3892,16 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3214
3892
|
"tc",
|
|
3215
3893
|
"tgc"
|
|
3216
3894
|
]) {
|
|
3217
|
-
|
|
3895
|
+
expect3((await engine.waitForRun(id, {
|
|
3218
3896
|
timeoutMs: 2e4
|
|
3219
3897
|
})).status).toBe("suspended");
|
|
3220
3898
|
}
|
|
3221
3899
|
await engine.sweepTimeouts(Date.now() + 37e5);
|
|
3222
3900
|
const parent = await store.getRun("tp");
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3901
|
+
expect3(parent?.status).toBe("cancelled");
|
|
3902
|
+
expect3(parent?.error?.code).toBe("execution_timeout");
|
|
3903
|
+
expect3((await store.getRun("tc"))?.status).toBe("cancelled");
|
|
3904
|
+
expect3((await store.getRun("tgc"))?.status).toBe("cancelled");
|
|
3227
3905
|
});
|
|
3228
3906
|
});
|
|
3229
3907
|
}
|
|
@@ -3295,6 +3973,7 @@ export {
|
|
|
3295
3973
|
DURABLE_CANONICAL_COLUMNS,
|
|
3296
3974
|
MutableClock,
|
|
3297
3975
|
StateStoreUnavailableError,
|
|
3976
|
+
aggregationFixture,
|
|
3298
3977
|
assertDurableColumns,
|
|
3299
3978
|
assertOutput,
|
|
3300
3979
|
assertReplayable,
|
|
@@ -3307,6 +3986,7 @@ export {
|
|
|
3307
3986
|
failTimes,
|
|
3308
3987
|
preserveColumnExpectation,
|
|
3309
3988
|
recordedSteps,
|
|
3989
|
+
registerCheckpointAggregationCases,
|
|
3310
3990
|
runAdmissionBackendContract,
|
|
3311
3991
|
runStateStoreContract
|
|
3312
3992
|
};
|