@dudousxd/nestjs-durable-testing 0.13.0 → 0.15.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 +12 -0
- package/dist/index.js +1060 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/dist/index.js
CHANGED
|
@@ -287,6 +287,19 @@ var DURABLE_CANONICAL_COLUMNS = {
|
|
|
287
287
|
name: "name",
|
|
288
288
|
payload: "payload",
|
|
289
289
|
publishedAt: "published_at"
|
|
290
|
+
},
|
|
291
|
+
// Persisted schedules (`engine.schedules`). `spec`/`state` are engine-owned JSON documents.
|
|
292
|
+
durable_schedules: {
|
|
293
|
+
id: "id",
|
|
294
|
+
namespace: "namespace",
|
|
295
|
+
workflow: "workflow",
|
|
296
|
+
paused: "paused",
|
|
297
|
+
nextFireAt: "next_fire_at",
|
|
298
|
+
tags: "tags",
|
|
299
|
+
spec: "spec",
|
|
300
|
+
state: "state",
|
|
301
|
+
createdAt: "created_at",
|
|
302
|
+
updatedAt: "updated_at"
|
|
290
303
|
}
|
|
291
304
|
};
|
|
292
305
|
function assertDurableColumns(resolve, expected = DURABLE_CANONICAL_COLUMNS) {
|
|
@@ -403,6 +416,8 @@ function isUnavailable(err) {
|
|
|
403
416
|
}
|
|
404
417
|
__name(isUnavailable, "isUnavailable");
|
|
405
418
|
var at = /* @__PURE__ */ new Date("2026-06-11T00:00:00.000Z");
|
|
419
|
+
var NOW = Date.parse("2026-06-20T00:00:00.000Z");
|
|
420
|
+
var DAY = 864e5;
|
|
406
421
|
var run = /* @__PURE__ */ __name((over = {}) => ({
|
|
407
422
|
id: "r1",
|
|
408
423
|
workflow: "checkout",
|
|
@@ -544,6 +559,249 @@ function runStateStoreContract(name, makeStore) {
|
|
|
544
559
|
t("deleteRun is a no-op for a missing run", async () => {
|
|
545
560
|
await expect2(store.deleteRun("nope")).resolves.toBeUndefined();
|
|
546
561
|
});
|
|
562
|
+
t("deleteRuns removes every listed run with its rows and ignores missing ids", async () => {
|
|
563
|
+
await store.createRun(run({
|
|
564
|
+
id: "d1",
|
|
565
|
+
searchAttributes: {
|
|
566
|
+
tier: "pro"
|
|
567
|
+
}
|
|
568
|
+
}));
|
|
569
|
+
await store.createRun(run({
|
|
570
|
+
id: "d2"
|
|
571
|
+
}));
|
|
572
|
+
await store.createRun(run({
|
|
573
|
+
id: "keep"
|
|
574
|
+
}));
|
|
575
|
+
await store.saveCheckpoint(checkpoint({
|
|
576
|
+
runId: "d1",
|
|
577
|
+
stepId: "d1:0"
|
|
578
|
+
}));
|
|
579
|
+
await store.putSignalWaiter({
|
|
580
|
+
token: "approve-d2",
|
|
581
|
+
runId: "d2",
|
|
582
|
+
seq: 0
|
|
583
|
+
});
|
|
584
|
+
expect2(typeof store.deleteRuns).toBe("function");
|
|
585
|
+
await store.deleteRuns?.([
|
|
586
|
+
"d1",
|
|
587
|
+
"d2",
|
|
588
|
+
"ghost"
|
|
589
|
+
]);
|
|
590
|
+
expect2(await store.getRun("d1")).toBeNull();
|
|
591
|
+
expect2(await store.getRun("d2")).toBeNull();
|
|
592
|
+
expect2(await store.getRun("keep")).not.toBeNull();
|
|
593
|
+
expect2(await store.listCheckpoints("d1")).toEqual([]);
|
|
594
|
+
expect2(await store.listSignalWaiters("approve-")).toEqual([]);
|
|
595
|
+
expect2(await store.listRuns({
|
|
596
|
+
attributes: [
|
|
597
|
+
{
|
|
598
|
+
key: "tier",
|
|
599
|
+
op: "eq",
|
|
600
|
+
value: "pro"
|
|
601
|
+
}
|
|
602
|
+
]
|
|
603
|
+
})).toEqual([]);
|
|
604
|
+
await expect2(store.deleteRuns?.([])).resolves.toBeUndefined();
|
|
605
|
+
});
|
|
606
|
+
const aged = /* @__PURE__ */ __name((id, ageDays, over = {}) => run({
|
|
607
|
+
id,
|
|
608
|
+
status: "completed",
|
|
609
|
+
createdAt: new Date(NOW - ageDays * DAY),
|
|
610
|
+
updatedAt: new Date(NOW - ageDays * DAY),
|
|
611
|
+
...over
|
|
612
|
+
}), "aged");
|
|
613
|
+
const ids = /* @__PURE__ */ __name(async () => (await store.listRuns({})).map((r) => r.id).sort(), "ids");
|
|
614
|
+
t("pruneTerminalRuns drops terminal runs past maxAge and never touches live ones", async () => {
|
|
615
|
+
await store.createRun(aged("old-done", 10));
|
|
616
|
+
await store.createRun(aged("old-failed", 10, {
|
|
617
|
+
status: "failed"
|
|
618
|
+
}));
|
|
619
|
+
await store.createRun(aged("old-live", 10, {
|
|
620
|
+
status: "suspended"
|
|
621
|
+
}));
|
|
622
|
+
await store.createRun(aged("fresh-done", 1));
|
|
623
|
+
await store.saveCheckpoint(checkpoint({
|
|
624
|
+
runId: "old-done",
|
|
625
|
+
stepId: "old-done:0"
|
|
626
|
+
}));
|
|
627
|
+
expect2(typeof store.pruneTerminalRuns).toBe("function");
|
|
628
|
+
const deleted = await store.pruneTerminalRuns?.({
|
|
629
|
+
statuses: [
|
|
630
|
+
"completed",
|
|
631
|
+
"failed",
|
|
632
|
+
"suspended"
|
|
633
|
+
],
|
|
634
|
+
maxAge: "7d"
|
|
635
|
+
}, NOW, 100);
|
|
636
|
+
expect2(deleted).toBe(2);
|
|
637
|
+
expect2(await ids()).toEqual([
|
|
638
|
+
"fresh-done",
|
|
639
|
+
"old-live"
|
|
640
|
+
]);
|
|
641
|
+
expect2(await store.listCheckpoints("old-done")).toEqual([]);
|
|
642
|
+
});
|
|
643
|
+
t("pruneTerminalRuns keeps only the newest maxCount and drains in batches of limit", async () => {
|
|
644
|
+
for (let i = 1; i <= 5; i++) await store.createRun(aged(`c${i}`, i));
|
|
645
|
+
const policy = {
|
|
646
|
+
statuses: [
|
|
647
|
+
"completed"
|
|
648
|
+
],
|
|
649
|
+
maxCount: 2
|
|
650
|
+
};
|
|
651
|
+
expect2(await store.pruneTerminalRuns?.(policy, NOW, 2)).toBe(2);
|
|
652
|
+
expect2(await store.pruneTerminalRuns?.(policy, NOW, 2)).toBe(1);
|
|
653
|
+
expect2(await store.pruneTerminalRuns?.(policy, NOW, 2)).toBe(0);
|
|
654
|
+
expect2(await ids()).toEqual([
|
|
655
|
+
"c1",
|
|
656
|
+
"c2"
|
|
657
|
+
]);
|
|
658
|
+
});
|
|
659
|
+
t("a scoped retention policy only prunes (and counts) the runs its scope matches", async () => {
|
|
660
|
+
await store.createRun(aged("acme-old", 10, {
|
|
661
|
+
namespace: "acme"
|
|
662
|
+
}));
|
|
663
|
+
await store.createRun(aged("acme-new", 1, {
|
|
664
|
+
namespace: "acme"
|
|
665
|
+
}));
|
|
666
|
+
await store.createRun(aged("beta-old", 10, {
|
|
667
|
+
namespace: "beta"
|
|
668
|
+
}));
|
|
669
|
+
await store.createRun(aged("report-old", 10, {
|
|
670
|
+
workflow: "report"
|
|
671
|
+
}));
|
|
672
|
+
await store.pruneTerminalRuns?.({
|
|
673
|
+
statuses: [
|
|
674
|
+
"completed"
|
|
675
|
+
],
|
|
676
|
+
maxAge: "7d",
|
|
677
|
+
scope: {
|
|
678
|
+
namespace: "acme"
|
|
679
|
+
}
|
|
680
|
+
}, NOW, 100);
|
|
681
|
+
expect2(await ids()).toEqual([
|
|
682
|
+
"acme-new",
|
|
683
|
+
"beta-old",
|
|
684
|
+
"report-old"
|
|
685
|
+
]);
|
|
686
|
+
await store.pruneTerminalRuns?.({
|
|
687
|
+
statuses: [
|
|
688
|
+
"completed"
|
|
689
|
+
],
|
|
690
|
+
maxAge: "7d",
|
|
691
|
+
scope: {
|
|
692
|
+
workflows: [
|
|
693
|
+
"report"
|
|
694
|
+
]
|
|
695
|
+
}
|
|
696
|
+
}, NOW, 100);
|
|
697
|
+
expect2(await ids()).toEqual([
|
|
698
|
+
"acme-new",
|
|
699
|
+
"beta-old"
|
|
700
|
+
]);
|
|
701
|
+
await store.createRun(aged("acme-mid", 3, {
|
|
702
|
+
namespace: "acme"
|
|
703
|
+
}));
|
|
704
|
+
await store.pruneTerminalRuns?.({
|
|
705
|
+
statuses: [
|
|
706
|
+
"completed"
|
|
707
|
+
],
|
|
708
|
+
maxCount: 1,
|
|
709
|
+
scope: {
|
|
710
|
+
namespace: "acme"
|
|
711
|
+
}
|
|
712
|
+
}, NOW, 100);
|
|
713
|
+
expect2(await ids()).toEqual([
|
|
714
|
+
"acme-new",
|
|
715
|
+
"beta-old"
|
|
716
|
+
]);
|
|
717
|
+
});
|
|
718
|
+
t("a retention scope can select by tag and by search attribute", async () => {
|
|
719
|
+
if (!supportsTagFilter) return;
|
|
720
|
+
await store.createRun(aged("chat-old", 10, {
|
|
721
|
+
tags: [
|
|
722
|
+
"chat",
|
|
723
|
+
"tenant:t1"
|
|
724
|
+
]
|
|
725
|
+
}));
|
|
726
|
+
await store.createRun(aged("chatty-old", 10, {
|
|
727
|
+
tags: [
|
|
728
|
+
"chatty"
|
|
729
|
+
]
|
|
730
|
+
}));
|
|
731
|
+
await store.createRun(aged("attr-old", 10, {
|
|
732
|
+
searchAttributes: {
|
|
733
|
+
tenantId: "t2"
|
|
734
|
+
}
|
|
735
|
+
}));
|
|
736
|
+
await store.createRun(aged("attr-other", 10, {
|
|
737
|
+
searchAttributes: {
|
|
738
|
+
tenantId: "t3"
|
|
739
|
+
}
|
|
740
|
+
}));
|
|
741
|
+
await store.pruneTerminalRuns?.({
|
|
742
|
+
statuses: [
|
|
743
|
+
"completed"
|
|
744
|
+
],
|
|
745
|
+
maxAge: "7d",
|
|
746
|
+
scope: {
|
|
747
|
+
tags: [
|
|
748
|
+
"chat"
|
|
749
|
+
]
|
|
750
|
+
}
|
|
751
|
+
}, NOW, 100);
|
|
752
|
+
expect2(await ids()).toEqual([
|
|
753
|
+
"attr-old",
|
|
754
|
+
"attr-other",
|
|
755
|
+
"chatty-old"
|
|
756
|
+
]);
|
|
757
|
+
await store.pruneTerminalRuns?.({
|
|
758
|
+
statuses: [
|
|
759
|
+
"completed"
|
|
760
|
+
],
|
|
761
|
+
maxAge: "7d",
|
|
762
|
+
scope: {
|
|
763
|
+
attributes: [
|
|
764
|
+
{
|
|
765
|
+
key: "tenantId",
|
|
766
|
+
op: "eq",
|
|
767
|
+
value: "t2"
|
|
768
|
+
}
|
|
769
|
+
]
|
|
770
|
+
}
|
|
771
|
+
}, NOW, 100);
|
|
772
|
+
expect2(await ids()).toEqual([
|
|
773
|
+
"attr-other",
|
|
774
|
+
"chatty-old"
|
|
775
|
+
]);
|
|
776
|
+
});
|
|
777
|
+
t("engine.purgeRuns deletes a scope with its child subtrees and cancels live runs", async () => {
|
|
778
|
+
const engine = new WorkflowEngine3({
|
|
779
|
+
store
|
|
780
|
+
});
|
|
781
|
+
engine.register("p-child", "1", async (ctx) => ctx.waitForSignal("never"));
|
|
782
|
+
engine.register("p-root", "1", async (ctx) => {
|
|
783
|
+
await ctx.startChild("p-child", {}, "pc");
|
|
784
|
+
return ctx.waitForSignal("never");
|
|
785
|
+
});
|
|
786
|
+
engine.register("p-other", "1", async () => "done");
|
|
787
|
+
await engine.start("p-root", {}, "pr");
|
|
788
|
+
await engine.start("p-other", {}, "po");
|
|
789
|
+
expect2((await engine.waitForRun("pc", {
|
|
790
|
+
timeoutMs: 2e4
|
|
791
|
+
})).status).toBe("suspended");
|
|
792
|
+
expect2((await engine.waitForRun("po", {
|
|
793
|
+
timeoutMs: 2e4
|
|
794
|
+
})).status).toBe("completed");
|
|
795
|
+
await expect2(engine.purgeRuns({})).rejects.toThrow(/empty scope/);
|
|
796
|
+
expect2(await engine.purgeRuns({
|
|
797
|
+
workflows: [
|
|
798
|
+
"p-root"
|
|
799
|
+
]
|
|
800
|
+
})).toBe(2);
|
|
801
|
+
expect2(await store.getRun("pr")).toBeNull();
|
|
802
|
+
expect2(await store.getRun("pc")).toBeNull();
|
|
803
|
+
expect2(await store.getRun("po")).not.toBeNull();
|
|
804
|
+
});
|
|
547
805
|
t("updateRun maps every patchable field \u2014 clears error and patches tags/lockedBy/input/timers", async () => {
|
|
548
806
|
await store.createRun(run({
|
|
549
807
|
status: "failed",
|
|
@@ -1080,6 +1338,808 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1080
1338
|
}
|
|
1081
1339
|
]);
|
|
1082
1340
|
});
|
|
1341
|
+
t("matches ANY of a set of statuses, workflows, tenants or origins", async () => {
|
|
1342
|
+
await store.createRun(run({
|
|
1343
|
+
id: "a",
|
|
1344
|
+
status: "failed",
|
|
1345
|
+
namespace: "acme",
|
|
1346
|
+
workflow: "wa"
|
|
1347
|
+
}));
|
|
1348
|
+
await store.createRun(run({
|
|
1349
|
+
id: "b",
|
|
1350
|
+
status: "dead",
|
|
1351
|
+
namespace: "globex",
|
|
1352
|
+
workflow: "wb"
|
|
1353
|
+
}));
|
|
1354
|
+
await store.createRun(run({
|
|
1355
|
+
id: "c",
|
|
1356
|
+
status: "completed",
|
|
1357
|
+
namespace: "initech",
|
|
1358
|
+
workflow: "wc"
|
|
1359
|
+
}));
|
|
1360
|
+
expect2((await store.listRuns({
|
|
1361
|
+
statuses: [
|
|
1362
|
+
"failed",
|
|
1363
|
+
"dead"
|
|
1364
|
+
]
|
|
1365
|
+
})).map((r) => r.id).sort()).toEqual([
|
|
1366
|
+
"a",
|
|
1367
|
+
"b"
|
|
1368
|
+
]);
|
|
1369
|
+
expect2((await store.listRuns({
|
|
1370
|
+
namespaces: [
|
|
1371
|
+
"acme",
|
|
1372
|
+
"globex"
|
|
1373
|
+
]
|
|
1374
|
+
})).map((r) => r.id).sort()).toEqual([
|
|
1375
|
+
"a",
|
|
1376
|
+
"b"
|
|
1377
|
+
]);
|
|
1378
|
+
expect2((await store.listRuns({
|
|
1379
|
+
workflows: [
|
|
1380
|
+
"wa",
|
|
1381
|
+
"wc"
|
|
1382
|
+
]
|
|
1383
|
+
})).map((r) => r.id).sort()).toEqual([
|
|
1384
|
+
"a",
|
|
1385
|
+
"c"
|
|
1386
|
+
]);
|
|
1387
|
+
});
|
|
1388
|
+
t("carries the absent-origin bucket INSIDE a set of origins", async () => {
|
|
1389
|
+
await store.createRun(run({
|
|
1390
|
+
id: "a",
|
|
1391
|
+
origin: "pkg-a"
|
|
1392
|
+
}));
|
|
1393
|
+
await store.createRun(run({
|
|
1394
|
+
id: "b",
|
|
1395
|
+
origin: "pkg-b"
|
|
1396
|
+
}));
|
|
1397
|
+
await store.createRun(run({
|
|
1398
|
+
id: "c"
|
|
1399
|
+
}));
|
|
1400
|
+
expect2((await store.listRuns({
|
|
1401
|
+
origins: [
|
|
1402
|
+
"pkg-a",
|
|
1403
|
+
null
|
|
1404
|
+
]
|
|
1405
|
+
})).map((r) => r.id).sort()).toEqual([
|
|
1406
|
+
"a",
|
|
1407
|
+
"c"
|
|
1408
|
+
]);
|
|
1409
|
+
expect2((await store.listRuns({
|
|
1410
|
+
origins: [
|
|
1411
|
+
"pkg-a",
|
|
1412
|
+
"pkg-b"
|
|
1413
|
+
]
|
|
1414
|
+
})).map((r) => r.id).sort()).toEqual([
|
|
1415
|
+
"a",
|
|
1416
|
+
"b"
|
|
1417
|
+
]);
|
|
1418
|
+
});
|
|
1419
|
+
t("ANDs a plural predicate with everything else, and matches nothing when empty", async () => {
|
|
1420
|
+
await store.createRun(run({
|
|
1421
|
+
id: "a",
|
|
1422
|
+
status: "failed",
|
|
1423
|
+
namespace: "acme"
|
|
1424
|
+
}));
|
|
1425
|
+
await store.createRun(run({
|
|
1426
|
+
id: "b",
|
|
1427
|
+
status: "dead",
|
|
1428
|
+
namespace: "acme"
|
|
1429
|
+
}));
|
|
1430
|
+
expect2((await store.listRuns({
|
|
1431
|
+
namespaces: [
|
|
1432
|
+
"acme"
|
|
1433
|
+
],
|
|
1434
|
+
statuses: [
|
|
1435
|
+
"dead"
|
|
1436
|
+
]
|
|
1437
|
+
})).map((r) => r.id)).toEqual([
|
|
1438
|
+
"b"
|
|
1439
|
+
]);
|
|
1440
|
+
expect2(await store.listRuns({
|
|
1441
|
+
namespaces: []
|
|
1442
|
+
})).toEqual([]);
|
|
1443
|
+
expect2(await store.listRuns({
|
|
1444
|
+
statuses: []
|
|
1445
|
+
})).toEqual([]);
|
|
1446
|
+
expect2(await store.listRuns({
|
|
1447
|
+
origins: []
|
|
1448
|
+
})).toEqual([]);
|
|
1449
|
+
});
|
|
1450
|
+
t("matches runs carrying ANY of a set of tags", async () => {
|
|
1451
|
+
if (!supportsTagFilter) return;
|
|
1452
|
+
await store.createRun(run({
|
|
1453
|
+
id: "a",
|
|
1454
|
+
tags: [
|
|
1455
|
+
"etl"
|
|
1456
|
+
]
|
|
1457
|
+
}));
|
|
1458
|
+
await store.createRun(run({
|
|
1459
|
+
id: "b",
|
|
1460
|
+
tags: [
|
|
1461
|
+
"nightly",
|
|
1462
|
+
"batch"
|
|
1463
|
+
]
|
|
1464
|
+
}));
|
|
1465
|
+
await store.createRun(run({
|
|
1466
|
+
id: "c",
|
|
1467
|
+
tags: [
|
|
1468
|
+
"adhoc"
|
|
1469
|
+
]
|
|
1470
|
+
}));
|
|
1471
|
+
expect2((await store.listRuns({
|
|
1472
|
+
tags: [
|
|
1473
|
+
"etl",
|
|
1474
|
+
"nightly"
|
|
1475
|
+
]
|
|
1476
|
+
})).map((r) => r.id).sort()).toEqual([
|
|
1477
|
+
"a",
|
|
1478
|
+
"b"
|
|
1479
|
+
]);
|
|
1480
|
+
expect2(await store.listRuns({
|
|
1481
|
+
tags: []
|
|
1482
|
+
})).toEqual([]);
|
|
1483
|
+
});
|
|
1484
|
+
t("matches a search attribute against a SET of values (the `in` predicate)", async () => {
|
|
1485
|
+
await store.createRun(run({
|
|
1486
|
+
id: "a",
|
|
1487
|
+
searchAttributes: {
|
|
1488
|
+
tier: "pro"
|
|
1489
|
+
}
|
|
1490
|
+
}));
|
|
1491
|
+
await store.createRun(run({
|
|
1492
|
+
id: "b",
|
|
1493
|
+
searchAttributes: {
|
|
1494
|
+
tier: "enterprise"
|
|
1495
|
+
}
|
|
1496
|
+
}));
|
|
1497
|
+
await store.createRun(run({
|
|
1498
|
+
id: "c",
|
|
1499
|
+
searchAttributes: {
|
|
1500
|
+
tier: "free"
|
|
1501
|
+
}
|
|
1502
|
+
}));
|
|
1503
|
+
await store.createRun(run({
|
|
1504
|
+
id: "d",
|
|
1505
|
+
searchAttributes: {
|
|
1506
|
+
amount: 200
|
|
1507
|
+
}
|
|
1508
|
+
}));
|
|
1509
|
+
const inSet = await store.listRuns({
|
|
1510
|
+
attributes: [
|
|
1511
|
+
{
|
|
1512
|
+
key: "tier",
|
|
1513
|
+
op: "in",
|
|
1514
|
+
values: [
|
|
1515
|
+
"pro",
|
|
1516
|
+
"enterprise"
|
|
1517
|
+
]
|
|
1518
|
+
}
|
|
1519
|
+
]
|
|
1520
|
+
});
|
|
1521
|
+
expect2(inSet.map((r) => r.id).sort()).toEqual([
|
|
1522
|
+
"a",
|
|
1523
|
+
"b"
|
|
1524
|
+
]);
|
|
1525
|
+
const mixed = await store.listRuns({
|
|
1526
|
+
attributes: [
|
|
1527
|
+
{
|
|
1528
|
+
key: "amount",
|
|
1529
|
+
op: "in",
|
|
1530
|
+
values: [
|
|
1531
|
+
200,
|
|
1532
|
+
"none"
|
|
1533
|
+
]
|
|
1534
|
+
}
|
|
1535
|
+
]
|
|
1536
|
+
});
|
|
1537
|
+
expect2(mixed.map((r) => r.id)).toEqual([
|
|
1538
|
+
"d"
|
|
1539
|
+
]);
|
|
1540
|
+
expect2(await store.listRuns({
|
|
1541
|
+
attributes: [
|
|
1542
|
+
{
|
|
1543
|
+
key: "tier",
|
|
1544
|
+
op: "in",
|
|
1545
|
+
values: []
|
|
1546
|
+
}
|
|
1547
|
+
]
|
|
1548
|
+
})).toEqual([]);
|
|
1549
|
+
expect2((await store.listRuns({
|
|
1550
|
+
attributes: [
|
|
1551
|
+
{
|
|
1552
|
+
key: "tier",
|
|
1553
|
+
op: "in",
|
|
1554
|
+
values: [
|
|
1555
|
+
"pro"
|
|
1556
|
+
]
|
|
1557
|
+
}
|
|
1558
|
+
]
|
|
1559
|
+
})).map((r) => r.id)).toEqual([
|
|
1560
|
+
"a"
|
|
1561
|
+
]);
|
|
1562
|
+
});
|
|
1563
|
+
t("enumerates the distinct values of a run column, counted and ordered by frequency", async () => {
|
|
1564
|
+
if (!store.runValueFacets) return;
|
|
1565
|
+
await store.createRun(run({
|
|
1566
|
+
id: "a",
|
|
1567
|
+
namespace: "acme"
|
|
1568
|
+
}));
|
|
1569
|
+
await store.createRun(run({
|
|
1570
|
+
id: "b",
|
|
1571
|
+
namespace: "acme"
|
|
1572
|
+
}));
|
|
1573
|
+
await store.createRun(run({
|
|
1574
|
+
id: "c",
|
|
1575
|
+
namespace: "globex"
|
|
1576
|
+
}));
|
|
1577
|
+
expect2(await store.runValueFacets({
|
|
1578
|
+
field: "namespace"
|
|
1579
|
+
}, {})).toEqual([
|
|
1580
|
+
{
|
|
1581
|
+
value: "acme",
|
|
1582
|
+
count: 2
|
|
1583
|
+
},
|
|
1584
|
+
{
|
|
1585
|
+
value: "globex",
|
|
1586
|
+
count: 1
|
|
1587
|
+
}
|
|
1588
|
+
]);
|
|
1589
|
+
});
|
|
1590
|
+
t("reports the unattributed bucket as a `null` value, not as a name", async () => {
|
|
1591
|
+
if (!store.runValueFacets) return;
|
|
1592
|
+
await store.createRun(run({
|
|
1593
|
+
id: "a",
|
|
1594
|
+
origin: "pkg-a"
|
|
1595
|
+
}));
|
|
1596
|
+
await store.createRun(run({
|
|
1597
|
+
id: "b"
|
|
1598
|
+
}));
|
|
1599
|
+
const values = await store.runValueFacets({
|
|
1600
|
+
field: "origin"
|
|
1601
|
+
}, {});
|
|
1602
|
+
expect2(values).toContainEqual({
|
|
1603
|
+
value: "pkg-a",
|
|
1604
|
+
count: 1
|
|
1605
|
+
});
|
|
1606
|
+
expect2(values).toContainEqual({
|
|
1607
|
+
value: null,
|
|
1608
|
+
count: 1
|
|
1609
|
+
});
|
|
1610
|
+
});
|
|
1611
|
+
t("scopes the values to the SAME predicates the list is under", async () => {
|
|
1612
|
+
if (!store.runValueFacets) return;
|
|
1613
|
+
if (!supportsTagFilter) return;
|
|
1614
|
+
await store.createRun(run({
|
|
1615
|
+
id: "a",
|
|
1616
|
+
namespace: "acme",
|
|
1617
|
+
tags: [
|
|
1618
|
+
"etl"
|
|
1619
|
+
]
|
|
1620
|
+
}));
|
|
1621
|
+
await store.createRun(run({
|
|
1622
|
+
id: "b",
|
|
1623
|
+
namespace: "globex",
|
|
1624
|
+
tags: [
|
|
1625
|
+
"nightly"
|
|
1626
|
+
]
|
|
1627
|
+
}));
|
|
1628
|
+
expect2(await store.runValueFacets({
|
|
1629
|
+
field: "tag"
|
|
1630
|
+
}, {
|
|
1631
|
+
namespace: "acme"
|
|
1632
|
+
})).toEqual([
|
|
1633
|
+
{
|
|
1634
|
+
value: "etl",
|
|
1635
|
+
count: 1
|
|
1636
|
+
}
|
|
1637
|
+
]);
|
|
1638
|
+
});
|
|
1639
|
+
t("enumerates tags, attribute keys and the values under one key", async () => {
|
|
1640
|
+
if (!store.runValueFacets) return;
|
|
1641
|
+
await store.createRun(run({
|
|
1642
|
+
id: "a",
|
|
1643
|
+
tags: [
|
|
1644
|
+
"etl"
|
|
1645
|
+
],
|
|
1646
|
+
searchAttributes: {
|
|
1647
|
+
tier: "pro"
|
|
1648
|
+
}
|
|
1649
|
+
}));
|
|
1650
|
+
await store.createRun(run({
|
|
1651
|
+
id: "b",
|
|
1652
|
+
tags: [
|
|
1653
|
+
"etl",
|
|
1654
|
+
"nightly"
|
|
1655
|
+
],
|
|
1656
|
+
searchAttributes: {
|
|
1657
|
+
tier: "free"
|
|
1658
|
+
}
|
|
1659
|
+
}));
|
|
1660
|
+
expect2(await store.runValueFacets({
|
|
1661
|
+
field: "tag"
|
|
1662
|
+
}, {})).toEqual([
|
|
1663
|
+
{
|
|
1664
|
+
value: "etl",
|
|
1665
|
+
count: 2
|
|
1666
|
+
},
|
|
1667
|
+
{
|
|
1668
|
+
value: "nightly",
|
|
1669
|
+
count: 1
|
|
1670
|
+
}
|
|
1671
|
+
]);
|
|
1672
|
+
expect2(await store.runValueFacets({
|
|
1673
|
+
field: "attributeKey"
|
|
1674
|
+
}, {})).toEqual([
|
|
1675
|
+
{
|
|
1676
|
+
value: "tier",
|
|
1677
|
+
count: 2
|
|
1678
|
+
}
|
|
1679
|
+
]);
|
|
1680
|
+
expect2((await store.runValueFacets({
|
|
1681
|
+
field: "attributeValue",
|
|
1682
|
+
key: "tier"
|
|
1683
|
+
}, {})).map((r) => r.value).sort()).toEqual([
|
|
1684
|
+
"free",
|
|
1685
|
+
"pro"
|
|
1686
|
+
]);
|
|
1687
|
+
});
|
|
1688
|
+
t("searches values server-side, BEFORE the bound", async () => {
|
|
1689
|
+
if (!store.runValueFacets) return;
|
|
1690
|
+
await store.createRun(run({
|
|
1691
|
+
id: "a",
|
|
1692
|
+
namespace: "acme-prod"
|
|
1693
|
+
}));
|
|
1694
|
+
await store.createRun(run({
|
|
1695
|
+
id: "b",
|
|
1696
|
+
namespace: "acme-staging"
|
|
1697
|
+
}));
|
|
1698
|
+
await store.createRun(run({
|
|
1699
|
+
id: "c",
|
|
1700
|
+
namespace: "globex"
|
|
1701
|
+
}));
|
|
1702
|
+
await store.createRun(run({
|
|
1703
|
+
id: "d",
|
|
1704
|
+
namespace: "globex"
|
|
1705
|
+
}));
|
|
1706
|
+
const matches = await store.runValueFacets({
|
|
1707
|
+
field: "namespace"
|
|
1708
|
+
}, {}, {
|
|
1709
|
+
search: "acme"
|
|
1710
|
+
});
|
|
1711
|
+
expect2(matches.map((row) => row.value).sort()).toEqual([
|
|
1712
|
+
"acme-prod",
|
|
1713
|
+
"acme-staging"
|
|
1714
|
+
]);
|
|
1715
|
+
expect2((await store.runValueFacets({
|
|
1716
|
+
field: "namespace"
|
|
1717
|
+
}, {}, {
|
|
1718
|
+
limit: 1
|
|
1719
|
+
})).map((r) => r.value)).toEqual([
|
|
1720
|
+
"globex"
|
|
1721
|
+
]);
|
|
1722
|
+
expect2((await store.runValueFacets({
|
|
1723
|
+
field: "namespace"
|
|
1724
|
+
}, {}, {
|
|
1725
|
+
limit: 1,
|
|
1726
|
+
search: "staging"
|
|
1727
|
+
})).map((r) => r.value)).toEqual([
|
|
1728
|
+
"acme-staging"
|
|
1729
|
+
]);
|
|
1730
|
+
});
|
|
1731
|
+
t("pages values in a stable order, so page two continues page one", async () => {
|
|
1732
|
+
if (!store.runValueFacets) return;
|
|
1733
|
+
await store.createRun(run({
|
|
1734
|
+
id: "a",
|
|
1735
|
+
namespace: "one"
|
|
1736
|
+
}));
|
|
1737
|
+
await store.createRun(run({
|
|
1738
|
+
id: "b",
|
|
1739
|
+
namespace: "one"
|
|
1740
|
+
}));
|
|
1741
|
+
await store.createRun(run({
|
|
1742
|
+
id: "c",
|
|
1743
|
+
namespace: "one"
|
|
1744
|
+
}));
|
|
1745
|
+
await store.createRun(run({
|
|
1746
|
+
id: "d",
|
|
1747
|
+
namespace: "two"
|
|
1748
|
+
}));
|
|
1749
|
+
await store.createRun(run({
|
|
1750
|
+
id: "e",
|
|
1751
|
+
namespace: "two"
|
|
1752
|
+
}));
|
|
1753
|
+
await store.createRun(run({
|
|
1754
|
+
id: "f",
|
|
1755
|
+
namespace: "three"
|
|
1756
|
+
}));
|
|
1757
|
+
const first = await store.runValueFacets({
|
|
1758
|
+
field: "namespace"
|
|
1759
|
+
}, {}, {
|
|
1760
|
+
limit: 2
|
|
1761
|
+
});
|
|
1762
|
+
const second = await store.runValueFacets({
|
|
1763
|
+
field: "namespace"
|
|
1764
|
+
}, {}, {
|
|
1765
|
+
limit: 2,
|
|
1766
|
+
offset: 2
|
|
1767
|
+
});
|
|
1768
|
+
expect2(first).toEqual([
|
|
1769
|
+
{
|
|
1770
|
+
value: "one",
|
|
1771
|
+
count: 3
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
value: "two",
|
|
1775
|
+
count: 2
|
|
1776
|
+
}
|
|
1777
|
+
]);
|
|
1778
|
+
expect2(second).toEqual([
|
|
1779
|
+
{
|
|
1780
|
+
value: "three",
|
|
1781
|
+
count: 1
|
|
1782
|
+
}
|
|
1783
|
+
]);
|
|
1784
|
+
expect2(new Set([
|
|
1785
|
+
...first,
|
|
1786
|
+
...second
|
|
1787
|
+
].map((r) => r.value)).size).toBe(3);
|
|
1788
|
+
});
|
|
1789
|
+
t("searches and pages the tag axis too, where the values actually pile up", async () => {
|
|
1790
|
+
if (!store.runValueFacets) return;
|
|
1791
|
+
if (!supportsTagFilter) return;
|
|
1792
|
+
await store.createRun(run({
|
|
1793
|
+
id: "a",
|
|
1794
|
+
tags: [
|
|
1795
|
+
"type:mvr",
|
|
1796
|
+
"etl"
|
|
1797
|
+
]
|
|
1798
|
+
}));
|
|
1799
|
+
await store.createRun(run({
|
|
1800
|
+
id: "b",
|
|
1801
|
+
tags: [
|
|
1802
|
+
"type:mel",
|
|
1803
|
+
"etl"
|
|
1804
|
+
]
|
|
1805
|
+
}));
|
|
1806
|
+
await store.createRun(run({
|
|
1807
|
+
id: "c",
|
|
1808
|
+
tags: [
|
|
1809
|
+
"etl"
|
|
1810
|
+
]
|
|
1811
|
+
}));
|
|
1812
|
+
expect2((await store.runValueFacets({
|
|
1813
|
+
field: "tag"
|
|
1814
|
+
}, {}, {
|
|
1815
|
+
search: "type:"
|
|
1816
|
+
})).map((r) => r.value).sort()).toEqual([
|
|
1817
|
+
"type:mel",
|
|
1818
|
+
"type:mvr"
|
|
1819
|
+
]);
|
|
1820
|
+
expect2(await store.runValueFacets({
|
|
1821
|
+
field: "tag"
|
|
1822
|
+
}, {}, {
|
|
1823
|
+
limit: 1
|
|
1824
|
+
})).toEqual([
|
|
1825
|
+
{
|
|
1826
|
+
value: "etl",
|
|
1827
|
+
count: 3
|
|
1828
|
+
}
|
|
1829
|
+
]);
|
|
1830
|
+
});
|
|
1831
|
+
t("bounds the values it returns, keeping the most common ones", async () => {
|
|
1832
|
+
if (!store.runValueFacets) return;
|
|
1833
|
+
await store.createRun(run({
|
|
1834
|
+
id: "a",
|
|
1835
|
+
namespace: "acme"
|
|
1836
|
+
}));
|
|
1837
|
+
await store.createRun(run({
|
|
1838
|
+
id: "b",
|
|
1839
|
+
namespace: "acme"
|
|
1840
|
+
}));
|
|
1841
|
+
await store.createRun(run({
|
|
1842
|
+
id: "c",
|
|
1843
|
+
namespace: "globex"
|
|
1844
|
+
}));
|
|
1845
|
+
expect2(await store.runValueFacets({
|
|
1846
|
+
field: "namespace"
|
|
1847
|
+
}, {}, {
|
|
1848
|
+
limit: 1
|
|
1849
|
+
})).toEqual([
|
|
1850
|
+
{
|
|
1851
|
+
value: "acme",
|
|
1852
|
+
count: 2
|
|
1853
|
+
}
|
|
1854
|
+
]);
|
|
1855
|
+
});
|
|
1856
|
+
t("countRuns counts the same set listRuns returns, without the paging", async () => {
|
|
1857
|
+
expect2(typeof store.countRuns).toBe("function");
|
|
1858
|
+
await store.createRun(run({
|
|
1859
|
+
id: "n1",
|
|
1860
|
+
status: "running",
|
|
1861
|
+
namespace: "a",
|
|
1862
|
+
tags: [
|
|
1863
|
+
"q"
|
|
1864
|
+
]
|
|
1865
|
+
}));
|
|
1866
|
+
await store.createRun(run({
|
|
1867
|
+
id: "n2",
|
|
1868
|
+
status: "pending",
|
|
1869
|
+
namespace: "a"
|
|
1870
|
+
}));
|
|
1871
|
+
await store.createRun(run({
|
|
1872
|
+
id: "n3",
|
|
1873
|
+
status: "completed",
|
|
1874
|
+
namespace: "a",
|
|
1875
|
+
tags: [
|
|
1876
|
+
"q"
|
|
1877
|
+
]
|
|
1878
|
+
}));
|
|
1879
|
+
await store.createRun(run({
|
|
1880
|
+
id: "n4",
|
|
1881
|
+
status: "running",
|
|
1882
|
+
namespace: "b",
|
|
1883
|
+
tags: [
|
|
1884
|
+
"q"
|
|
1885
|
+
]
|
|
1886
|
+
}));
|
|
1887
|
+
expect2(await store.countRuns?.({})).toBe(4);
|
|
1888
|
+
expect2(await store.countRuns?.({
|
|
1889
|
+
namespace: "a"
|
|
1890
|
+
})).toBe(3);
|
|
1891
|
+
expect2(await store.countRuns?.({
|
|
1892
|
+
namespace: "a",
|
|
1893
|
+
statuses: [
|
|
1894
|
+
"pending",
|
|
1895
|
+
"running"
|
|
1896
|
+
]
|
|
1897
|
+
})).toBe(2);
|
|
1898
|
+
expect2(await store.countRuns?.({
|
|
1899
|
+
statuses: []
|
|
1900
|
+
})).toBe(0);
|
|
1901
|
+
if (supportsTagFilter) {
|
|
1902
|
+
expect2(await store.countRuns?.({
|
|
1903
|
+
tag: "q",
|
|
1904
|
+
statuses: [
|
|
1905
|
+
"running",
|
|
1906
|
+
"pending"
|
|
1907
|
+
]
|
|
1908
|
+
})).toBe(2);
|
|
1909
|
+
}
|
|
1910
|
+
});
|
|
1911
|
+
t("engine rejects a start over its concurrency quota and frees the slot once a run settles", async () => {
|
|
1912
|
+
if (!supportsTagFilter) return;
|
|
1913
|
+
const engine = new WorkflowEngine3({
|
|
1914
|
+
store
|
|
1915
|
+
});
|
|
1916
|
+
engine.register("q-wait", "1", async (ctx) => ctx.waitForSignal("never"));
|
|
1917
|
+
const quota = {
|
|
1918
|
+
key: "tenant:a",
|
|
1919
|
+
limit: 1,
|
|
1920
|
+
countStatuses: [
|
|
1921
|
+
"pending",
|
|
1922
|
+
"running",
|
|
1923
|
+
"suspended"
|
|
1924
|
+
]
|
|
1925
|
+
};
|
|
1926
|
+
await engine.start("q-wait", {}, "q1", {
|
|
1927
|
+
concurrency: quota
|
|
1928
|
+
});
|
|
1929
|
+
expect2((await engine.waitForRun("q1", {
|
|
1930
|
+
timeoutMs: 2e4
|
|
1931
|
+
})).status).toBe("suspended");
|
|
1932
|
+
await expect2(engine.start("q-wait", {}, "q2", {
|
|
1933
|
+
concurrency: quota
|
|
1934
|
+
})).rejects.toThrow(/concurrency limit/);
|
|
1935
|
+
expect2(await store.getRun("q2")).toBeNull();
|
|
1936
|
+
await engine.start("q-wait", {}, "q3", {
|
|
1937
|
+
concurrency: {
|
|
1938
|
+
...quota,
|
|
1939
|
+
key: "tenant:b"
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
expect2((await engine.waitForRun("q1", {
|
|
1943
|
+
timeoutMs: 2e4
|
|
1944
|
+
})).status).toBe("suspended");
|
|
1945
|
+
await engine.cancel("q1");
|
|
1946
|
+
expect2((await store.getRun("q1"))?.status).toBe("cancelled");
|
|
1947
|
+
await engine.start("q-wait", {}, "q4", {
|
|
1948
|
+
concurrency: quota
|
|
1949
|
+
});
|
|
1950
|
+
expect2(await store.getRun("q4")).not.toBeNull();
|
|
1951
|
+
for (const id of [
|
|
1952
|
+
"q3",
|
|
1953
|
+
"q4"
|
|
1954
|
+
]) await engine.waitForRun(id, {
|
|
1955
|
+
timeoutMs: 2e4
|
|
1956
|
+
});
|
|
1957
|
+
await engine.drain();
|
|
1958
|
+
});
|
|
1959
|
+
const schedule = /* @__PURE__ */ __name((over = {}) => ({
|
|
1960
|
+
id: "s1",
|
|
1961
|
+
namespace: "default",
|
|
1962
|
+
workflow: "digest",
|
|
1963
|
+
paused: false,
|
|
1964
|
+
nextFireAt: NOW + 6e4,
|
|
1965
|
+
tags: [
|
|
1966
|
+
"tenant:a"
|
|
1967
|
+
],
|
|
1968
|
+
spec: {
|
|
1969
|
+
v: 1,
|
|
1970
|
+
cron: "0 9 * * *",
|
|
1971
|
+
input: {
|
|
1972
|
+
userId: "u1",
|
|
1973
|
+
deep: [
|
|
1974
|
+
1,
|
|
1975
|
+
{
|
|
1976
|
+
x: true
|
|
1977
|
+
}
|
|
1978
|
+
]
|
|
1979
|
+
}
|
|
1980
|
+
},
|
|
1981
|
+
state: {
|
|
1982
|
+
window: NOW + 6e4
|
|
1983
|
+
},
|
|
1984
|
+
createdAt: at,
|
|
1985
|
+
updatedAt: at,
|
|
1986
|
+
...over
|
|
1987
|
+
}), "schedule");
|
|
1988
|
+
t("saves, reads back, replaces and deletes a schedule", async () => {
|
|
1989
|
+
expect2(typeof store.saveSchedule).toBe("function");
|
|
1990
|
+
await store.saveSchedule?.(schedule());
|
|
1991
|
+
const loaded = await store.getSchedule?.("s1");
|
|
1992
|
+
expect2(loaded).toMatchObject({
|
|
1993
|
+
id: "s1",
|
|
1994
|
+
namespace: "default",
|
|
1995
|
+
workflow: "digest",
|
|
1996
|
+
paused: false,
|
|
1997
|
+
nextFireAt: NOW + 6e4,
|
|
1998
|
+
tags: [
|
|
1999
|
+
"tenant:a"
|
|
2000
|
+
],
|
|
2001
|
+
spec: {
|
|
2002
|
+
v: 1,
|
|
2003
|
+
cron: "0 9 * * *",
|
|
2004
|
+
input: {
|
|
2005
|
+
userId: "u1",
|
|
2006
|
+
deep: [
|
|
2007
|
+
1,
|
|
2008
|
+
{
|
|
2009
|
+
x: true
|
|
2010
|
+
}
|
|
2011
|
+
]
|
|
2012
|
+
}
|
|
2013
|
+
},
|
|
2014
|
+
state: {
|
|
2015
|
+
window: NOW + 6e4
|
|
2016
|
+
}
|
|
2017
|
+
});
|
|
2018
|
+
expect2(loaded?.createdAt.getTime()).toBe(at.getTime());
|
|
2019
|
+
await store.saveSchedule?.(schedule({
|
|
2020
|
+
workflow: "digest2",
|
|
2021
|
+
nextFireAt: null,
|
|
2022
|
+
tags: void 0
|
|
2023
|
+
}));
|
|
2024
|
+
const replaced = await store.getSchedule?.("s1");
|
|
2025
|
+
expect2(replaced?.workflow).toBe("digest2");
|
|
2026
|
+
expect2(replaced?.nextFireAt).toBeNull();
|
|
2027
|
+
expect2(replaced?.tags ?? []).toEqual([]);
|
|
2028
|
+
expect2(await store.getSchedule?.("nope")).toBeNull();
|
|
2029
|
+
expect2(await store.deleteSchedule?.("s1")).toBe(true);
|
|
2030
|
+
expect2(await store.deleteSchedule?.("s1")).toBe(false);
|
|
2031
|
+
expect2(await store.getSchedule?.("s1")).toBeNull();
|
|
2032
|
+
});
|
|
2033
|
+
t("updateSchedule patches, and compare-and-sets on nextFireAt", async () => {
|
|
2034
|
+
await store.saveSchedule?.(schedule());
|
|
2035
|
+
expect2(await store.updateSchedule?.("s1", {
|
|
2036
|
+
paused: true
|
|
2037
|
+
})).toBe(true);
|
|
2038
|
+
expect2((await store.getSchedule?.("s1"))?.paused).toBe(true);
|
|
2039
|
+
const next = {
|
|
2040
|
+
nextFireAt: NOW + 12e4,
|
|
2041
|
+
state: {
|
|
2042
|
+
window: NOW + 12e4,
|
|
2043
|
+
fires: 1
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
expect2(await store.updateSchedule?.("s1", next, NOW + 6e4)).toBe(true);
|
|
2047
|
+
expect2(await store.updateSchedule?.("s1", next, NOW + 6e4)).toBe(false);
|
|
2048
|
+
const after = await store.getSchedule?.("s1");
|
|
2049
|
+
expect2(after?.nextFireAt).toBe(NOW + 12e4);
|
|
2050
|
+
expect2(after?.state).toEqual({
|
|
2051
|
+
window: NOW + 12e4,
|
|
2052
|
+
fires: 1
|
|
2053
|
+
});
|
|
2054
|
+
await store.updateSchedule?.("s1", {
|
|
2055
|
+
nextFireAt: null
|
|
2056
|
+
});
|
|
2057
|
+
expect2(await store.updateSchedule?.("s1", {
|
|
2058
|
+
nextFireAt: NOW
|
|
2059
|
+
}, NOW + 1)).toBe(false);
|
|
2060
|
+
expect2(await store.updateSchedule?.("s1", {
|
|
2061
|
+
nextFireAt: NOW
|
|
2062
|
+
}, null)).toBe(true);
|
|
2063
|
+
expect2(await store.updateSchedule?.("ghost", {
|
|
2064
|
+
paused: true
|
|
2065
|
+
})).toBe(false);
|
|
2066
|
+
});
|
|
2067
|
+
t('lists schedules by namespace/workflow/tag/paused and answers "due by"', async () => {
|
|
2068
|
+
await store.saveSchedule?.(schedule({
|
|
2069
|
+
id: "a",
|
|
2070
|
+
nextFireAt: NOW - 10
|
|
2071
|
+
}));
|
|
2072
|
+
await store.saveSchedule?.(schedule({
|
|
2073
|
+
id: "b",
|
|
2074
|
+
nextFireAt: NOW - 20,
|
|
2075
|
+
namespace: "x"
|
|
2076
|
+
}));
|
|
2077
|
+
await store.saveSchedule?.(schedule({
|
|
2078
|
+
id: "c",
|
|
2079
|
+
nextFireAt: NOW - 30,
|
|
2080
|
+
paused: true
|
|
2081
|
+
}));
|
|
2082
|
+
await store.saveSchedule?.(schedule({
|
|
2083
|
+
id: "d",
|
|
2084
|
+
nextFireAt: NOW + 10,
|
|
2085
|
+
workflow: "other"
|
|
2086
|
+
}));
|
|
2087
|
+
await store.saveSchedule?.(schedule({
|
|
2088
|
+
id: "e",
|
|
2089
|
+
nextFireAt: null,
|
|
2090
|
+
tags: [
|
|
2091
|
+
"tenant:b"
|
|
2092
|
+
]
|
|
2093
|
+
}));
|
|
2094
|
+
const ids2 = /* @__PURE__ */ __name(async (q) => (await store.listSchedules?.(q) ?? []).map((r) => r.id), "ids");
|
|
2095
|
+
expect2(await ids2({})).toEqual([
|
|
2096
|
+
"c",
|
|
2097
|
+
"b",
|
|
2098
|
+
"a",
|
|
2099
|
+
"d",
|
|
2100
|
+
"e"
|
|
2101
|
+
]);
|
|
2102
|
+
expect2(await ids2({
|
|
2103
|
+
dueBy: NOW
|
|
2104
|
+
})).toEqual([
|
|
2105
|
+
"b",
|
|
2106
|
+
"a"
|
|
2107
|
+
]);
|
|
2108
|
+
expect2(await ids2({
|
|
2109
|
+
dueBy: NOW,
|
|
2110
|
+
namespace: "default"
|
|
2111
|
+
})).toEqual([
|
|
2112
|
+
"a"
|
|
2113
|
+
]);
|
|
2114
|
+
expect2(await ids2({
|
|
2115
|
+
dueBy: NOW,
|
|
2116
|
+
limit: 1
|
|
2117
|
+
})).toEqual([
|
|
2118
|
+
"b"
|
|
2119
|
+
]);
|
|
2120
|
+
expect2(await ids2({
|
|
2121
|
+
workflow: "other"
|
|
2122
|
+
})).toEqual([
|
|
2123
|
+
"d"
|
|
2124
|
+
]);
|
|
2125
|
+
expect2(await ids2({
|
|
2126
|
+
paused: true
|
|
2127
|
+
})).toEqual([
|
|
2128
|
+
"c"
|
|
2129
|
+
]);
|
|
2130
|
+
if (supportsTagFilter) expect2(await ids2({
|
|
2131
|
+
tag: "tenant:b"
|
|
2132
|
+
})).toEqual([
|
|
2133
|
+
"e"
|
|
2134
|
+
]);
|
|
2135
|
+
expect2(await ids2({
|
|
2136
|
+
limit: 2,
|
|
2137
|
+
offset: 1
|
|
2138
|
+
})).toEqual([
|
|
2139
|
+
"b",
|
|
2140
|
+
"a"
|
|
2141
|
+
]);
|
|
2142
|
+
});
|
|
1083
2143
|
t("orders listRuns newest-first and paginates with limit/offset", async () => {
|
|
1084
2144
|
await store.createRun(run({
|
|
1085
2145
|
id: "old",
|