@iterativeflow/conformance 2.1.1 → 2.2.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 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({
@@ -2306,7 +2346,7 @@ const claimFilterConformance = (label, makeBackend) => {
2306
2346
  });
2307
2347
  expect(onlyB.map((l) => l.runId)).toEqual([b.runId]);
2308
2348
  });
2309
- it("an empty set leases nothing; an omitted set leases everything", async () => {
2349
+ it("an empty set leases nothing — not even a run-less job; an omitted set leases everything", async () => {
2310
2350
  const { store, queue } = await makeBackend();
2311
2351
  const a = await store.startRun({
2312
2352
  name: "flow-a",
@@ -2314,6 +2354,7 @@ const claimFilterConformance = (label, makeBackend) => {
2314
2354
  input: {}
2315
2355
  });
2316
2356
  await queue.enqueue(a.runId);
2357
+ await queue.enqueue("orphan-run");
2317
2358
  expect(await queue.claim({
2318
2359
  limit: 10,
2319
2360
  leaseMs: 1e3,
@@ -2325,7 +2366,7 @@ const claimFilterConformance = (label, makeBackend) => {
2325
2366
  leaseMs: 1e3,
2326
2367
  now: at(0)
2327
2368
  });
2328
- expect(all.map((l) => l.runId)).toEqual([a.runId]);
2369
+ expect(all.map((l) => l.runId).sort()).toEqual([a.runId, "orphan-run"].sort());
2329
2370
  });
2330
2371
  it("leases a run-less job under a name filter so it can be acked, not skipped forever", async () => {
2331
2372
  const { store, queue } = await makeBackend();
@@ -2412,6 +2453,10 @@ const shardedClaimConformance = (label, makeBackend) => {
2412
2453
  * `Store.dueCronCount(now, names)` — that `engine.pendingWork` composes. Every backend must count the
2413
2454
  * same due work (claimable jobs, due timers, due crons), exclude leased/future work, and filter by
2414
2455
  * flow name identically.
2456
+ *
2457
+ * A row whose run is gone counts under EVERY name filter, matching `Queue.claim` — a sharded fleet
2458
+ * has to see the backlog it can actually lease, or it stays scaled to zero and the row never drains.
2459
+ * An empty `names` is the exception: it means "this worker handles nothing", so it counts nothing.
2415
2460
  */
2416
2461
  const pendingWorkConformance = (label, makeBackend) => {
2417
2462
  describe(`pending-work conformance (${label})`, () => {
@@ -2441,6 +2486,7 @@ const pendingWorkConformance = (label, makeBackend) => {
2441
2486
  const napping = await start("a");
2442
2487
  await be.timer.schedule(napping, future);
2443
2488
  await be.timer.schedule("orphan-run", past);
2489
+ await be.queue.enqueue("orphan-job");
2444
2490
  const futureJob = await start("a");
2445
2491
  await be.queue.enqueue(futureJob, { runAt: future });
2446
2492
  await be.store.upsertCron({
@@ -2455,16 +2501,17 @@ const pendingWorkConformance = (label, makeBackend) => {
2455
2501
  };
2456
2502
  it("depth counts claimable jobs (not leased/future), filtered by flow name", async () => {
2457
2503
  const be = await seed();
2458
- expect((await be.queue.depth(now)).claimable).toBe(2);
2459
- expect((await be.queue.depth(now, ["a"])).claimable).toBe(1);
2460
- expect((await be.queue.depth(now, ["b"])).claimable).toBe(1);
2504
+ expect((await be.queue.depth(now)).claimable).toBe(3);
2505
+ expect((await be.queue.depth(now, ["a"])).claimable).toBe(2);
2506
+ expect((await be.queue.depth(now, ["b"])).claimable).toBe(2);
2461
2507
  expect((await be.queue.depth(now, [])).claimable).toBe(0);
2462
2508
  });
2463
2509
  it("dueCount counts due timers incl. run-less (not future), filtered by flow name", async () => {
2464
2510
  const be = await seed();
2465
2511
  expect(await be.timer.dueCount(now)).toBe(2);
2466
- expect(await be.timer.dueCount(now, ["a"])).toBe(1);
2467
- expect(await be.timer.dueCount(now, ["b"])).toBe(0);
2512
+ expect(await be.timer.dueCount(now, ["a"])).toBe(2);
2513
+ expect(await be.timer.dueCount(now, ["b"])).toBe(1);
2514
+ expect(await be.timer.dueCount(now, [])).toBe(0);
2468
2515
  });
2469
2516
  it("dueCronCount counts due crons, filtered by flow name", async () => {
2470
2517
  const be = await seed();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iterativeflow/conformance",
3
- "version": "2.1.1",
3
+ "version": "2.2.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.1.1"
28
+ "@iterativeflow/core": "2.2.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "vitest": ">=3"