@iterativeflow/conformance 2.1.1 → 2.3.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.mts +4 -0
- package/dist/index.mjs +75 -7
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -103,6 +103,10 @@ declare const shardedClaimConformance: (label: string, makeBackend: () => Backen
|
|
|
103
103
|
* `Store.dueCronCount(now, names)` — that `engine.pendingWork` composes. Every backend must count the
|
|
104
104
|
* same due work (claimable jobs, due timers, due crons), exclude leased/future work, and filter by
|
|
105
105
|
* flow name identically.
|
|
106
|
+
*
|
|
107
|
+
* A row whose run is gone counts under EVERY name filter, matching `Queue.claim` — a sharded fleet
|
|
108
|
+
* has to see the backlog it can actually lease, or it stays scaled to zero and the row never drains.
|
|
109
|
+
* An empty `names` is the exception: it means "this worker handles nothing", so it counts nothing.
|
|
106
110
|
*/
|
|
107
111
|
declare const pendingWorkConformance: (label: string, makeBackend: () => Backend$1 | Promise<Backend$1>) => void;
|
|
108
112
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -392,6 +392,46 @@ const storeConformance = (label, makeStore) => {
|
|
|
392
392
|
expect(await s.deleteRunsOlderThan(after, 1)).toBe(1);
|
|
393
393
|
expect(await s.deleteRunsOlderThan(after, 1)).toBe(0);
|
|
394
394
|
});
|
|
395
|
+
it("deleteRuns narrows by name/version/status, refuses live runs and an empty filter", async () => {
|
|
396
|
+
const s = await makeStore();
|
|
397
|
+
const old = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
|
|
398
|
+
const mk = async (name, version) => (await s.startRun({
|
|
399
|
+
name,
|
|
400
|
+
version,
|
|
401
|
+
input: {},
|
|
402
|
+
createdAt: old
|
|
403
|
+
})).runId;
|
|
404
|
+
const doomed = await mk("f", 1);
|
|
405
|
+
const otherVersion = await mk("f", 2);
|
|
406
|
+
const otherStatus = await mk("f", 1);
|
|
407
|
+
const otherName = await mk("g", 1);
|
|
408
|
+
const live = await mk("f", 1);
|
|
409
|
+
await s.markTerminal(doomed, { status: "canceled" });
|
|
410
|
+
await s.markTerminal(otherVersion, { status: "canceled" });
|
|
411
|
+
await s.markTerminal(otherStatus, {
|
|
412
|
+
status: "done",
|
|
413
|
+
output: 1
|
|
414
|
+
});
|
|
415
|
+
await s.markTerminal(otherName, { status: "canceled" });
|
|
416
|
+
expect(await s.deleteRuns({
|
|
417
|
+
name: "f",
|
|
418
|
+
version: 1,
|
|
419
|
+
status: "canceled"
|
|
420
|
+
}, 100)).toBe(1);
|
|
421
|
+
expect(await s.loadRunRow(doomed)).toBeUndefined();
|
|
422
|
+
for (const spared of [
|
|
423
|
+
otherVersion,
|
|
424
|
+
otherStatus,
|
|
425
|
+
otherName,
|
|
426
|
+
live
|
|
427
|
+
]) expect(await s.loadRunRow(spared)).toBeDefined();
|
|
428
|
+
expect(await s.deleteRuns({
|
|
429
|
+
status: "pending",
|
|
430
|
+
before: /* @__PURE__ */ new Date()
|
|
431
|
+
}, 100)).toBe(0);
|
|
432
|
+
expect((await s.loadRunRow(live))?.status).toBe("pending");
|
|
433
|
+
await expect(s.deleteRuns({}, 100)).rejects.toThrow();
|
|
434
|
+
});
|
|
395
435
|
it("checkpointStep on an unknown run is rejected (no orphan step)", async () => {
|
|
396
436
|
const s = await makeStore();
|
|
397
437
|
await expect(s.checkpointStep({
|
|
@@ -575,6 +615,27 @@ const storeConformance = (label, makeStore) => {
|
|
|
575
615
|
const again = await s.retryRun(runId);
|
|
576
616
|
expect(again.retried).toBe(false);
|
|
577
617
|
});
|
|
618
|
+
it("retryRun clears the spent attempt budget, so a dead-lettered run gets dispatched again", async () => {
|
|
619
|
+
const s = await makeStore();
|
|
620
|
+
const { runId } = await s.startRun({
|
|
621
|
+
name: "f",
|
|
622
|
+
version: 1,
|
|
623
|
+
input: {}
|
|
624
|
+
});
|
|
625
|
+
await s.markRunning(runId);
|
|
626
|
+
await s.markRunning(runId);
|
|
627
|
+
expect(await s.markRunning(runId)).toBe(3);
|
|
628
|
+
await s.markTerminal(runId, {
|
|
629
|
+
status: "failed",
|
|
630
|
+
error: {
|
|
631
|
+
code: "X",
|
|
632
|
+
message: "boom"
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
await s.retryRun(runId);
|
|
636
|
+
expect((await s.loadRunRow(runId))?.attempts).toBe(0);
|
|
637
|
+
expect(await s.markRunning(runId)).toBe(1);
|
|
638
|
+
});
|
|
578
639
|
it("runStats counts runs per status", async () => {
|
|
579
640
|
const s = await makeStore();
|
|
580
641
|
const a = await s.startRun({
|
|
@@ -2306,7 +2367,7 @@ const claimFilterConformance = (label, makeBackend) => {
|
|
|
2306
2367
|
});
|
|
2307
2368
|
expect(onlyB.map((l) => l.runId)).toEqual([b.runId]);
|
|
2308
2369
|
});
|
|
2309
|
-
it("an empty set leases nothing; an omitted set leases everything", async () => {
|
|
2370
|
+
it("an empty set leases nothing — not even a run-less job; an omitted set leases everything", async () => {
|
|
2310
2371
|
const { store, queue } = await makeBackend();
|
|
2311
2372
|
const a = await store.startRun({
|
|
2312
2373
|
name: "flow-a",
|
|
@@ -2314,6 +2375,7 @@ const claimFilterConformance = (label, makeBackend) => {
|
|
|
2314
2375
|
input: {}
|
|
2315
2376
|
});
|
|
2316
2377
|
await queue.enqueue(a.runId);
|
|
2378
|
+
await queue.enqueue("orphan-run");
|
|
2317
2379
|
expect(await queue.claim({
|
|
2318
2380
|
limit: 10,
|
|
2319
2381
|
leaseMs: 1e3,
|
|
@@ -2325,7 +2387,7 @@ const claimFilterConformance = (label, makeBackend) => {
|
|
|
2325
2387
|
leaseMs: 1e3,
|
|
2326
2388
|
now: at(0)
|
|
2327
2389
|
});
|
|
2328
|
-
expect(all.map((l) => l.runId)).toEqual([a.runId]);
|
|
2390
|
+
expect(all.map((l) => l.runId).sort()).toEqual([a.runId, "orphan-run"].sort());
|
|
2329
2391
|
});
|
|
2330
2392
|
it("leases a run-less job under a name filter so it can be acked, not skipped forever", async () => {
|
|
2331
2393
|
const { store, queue } = await makeBackend();
|
|
@@ -2412,6 +2474,10 @@ const shardedClaimConformance = (label, makeBackend) => {
|
|
|
2412
2474
|
* `Store.dueCronCount(now, names)` — that `engine.pendingWork` composes. Every backend must count the
|
|
2413
2475
|
* same due work (claimable jobs, due timers, due crons), exclude leased/future work, and filter by
|
|
2414
2476
|
* flow name identically.
|
|
2477
|
+
*
|
|
2478
|
+
* A row whose run is gone counts under EVERY name filter, matching `Queue.claim` — a sharded fleet
|
|
2479
|
+
* has to see the backlog it can actually lease, or it stays scaled to zero and the row never drains.
|
|
2480
|
+
* An empty `names` is the exception: it means "this worker handles nothing", so it counts nothing.
|
|
2415
2481
|
*/
|
|
2416
2482
|
const pendingWorkConformance = (label, makeBackend) => {
|
|
2417
2483
|
describe(`pending-work conformance (${label})`, () => {
|
|
@@ -2441,6 +2507,7 @@ const pendingWorkConformance = (label, makeBackend) => {
|
|
|
2441
2507
|
const napping = await start("a");
|
|
2442
2508
|
await be.timer.schedule(napping, future);
|
|
2443
2509
|
await be.timer.schedule("orphan-run", past);
|
|
2510
|
+
await be.queue.enqueue("orphan-job");
|
|
2444
2511
|
const futureJob = await start("a");
|
|
2445
2512
|
await be.queue.enqueue(futureJob, { runAt: future });
|
|
2446
2513
|
await be.store.upsertCron({
|
|
@@ -2455,16 +2522,17 @@ const pendingWorkConformance = (label, makeBackend) => {
|
|
|
2455
2522
|
};
|
|
2456
2523
|
it("depth counts claimable jobs (not leased/future), filtered by flow name", async () => {
|
|
2457
2524
|
const be = await seed();
|
|
2458
|
-
expect((await be.queue.depth(now)).claimable).toBe(
|
|
2459
|
-
expect((await be.queue.depth(now, ["a"])).claimable).toBe(
|
|
2460
|
-
expect((await be.queue.depth(now, ["b"])).claimable).toBe(
|
|
2525
|
+
expect((await be.queue.depth(now)).claimable).toBe(3);
|
|
2526
|
+
expect((await be.queue.depth(now, ["a"])).claimable).toBe(2);
|
|
2527
|
+
expect((await be.queue.depth(now, ["b"])).claimable).toBe(2);
|
|
2461
2528
|
expect((await be.queue.depth(now, [])).claimable).toBe(0);
|
|
2462
2529
|
});
|
|
2463
2530
|
it("dueCount counts due timers incl. run-less (not future), filtered by flow name", async () => {
|
|
2464
2531
|
const be = await seed();
|
|
2465
2532
|
expect(await be.timer.dueCount(now)).toBe(2);
|
|
2466
|
-
expect(await be.timer.dueCount(now, ["a"])).toBe(
|
|
2467
|
-
expect(await be.timer.dueCount(now, ["b"])).toBe(
|
|
2533
|
+
expect(await be.timer.dueCount(now, ["a"])).toBe(2);
|
|
2534
|
+
expect(await be.timer.dueCount(now, ["b"])).toBe(1);
|
|
2535
|
+
expect(await be.timer.dueCount(now, [])).toBe(0);
|
|
2468
2536
|
});
|
|
2469
2537
|
it("dueCronCount counts due crons, filtered by flow name", async () => {
|
|
2470
2538
|
const be = await seed();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iterativeflow/conformance",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@iterativeflow/core": "2.
|
|
28
|
+
"@iterativeflow/core": "2.3.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"vitest": ">=3"
|