@redflow/client 0.0.1 → 0.0.3

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.
@@ -42,10 +42,7 @@ test("manual run: succeeds and records steps", async () => {
42
42
  const client = createClient({ redis, prefix });
43
43
  setDefaultClient(client);
44
44
 
45
- const wf = defineWorkflow(
46
- {
47
- name: "manual-success",
48
- queue: "q1",
45
+ const wf = defineWorkflow("manual-success", {queue: "q1",
49
46
  schema: z.object({ n: z.number().int() }),
50
47
  },
51
48
  async ({ input, step }) => {
@@ -63,7 +60,7 @@ test("manual run: succeeds and records steps", async () => {
63
60
  });
64
61
 
65
62
  const handle = await wf.run({ n: 1 });
66
- const result = await handle.result({ timeoutMs: 3000, pollMs: 50 });
63
+ const result = await handle.result({ timeoutMs: 3000 });
67
64
  expect(result).toEqual({ ok: true, b: 3 });
68
65
 
69
66
  const steps = await client.getRunSteps(handle.id);
@@ -74,13 +71,13 @@ test("manual run: succeeds and records steps", async () => {
74
71
  redis.close();
75
72
  });
76
73
 
77
- test("availableAt: scheduled -> promoted -> executed", async () => {
74
+ test("runAt: scheduled -> promoted -> executed", async () => {
78
75
  const prefix = testPrefix();
79
76
  const redis = new RedisClient(redisServer.url);
80
77
  const client = createClient({ redis, prefix });
81
78
  setDefaultClient(client);
82
79
 
83
- const wf = defineWorkflow({ name: "delayed", queue: "q2" }, async ({ step }) => {
80
+ const wf = defineWorkflow("delayed", {queue: "q2" }, async ({ step }) => {
84
81
  await step.run({ name: "do" }, async () => true);
85
82
  return { ok: true };
86
83
  });
@@ -93,11 +90,11 @@ test("availableAt: scheduled -> promoted -> executed", async () => {
93
90
  });
94
91
 
95
92
  const runAt = new Date(Date.now() + 1200);
96
- const handle = await wf.run({}, { availableAt: runAt });
93
+ const handle = await wf.run({}, { runAt });
97
94
  const state1 = await client.getRun(handle.id);
98
95
  expect(state1?.status).toBe("scheduled");
99
96
 
100
- const result = await handle.result({ timeoutMs: 4000, pollMs: 50 });
97
+ const result = await handle.result({ timeoutMs: 4000 });
101
98
  expect(result).toEqual({ ok: true });
102
99
 
103
100
  const state2 = await client.getRun(handle.id);
@@ -109,16 +106,13 @@ test("availableAt: scheduled -> promoted -> executed", async () => {
109
106
  redis.close();
110
107
  });
111
108
 
112
- test("availableAt: cleared after retry exhaustion final failure", async () => {
109
+ test("runAt: cleared after retry exhaustion final failure", async () => {
113
110
  const prefix = testPrefix();
114
111
  const redis = new RedisClient(redisServer.url);
115
112
  const client = createClient({ redis, prefix });
116
113
  setDefaultClient(client);
117
114
 
118
- const wf = defineWorkflow(
119
- {
120
- name: "retry-failure-clears-available-at",
121
- queue: "q2_retry_fail",
115
+ const wf = defineWorkflow("retry-failure-clears-available-at", {queue: "q2_retry_fail",
122
116
  retries: { maxAttempts: 2 },
123
117
  },
124
118
  async () => {
@@ -134,7 +128,7 @@ test("availableAt: cleared after retry exhaustion final failure", async () => {
134
128
  });
135
129
 
136
130
  const handle = await wf.run({});
137
- await expect(handle.result({ timeoutMs: 8000, pollMs: 50 })).rejects.toThrow("Run failed");
131
+ await expect(handle.result({ timeoutMs: 8000 })).rejects.toThrow("Run failed");
138
132
 
139
133
  const state = await client.getRun(handle.id);
140
134
  expect(state?.status).toBe("failed");
@@ -151,10 +145,7 @@ test("NonRetriableError: skips retries and fails immediately on attempt 1", asyn
151
145
  const client = createClient({ redis, prefix });
152
146
  setDefaultClient(client);
153
147
 
154
- const wf = defineWorkflow(
155
- {
156
- name: "non-retriable-error-wf",
157
- queue: "q_non_retriable",
148
+ const wf = defineWorkflow("non-retriable-error-wf", {queue: "q_non_retriable",
158
149
  retries: { maxAttempts: 3 },
159
150
  },
160
151
  async () => {
@@ -170,7 +161,7 @@ test("NonRetriableError: skips retries and fails immediately on attempt 1", asyn
170
161
  });
171
162
 
172
163
  const handle = await wf.run({});
173
- await expect(handle.result({ timeoutMs: 8000, pollMs: 50 })).rejects.toThrow("Run failed");
164
+ await expect(handle.result({ timeoutMs: 8000 })).rejects.toThrow("Run failed");
174
165
 
175
166
  const state = await client.getRun(handle.id);
176
167
  expect(state?.status).toBe("failed");
@@ -194,10 +185,7 @@ test("onFailure: called after retry exhaustion with correct context", async () =
194
185
 
195
186
  let failureCtx: any = null;
196
187
 
197
- const wf = defineWorkflow(
198
- {
199
- name: "on-failure-retry-exhaustion",
200
- queue: "q_on_failure_1",
188
+ const wf = defineWorkflow("on-failure-retry-exhaustion", {queue: "q_on_failure_1",
201
189
  retries: { maxAttempts: 2 },
202
190
  onFailure: (ctx) => {
203
191
  failureCtx = ctx;
@@ -216,7 +204,7 @@ test("onFailure: called after retry exhaustion with correct context", async () =
216
204
  });
217
205
 
218
206
  const handle = await wf.run({ some: "data" });
219
- await expect(handle.result({ timeoutMs: 8000, pollMs: 50 })).rejects.toThrow("Run failed");
207
+ await expect(handle.result({ timeoutMs: 8000 })).rejects.toThrow("Run failed");
220
208
 
221
209
  expect(failureCtx).not.toBeNull();
222
210
  expect(failureCtx.error).toBeInstanceOf(Error);
@@ -240,10 +228,7 @@ test("onFailure: called immediately with NonRetriableError", async () => {
240
228
 
241
229
  let failureCtx: any = null;
242
230
 
243
- const wf = defineWorkflow(
244
- {
245
- name: "on-failure-non-retriable",
246
- queue: "q_on_failure_2",
231
+ const wf = defineWorkflow("on-failure-non-retriable", {queue: "q_on_failure_2",
247
232
  retries: { maxAttempts: 5 },
248
233
  onFailure: (ctx) => {
249
234
  failureCtx = ctx;
@@ -262,7 +247,7 @@ test("onFailure: called immediately with NonRetriableError", async () => {
262
247
  });
263
248
 
264
249
  const handle = await wf.run({});
265
- await expect(handle.result({ timeoutMs: 8000, pollMs: 50 })).rejects.toThrow("Run failed");
250
+ await expect(handle.result({ timeoutMs: 8000 })).rejects.toThrow("Run failed");
266
251
 
267
252
  expect(failureCtx).not.toBeNull();
268
253
  expect(failureCtx.error).toBeInstanceOf(NonRetriableError);
@@ -280,10 +265,7 @@ test("onFailure: NOT called on cancellation", async () => {
280
265
 
281
266
  let onFailureCalled = false;
282
267
 
283
- const wf = defineWorkflow(
284
- {
285
- name: "on-failure-not-on-cancel",
286
- queue: "q_on_failure_3",
268
+ const wf = defineWorkflow("on-failure-not-on-cancel", {queue: "q_on_failure_3",
287
269
  onFailure: () => {
288
270
  onFailureCalled = true;
289
271
  },
@@ -339,11 +321,8 @@ test("cron: creates runs and executes workflow", async () => {
339
321
 
340
322
  const counterKey = `${prefix}:t:cronCount`;
341
323
 
342
- defineWorkflow(
343
- {
344
- name: "cron-wf",
345
- queue: "q4",
346
- triggers: { cron: [{ expression: "*/1 * * * * *", input: { x: 1 } }] },
324
+ defineWorkflow("cron-wf", {queue: "q4",
325
+ cron: [{ expression: "*/1 * * * * *", input: { x: 1 } }],
347
326
  },
348
327
  async ({ step }) => {
349
328
  await step.run({ name: "tick" }, async () => {
@@ -394,12 +373,12 @@ test("step.runWorkflow: auto idempotency and override are forwarded to child run
394
373
  return await originalRunByName.call(this, workflowName, input, options);
395
374
  }) as RedflowClient["runByName"];
396
375
 
397
- const child = defineWorkflow({ name: "child-rw-forward", queue: "q_rw_child" }, async ({ input }) => input);
376
+ const child = defineWorkflow("child-rw-forward", {queue: "q_rw_child" }, async ({ input }) => input);
398
377
 
399
378
  const encodePart = (value: string) => `${value.length}:${value}`;
400
379
  let expectedAutoIdempotencyKey = "";
401
380
 
402
- const parent = defineWorkflow({ name: "parent-rw-forward", queue: "q_rw_parent" }, async ({ run, step }) => {
381
+ const parent = defineWorkflow("parent-rw-forward", {queue: "q_rw_parent" }, async ({ run, step }) => {
403
382
  expectedAutoIdempotencyKey =
404
383
  `stepwf:${encodePart(run.id)}:${encodePart("child-auto")}:${encodePart(child.name)}`;
405
384
 
@@ -419,7 +398,7 @@ test("step.runWorkflow: auto idempotency and override are forwarded to child run
419
398
 
420
399
  try {
421
400
  const handle = await parent.run({});
422
- const out = await handle.result({ timeoutMs: 5000, pollMs: 50 });
401
+ const out = await handle.result({ timeoutMs: 5000 });
423
402
  expect(out).toEqual({
424
403
  auto: { n: 1 },
425
404
  custom: { n: 2 },
@@ -444,7 +423,7 @@ test("step.runWorkflow: child workflow executes once across parent retries", asy
444
423
  const childCountKey = `${prefix}:t:child-runWorkflow-count`;
445
424
  const failOnceKey = `${prefix}:t:parent-runWorkflow-fail-once`;
446
425
 
447
- const child = defineWorkflow({ name: "child-rw-retry", queue: "q_rw_retry_child" }, async ({ step }) => {
426
+ const child = defineWorkflow("child-rw-retry", {queue: "q_rw_retry_child" }, async ({ step }) => {
448
427
  await step.run({ name: "count-child" }, async () => {
449
428
  await redis.incr(childCountKey);
450
429
  return true;
@@ -453,10 +432,7 @@ test("step.runWorkflow: child workflow executes once across parent retries", asy
453
432
  return { ok: true };
454
433
  });
455
434
 
456
- const parent = defineWorkflow(
457
- {
458
- name: "parent-rw-retry",
459
- queue: "q_rw_retry_parent",
435
+ const parent = defineWorkflow("parent-rw-retry", {queue: "q_rw_retry_parent",
460
436
  retries: { maxAttempts: 2 },
461
437
  },
462
438
  async ({ step }) => {
@@ -484,7 +460,7 @@ test("step.runWorkflow: child workflow executes once across parent retries", asy
484
460
  });
485
461
 
486
462
  const handle = await parent.run({});
487
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
463
+ const out = await handle.result({ timeoutMs: 8000 });
488
464
  expect(out).toEqual({ ok: true });
489
465
 
490
466
  const childCount = Number((await redis.get(childCountKey)) ?? "0");
@@ -510,10 +486,7 @@ test("step.runWorkflow: same queue avoids self-deadlock with concurrency 1", asy
510
486
  const childFailOnceKey = `${prefix}:t:child-rw-fail-once`;
511
487
  const childCountKey = `${prefix}:t:child-rw-count`;
512
488
 
513
- const child = defineWorkflow(
514
- {
515
- name: "child-rw-single-worker",
516
- queue: "q_rw_single",
489
+ const child = defineWorkflow("child-rw-single-worker", {queue: "q_rw_single",
517
490
  retries: { maxAttempts: 2 },
518
491
  },
519
492
  async ({ step }) => {
@@ -531,7 +504,7 @@ test("step.runWorkflow: same queue avoids self-deadlock with concurrency 1", asy
531
504
  },
532
505
  );
533
506
 
534
- const parent = defineWorkflow({ name: "parent-rw-single-worker", queue: "q_rw_single" }, async ({ step }) => {
507
+ const parent = defineWorkflow("parent-rw-single-worker", {queue: "q_rw_single" }, async ({ step }) => {
535
508
  return await step.runWorkflow({ name: "call-child" }, child, {});
536
509
  });
537
510
 
@@ -544,7 +517,7 @@ test("step.runWorkflow: same queue avoids self-deadlock with concurrency 1", asy
544
517
  });
545
518
 
546
519
  const handle = await parent.run({});
547
- const out = await handle.result({ timeoutMs: 10_000, pollMs: 50 });
520
+ const out = await handle.result({ timeoutMs: 10_000 });
548
521
  expect(out).toEqual({ ok: true });
549
522
 
550
523
  const childCount = Number((await redis.get(childCountKey)) ?? "0");
@@ -559,241 +532,48 @@ test("step.runWorkflow: same queue avoids self-deadlock with concurrency 1", asy
559
532
  redis.close();
560
533
  }, { timeout: 20_000 });
561
534
 
562
- test("step.emitEvent: auto idempotency and override are forwarded", async () => {
535
+ test("step.emitWorkflow: supports workflow name strings", async () => {
563
536
  const prefix = testPrefix();
564
537
  const redis = new RedisClient(redisServer.url);
565
538
  const client = createClient({ redis, prefix });
566
539
  setDefaultClient(client);
567
540
 
568
- const observedIdempotencyKeys: string[] = [];
569
- const originalEmitEvent: RedflowClient["emitEvent"] = RedflowClient.prototype.emitEvent;
570
-
571
- RedflowClient.prototype.emitEvent = (async function (
572
- this: RedflowClient,
573
- name: string,
574
- payload: unknown,
575
- options?: Parameters<RedflowClient["emitEvent"]>[2],
576
- ): Promise<Awaited<ReturnType<RedflowClient["emitEvent"]>>> {
577
- if (name === "step.emit.forward" || name === "step.emit.forward.custom") {
578
- observedIdempotencyKeys.push(options?.idempotencyKey ?? "");
579
- }
580
- return await originalEmitEvent.call(this, name, payload, options);
581
- }) as RedflowClient["emitEvent"];
582
-
583
- defineWorkflow(
584
- { name: "step-emit-consumer-a", queue: "q_emit_a", triggers: { events: ["step.emit.forward"] } },
585
- async () => ({ ok: true }),
586
- );
587
-
588
- defineWorkflow(
589
- { name: "step-emit-consumer-b", queue: "q_emit_b", triggers: { events: ["step.emit.forward.custom"] } },
590
- async () => ({ ok: true }),
591
- );
592
-
593
- const encodePart = (value: string) => `${value.length}:${value}`;
594
- let expectedAutoIdempotencyKey = "";
595
-
596
- const parent = defineWorkflow({ name: "step-emit-parent-forward", queue: "q_emit_parent" }, async ({ run, step }) => {
597
- expectedAutoIdempotencyKey =
598
- `stepev:${encodePart(run.id)}:${encodePart("emit-auto")}:${encodePart("step.emit.forward")}`;
541
+ defineWorkflow("child-emit-by-name", { queue: "q_emit_name_child" }, async ({ input }) => {
542
+ return { ok: true, input };
543
+ });
599
544
 
600
- const auto = await step.emitEvent({ name: "emit-auto", event: "step.emit.forward" }, { ok: 1 });
601
- const custom = await step.emitEvent(
602
- { name: "emit-custom", event: "step.emit.forward.custom", idempotencyKey: "evt-custom" },
603
- { ok: 2 },
545
+ const parent = defineWorkflow("parent-emit-by-name", { queue: "q_emit_name_parent" }, async ({ step }) => {
546
+ const childRunId = await step.emitWorkflow(
547
+ { name: "emit-child-by-name" },
548
+ "child-emit-by-name",
549
+ { value: 1 },
604
550
  );
605
551
 
606
- return {
607
- autoCount: auto.length,
608
- customCount: custom.length,
609
- };
552
+ return { childRunId };
610
553
  });
611
554
 
612
555
  const worker = await startWorker({
613
556
  redis,
614
557
  prefix,
615
- queues: ["q_emit_parent", "q_emit_a", "q_emit_b"],
558
+ queues: ["q_emit_name_parent", "q_emit_name_child"],
616
559
  concurrency: 2,
617
560
  runtime: { leaseMs: 500 },
618
561
  });
619
562
 
620
563
  try {
621
- const handle = await parent.run({});
622
- const out = await handle.result({ timeoutMs: 5000, pollMs: 50 });
623
- expect(out).toEqual({ autoCount: 1, customCount: 1 });
564
+ const parentHandle = await parent.run({});
565
+ const parentOut = await parentHandle.result({ timeoutMs: 8000 });
566
+ expect(typeof parentOut.childRunId).toBe("string");
624
567
 
625
- expect(observedIdempotencyKeys.length).toBe(2);
626
- expect(observedIdempotencyKeys[0]).toBe(expectedAutoIdempotencyKey);
627
- expect(observedIdempotencyKeys[1]).toBe("evt-custom");
568
+ const childOut = await client.makeRunHandle(parentOut.childRunId).result({ timeoutMs: 8000 });
569
+ expect(childOut).toEqual({ ok: true, input: { value: 1 } });
628
570
  } finally {
629
- RedflowClient.prototype.emitEvent = originalEmitEvent;
630
571
  await worker.stop();
631
572
  redis.close();
632
573
  }
633
- }, { timeout: 15_000 });
634
-
635
- test("step.emitEvent: emitted workflows execute once across parent retries", async () => {
636
- const prefix = testPrefix();
637
- const redis = new RedisClient(redisServer.url);
638
- const client = createClient({ redis, prefix });
639
- setDefaultClient(client);
640
-
641
- const counterKey = `${prefix}:t:step-emit-once`;
642
- const failOnceKey = `${prefix}:t:step-emit-parent-fail`;
643
-
644
- defineWorkflow(
645
- {
646
- name: "step-emit-consumer-once",
647
- queue: "q_emit_retry_consumer",
648
- triggers: { events: ["step.emit.once"] },
649
- },
650
- async ({ step }) => {
651
- await step.run({ name: "count" }, async () => {
652
- await redis.incr(counterKey);
653
- return true;
654
- });
655
- return { ok: true };
656
- },
657
- );
658
-
659
- const parent = defineWorkflow(
660
- {
661
- name: "step-emit-parent-retry",
662
- queue: "q_emit_retry_parent",
663
- retries: { maxAttempts: 2 },
664
- },
665
- async ({ step }) => {
666
- const runIds = await step.emitEvent({ name: "emit-once", event: "step.emit.once" }, { v: 1 });
667
-
668
- await step.run({ name: "fail-once" }, async () => {
669
- const seen = await redis.get(failOnceKey);
670
- if (!seen) {
671
- await redis.set(failOnceKey, "1");
672
- throw new Error("fail once");
673
- }
674
- return true;
675
- });
676
-
677
- return { emitted: runIds.length };
678
- },
679
- );
680
-
681
- const worker = await startWorker({
682
- redis,
683
- prefix,
684
- queues: ["q_emit_retry_parent", "q_emit_retry_consumer"],
685
- concurrency: 2,
686
- runtime: { leaseMs: 500 },
687
- });
688
-
689
- const handle = await parent.run({});
690
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
691
- expect(out).toEqual({ emitted: 1 });
692
-
693
- await waitFor(
694
- async () => Number((await redis.get(counterKey)) ?? "0") >= 1,
695
- { timeoutMs: 5000, label: "step.emitEvent consumer run" },
696
- );
697
-
698
- await new Promise((r) => setTimeout(r, 200));
699
- const counter = Number((await redis.get(counterKey)) ?? "0");
700
- expect(counter).toBe(1);
701
-
702
- const consumerRuns = await client.listRuns({ workflow: "step-emit-consumer-once", limit: 10 });
703
- expect(consumerRuns.length).toBe(1);
704
-
705
- const parentState = await client.getRun(handle.id);
706
- expect(parentState?.status).toBe("succeeded");
707
- expect(parentState?.attempt).toBe(2);
708
-
709
- await worker.stop();
710
- redis.close();
711
- }, { timeout: 15_000 });
712
-
713
- test("step.scheduleEvent: scheduled workflows execute once across parent retries", async () => {
714
- const prefix = testPrefix();
715
- const redis = new RedisClient(redisServer.url);
716
- const client = createClient({ redis, prefix });
717
- setDefaultClient(client);
718
-
719
- const counterKey = `${prefix}:t:step-schedule-once`;
720
- const failOnceKey = `${prefix}:t:step-schedule-parent-fail`;
721
-
722
- defineWorkflow(
723
- {
724
- name: "step-schedule-consumer-once",
725
- queue: "q_schedule_retry_consumer",
726
- triggers: { events: ["step.schedule.once"] },
727
- },
728
- async ({ step }) => {
729
- await step.run({ name: "count" }, async () => {
730
- await redis.incr(counterKey);
731
- return true;
732
- });
733
- return { ok: true };
734
- },
735
- );
736
-
737
- const parent = defineWorkflow(
738
- {
739
- name: "step-schedule-parent-retry",
740
- queue: "q_schedule_retry_parent",
741
- retries: { maxAttempts: 2 },
742
- },
743
- async ({ step }) => {
744
- const runIds = await step.scheduleEvent(
745
- {
746
- name: "schedule-once",
747
- event: "step.schedule.once",
748
- schedule: { availableAt: new Date(Date.now() + 250) },
749
- },
750
- { v: 1 },
751
- );
752
-
753
- await step.run({ name: "fail-once" }, async () => {
754
- const seen = await redis.get(failOnceKey);
755
- if (!seen) {
756
- await redis.set(failOnceKey, "1");
757
- throw new Error("fail once");
758
- }
759
- return true;
760
- });
761
-
762
- return { emitted: runIds.length };
763
- },
764
- );
765
-
766
- const worker = await startWorker({
767
- redis,
768
- prefix,
769
- queues: ["q_schedule_retry_parent", "q_schedule_retry_consumer"],
770
- concurrency: 2,
771
- runtime: { leaseMs: 500 },
772
- });
773
-
774
- const handle = await parent.run({});
775
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
776
- expect(out).toEqual({ emitted: 1 });
777
-
778
- await waitFor(
779
- async () => Number((await redis.get(counterKey)) ?? "0") >= 1,
780
- { timeoutMs: 6000, label: "step.scheduleEvent consumer run" },
781
- );
574
+ }, { timeout: 20_000 });
782
575
 
783
- await new Promise((r) => setTimeout(r, 250));
784
- const counter = Number((await redis.get(counterKey)) ?? "0");
785
- expect(counter).toBe(1);
786
576
 
787
- const consumerRuns = await client.listRuns({ workflow: "step-schedule-consumer-once", limit: 10 });
788
- expect(consumerRuns.length).toBe(1);
789
-
790
- const parentState = await client.getRun(handle.id);
791
- expect(parentState?.status).toBe("succeeded");
792
- expect(parentState?.attempt).toBe(2);
793
-
794
- await worker.stop();
795
- redis.close();
796
- }, { timeout: 15_000 });
797
577
 
798
578
  test("retries: step results are cached across attempts", async () => {
799
579
  const prefix = testPrefix();
@@ -804,10 +584,7 @@ test("retries: step results are cached across attempts", async () => {
804
584
  const step1CountKey = `${prefix}:t:step1Count`;
805
585
  const failOnceKey = `${prefix}:t:failOnce`;
806
586
 
807
- const wf = defineWorkflow(
808
- {
809
- name: "retry-wf",
810
- queue: "q5",
587
+ const wf = defineWorkflow("retry-wf", {queue: "q5",
811
588
  retries: { maxAttempts: 2 },
812
589
  },
813
590
  async ({ step }) => {
@@ -837,7 +614,7 @@ test("retries: step results are cached across attempts", async () => {
837
614
  });
838
615
 
839
616
  const handle = await wf.run({});
840
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
617
+ const out = await handle.result({ timeoutMs: 8000 });
841
618
  expect(out).toEqual({ ok: true });
842
619
 
843
620
  const step1Count = Number((await redis.get(step1CountKey)) ?? "0");
@@ -859,10 +636,7 @@ test("retries: run queued before worker start keeps workflow maxAttempts", async
859
636
 
860
637
  const failOnceKey = `${prefix}:t:retry-before-sync`;
861
638
 
862
- const wf = defineWorkflow(
863
- {
864
- name: "retry-before-sync",
865
- queue: "q5_before_sync",
639
+ const wf = defineWorkflow("retry-before-sync", {queue: "q5_before_sync",
866
640
  retries: { maxAttempts: 2 },
867
641
  },
868
642
  async () => {
@@ -887,7 +661,7 @@ test("retries: run queued before worker start keeps workflow maxAttempts", async
887
661
  runtime: { leaseMs: 500 },
888
662
  });
889
663
 
890
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
664
+ const out = await handle.result({ timeoutMs: 8000 });
891
665
  expect(out).toEqual({ ok: true });
892
666
 
893
667
  const state = await client.getRun(handle.id);
@@ -905,7 +679,7 @@ test("step timeout: run fails and error is recorded", async () => {
905
679
  const client = createClient({ redis, prefix });
906
680
  setDefaultClient(client);
907
681
 
908
- const wf = defineWorkflow({ name: "timeout-wf", queue: "q6" }, async ({ step }) => {
682
+ const wf = defineWorkflow("timeout-wf", {queue: "q6" }, async ({ step }) => {
909
683
  await step.run({ name: "slow", timeoutMs: 80 }, async () => {
910
684
  await new Promise((r) => setTimeout(r, 250));
911
685
  return true;
@@ -949,7 +723,7 @@ test("cancellation: scheduled/queued/running", async () => {
949
723
 
950
724
  const touchedKey = `${prefix}:t:touched`;
951
725
 
952
- const wf = defineWorkflow({ name: "cancel-wf", queue: "q7" }, async ({ step }) => {
726
+ const wf = defineWorkflow("cancel-wf", {queue: "q7" }, async ({ step }) => {
953
727
  await step.run({ name: "hold" }, async () => {
954
728
  await new Promise((r) => setTimeout(r, 600));
955
729
  return true;
@@ -970,7 +744,7 @@ test("cancellation: scheduled/queued/running", async () => {
970
744
  runtime: { leaseMs: 300 },
971
745
  });
972
746
 
973
- const handle = await wf.run({}, { availableAt: new Date(Date.now() + 300) });
747
+ const handle = await wf.run({}, { runAt: new Date(Date.now() + 300) });
974
748
  const canceled = await client.cancelRun(handle.id, { reason: "test" });
975
749
  expect(canceled).toBe(true);
976
750
 
@@ -1042,7 +816,7 @@ test("cancel race: queued cancel before start does not execute handler", async (
1042
816
  const queue = "q7_race";
1043
817
  const sideEffectsKey = `${prefix}:t:cancelRaceSideEffects`;
1044
818
 
1045
- const wf = defineWorkflow({ name: "cancel-race-wf", queue }, async () => {
819
+ const wf = defineWorkflow("cancel-race-wf", {queue }, async () => {
1046
820
  await redis.incr(sideEffectsKey);
1047
821
  return { ok: true };
1048
822
  });
@@ -1195,7 +969,7 @@ test("idempotencyKey: same key returns same run id and executes once", async ()
1195
969
 
1196
970
  const countKey = `${prefix}:t:idemCount`;
1197
971
 
1198
- const wf = defineWorkflow({ name: "idem-wf", queue: "q9" }, async ({ step }) => {
972
+ const wf = defineWorkflow("idem-wf", {queue: "q9" }, async ({ step }) => {
1199
973
  await step.run({ name: "do" }, async () => {
1200
974
  await redis.incr(countKey);
1201
975
  return true;
@@ -1213,7 +987,7 @@ test("idempotencyKey: same key returns same run id and executes once", async ()
1213
987
  const [h1, h2] = await Promise.all([wf.run({}, { idempotencyKey: "k" }), wf.run({}, { idempotencyKey: "k" })]);
1214
988
  expect(h1.id).toBe(h2.id);
1215
989
 
1216
- const out = await h1.result({ timeoutMs: 4000, pollMs: 50 });
990
+ const out = await h1.result({ timeoutMs: 4000 });
1217
991
  expect(out).toEqual({ ok: true });
1218
992
 
1219
993
  // Give the worker a moment to potentially pick duplicates.
@@ -1233,7 +1007,7 @@ test("idempotencyKey: delayed TTL refresh cannot fork duplicate runs", async ()
1233
1007
 
1234
1008
  const countKey = `${prefix}:t:idem-race-fix-count`;
1235
1009
 
1236
- const wf = defineWorkflow({ name: "idem-race-fix", queue: "q9_race_fix" }, async ({ step }) => {
1010
+ const wf = defineWorkflow("idem-race-fix", {queue: "q9_race_fix" }, async ({ step }) => {
1237
1011
  await step.run({ name: "do" }, async () => {
1238
1012
  await redis.incr(countKey);
1239
1013
  return true;
@@ -1267,8 +1041,8 @@ test("idempotencyKey: delayed TTL refresh cannot fork duplicate runs", async ()
1267
1041
  expect(h1.id).toBe(h2.id);
1268
1042
 
1269
1043
  const [out1, out2] = await Promise.all([
1270
- h1.result({ timeoutMs: 5000, pollMs: 50 }),
1271
- h2.result({ timeoutMs: 5000, pollMs: 50 }),
1044
+ h1.result({ timeoutMs: 5000 }),
1045
+ h2.result({ timeoutMs: 5000 }),
1272
1046
  ]);
1273
1047
  expect(out1).toEqual({ ok: true });
1274
1048
  expect(out2).toEqual({ ok: true });
@@ -1288,7 +1062,7 @@ test("enqueue: producer-side zadd failure does not leave runs orphaned", async (
1288
1062
  const client = createClient({ redis, prefix });
1289
1063
  setDefaultClient(client);
1290
1064
 
1291
- const wf = defineWorkflow({ name: "enqueue-atomic", queue: "q_enqueue_atomic" }, async () => ({ ok: true }));
1065
+ const wf = defineWorkflow("enqueue-atomic", {queue: "q_enqueue_atomic" }, async () => ({ ok: true }));
1292
1066
  await client.syncRegistry(getDefaultRegistry());
1293
1067
 
1294
1068
  const originalZadd = redis.zadd.bind(redis);
@@ -1316,7 +1090,7 @@ test("enqueue: producer-side zadd failure does not leave runs orphaned", async (
1316
1090
  });
1317
1091
 
1318
1092
  try {
1319
- const out = await handle.result({ timeoutMs: 6000, pollMs: 50 });
1093
+ const out = await handle.result({ timeoutMs: 6000 });
1320
1094
  expect(out).toEqual({ ok: true });
1321
1095
  } finally {
1322
1096
  await worker.stop();
@@ -1330,8 +1104,8 @@ test("idempotencyKey: delimiter-like workflow/key pairs do not collide", async (
1330
1104
  const client = createClient({ redis, prefix });
1331
1105
  setDefaultClient(client);
1332
1106
 
1333
- const wfA = defineWorkflow({ name: "idem:a", queue: "q9a" }, async () => ({ workflow: "idem:a" }));
1334
- const wfB = defineWorkflow({ name: "idem:a:b", queue: "q9b" }, async () => ({ workflow: "idem:a:b" }));
1107
+ const wfA = defineWorkflow("idem:a", {queue: "q9a" }, async () => ({ workflow: "idem:a" }));
1108
+ const wfB = defineWorkflow("idem:a:b", {queue: "q9b" }, async () => ({ workflow: "idem:a:b" }));
1335
1109
 
1336
1110
  const worker = await startWorker({
1337
1111
  redis,
@@ -1349,8 +1123,8 @@ test("idempotencyKey: delimiter-like workflow/key pairs do not collide", async (
1349
1123
  expect(h1.id).not.toBe(h2.id);
1350
1124
 
1351
1125
  const [out1, out2] = await Promise.all([
1352
- h1.result({ timeoutMs: 5000, pollMs: 50 }),
1353
- h2.result({ timeoutMs: 5000, pollMs: 50 }),
1126
+ h1.result({ timeoutMs: 5000 }),
1127
+ h2.result({ timeoutMs: 5000 }),
1354
1128
  ]);
1355
1129
 
1356
1130
  expect(out1).toEqual({ workflow: "idem:a" });
@@ -1368,7 +1142,7 @@ test("idempotencyTtl: short TTL expires and allows a new run with same key", asy
1368
1142
 
1369
1143
  const countKey = `${prefix}:t:idemTtlCount`;
1370
1144
 
1371
- const wf = defineWorkflow({ name: "idem-ttl-wf", queue: "q_idem_ttl" }, async ({ step }) => {
1145
+ const wf = defineWorkflow("idem-ttl-wf", {queue: "q_idem_ttl" }, async ({ step }) => {
1372
1146
  await step.run({ name: "count" }, async () => {
1373
1147
  await redis.incr(countKey);
1374
1148
  return true;
@@ -1385,7 +1159,7 @@ test("idempotencyTtl: short TTL expires and allows a new run with same key", asy
1385
1159
 
1386
1160
  // First run with 1-second TTL
1387
1161
  const h1 = await wf.run({}, { idempotencyKey: "ttl-key", idempotencyTtl: 1 });
1388
- const out1 = await h1.result({ timeoutMs: 5000, pollMs: 50 });
1162
+ const out1 = await h1.result({ timeoutMs: 5000 });
1389
1163
  expect(out1).toEqual({ ok: true });
1390
1164
 
1391
1165
  // Same key within TTL — returns existing run
@@ -1399,7 +1173,7 @@ test("idempotencyTtl: short TTL expires and allows a new run with same key", asy
1399
1173
  const h3 = await wf.run({}, { idempotencyKey: "ttl-key", idempotencyTtl: 1 });
1400
1174
  expect(h3.id).not.toBe(h1.id);
1401
1175
 
1402
- const out3 = await h3.result({ timeoutMs: 5000, pollMs: 50 });
1176
+ const out3 = await h3.result({ timeoutMs: 5000 });
1403
1177
  expect(out3).toEqual({ ok: true });
1404
1178
 
1405
1179
  // Handler executed twice (once per unique run)
@@ -1410,157 +1184,13 @@ test("idempotencyTtl: short TTL expires and allows a new run with same key", asy
1410
1184
  redis.close();
1411
1185
  });
1412
1186
 
1413
- test("emitEvent idempotency: same key across different events does not collide", async () => {
1414
- const prefix = testPrefix();
1415
- const redis = new RedisClient(redisServer.url);
1416
- const client = createClient({ redis, prefix });
1417
- setDefaultClient(client);
1418
-
1419
- const countKey = `${prefix}:t:evtScopeCount`;
1420
-
1421
- defineWorkflow(
1422
- {
1423
- name: "evt-idem-scope",
1424
- queue: "q10_scope",
1425
- triggers: { events: ["dup.event.a", "dup.event.b"] },
1426
- },
1427
- async ({ step }) => {
1428
- await step.run({ name: "count" }, async () => {
1429
- await redis.incr(countKey);
1430
- return true;
1431
- });
1432
- return { ok: true };
1433
- },
1434
- );
1435
-
1436
- const worker = await startWorker({
1437
- redis,
1438
- prefix,
1439
- queues: ["q10_scope"],
1440
- runtime: { leaseMs: 500 },
1441
- });
1442
-
1443
- const runIdsA = await client.emitEvent("dup.event.a", { a: 1 }, { idempotencyKey: "evt_shared" });
1444
- const runIdsB = await client.emitEvent("dup.event.b", { b: 1 }, { idempotencyKey: "evt_shared" });
1445
-
1446
- expect(runIdsA.length).toBe(1);
1447
- expect(runIdsB.length).toBe(1);
1448
- expect(runIdsA[0]).not.toBe(runIdsB[0]);
1449
-
1450
- await Promise.all([
1451
- client.makeRunHandle(runIdsA[0]!).result({ timeoutMs: 5000, pollMs: 50 }),
1452
- client.makeRunHandle(runIdsB[0]!).result({ timeoutMs: 5000, pollMs: 50 }),
1453
- ]);
1454
-
1455
- const cnt = Number((await redis.get(countKey)) ?? "0");
1456
- expect(cnt).toBe(2);
1457
-
1458
- await worker.stop();
1459
- redis.close();
1460
- });
1461
-
1462
- test("scheduleEvent: delayed fan-out is idempotent", async () => {
1463
- const prefix = testPrefix();
1464
- const redis = new RedisClient(redisServer.url);
1465
- const client = createClient({ redis, prefix });
1466
- setDefaultClient(client);
1467
-
1468
- const event = "schedule.event";
1469
- const countKey = `${prefix}:t:schedule-event-count`;
1470
-
1471
- defineWorkflow(
1472
- { name: "schedule-consumer", queue: "q10_schedule", triggers: { events: [event] } },
1473
- async ({ step }) => {
1474
- await step.run({ name: "count" }, async () => {
1475
- await redis.incr(countKey);
1476
- return true;
1477
- });
1478
- return { ok: true };
1479
- },
1480
- );
1481
-
1482
- const worker = await startWorker({
1483
- redis,
1484
- prefix,
1485
- queues: ["q10_schedule"],
1486
- runtime: { leaseMs: 500 },
1487
- });
1488
-
1489
- try {
1490
- const availableAt = new Date(Date.now() + 1200);
1491
- const [runIds1, runIds2] = await Promise.all([
1492
- client.scheduleEvent(event, { x: 1 }, { availableAt, idempotencyKey: "sched-shared" }),
1493
- client.scheduleEvent(event, { x: 1 }, { availableAt, idempotencyKey: "sched-shared" }),
1494
- ]);
1495
-
1496
- expect(runIds1.length).toBe(1);
1497
- expect(runIds2.length).toBe(1);
1498
- expect(runIds1[0]).toBe(runIds2[0]);
1499
-
1500
- const runId = runIds1[0]!;
1501
- const stateBefore = await client.getRun(runId);
1502
- expect(stateBefore?.status).toBe("scheduled");
1503
-
1504
- const out = await client.makeRunHandle(runId).result({ timeoutMs: 7000, pollMs: 50 });
1505
- expect(out).toEqual({ ok: true });
1506
-
1507
- const stateAfter = await client.getRun(runId);
1508
- expect((stateAfter?.startedAt ?? 0) + 40).toBeGreaterThanOrEqual(availableAt.getTime());
1509
-
1510
- const count = Number((await redis.get(countKey)) ?? "0");
1511
- expect(count).toBe(1);
1512
- } finally {
1513
- await worker.stop();
1514
- redis.close();
1515
- }
1516
- });
1517
-
1518
- test("emitEvent: stale subscribers are pruned and do not break fan-out", async () => {
1519
- const prefix = testPrefix();
1520
- const redis = new RedisClient(redisServer.url);
1521
- const client = createClient({ redis, prefix });
1522
- setDefaultClient(client);
1523
-
1524
- const event = "stale.subscribers.event";
1525
- defineWorkflow(
1526
- { name: "stale-subscriber-live", queue: "q10_stale_subs", triggers: { events: [event] } },
1527
- async () => ({ ok: true }),
1528
- );
1529
-
1530
- const worker = await startWorker({
1531
- redis,
1532
- prefix,
1533
- queues: ["q10_stale_subs"],
1534
- runtime: { leaseMs: 500 },
1535
- });
1536
-
1537
- try {
1538
- await redis.sadd(keys.eventWorkflows(prefix, event), "stale-subscriber-ghost");
1539
-
1540
- const runIds = await client.emitEvent(event, { v: 1 });
1541
- expect(runIds.length).toBe(1);
1542
-
1543
- const subscribers = await redis.smembers(keys.eventWorkflows(prefix, event));
1544
- expect(subscribers.includes("stale-subscriber-ghost")).toBe(false);
1545
-
1546
- const out = await client.makeRunHandle(runIds[0]!).result({ timeoutMs: 5000, pollMs: 50 });
1547
- expect(out).toEqual({ ok: true });
1548
- } finally {
1549
- await worker.stop();
1550
- redis.close();
1551
- }
1552
- });
1553
-
1554
1187
  test("input validation: invalid input fails once and is not retried", async () => {
1555
1188
  const prefix = testPrefix();
1556
1189
  const redis = new RedisClient(redisServer.url);
1557
1190
  const client = createClient({ redis, prefix });
1558
1191
  setDefaultClient(client);
1559
1192
 
1560
- defineWorkflow(
1561
- {
1562
- name: "validate-wf",
1563
- queue: "q11",
1193
+ defineWorkflow("validate-wf", {queue: "q11",
1564
1194
  schema: z.object({ x: z.number() }),
1565
1195
  retries: { maxAttempts: 3 },
1566
1196
  },
@@ -1649,7 +1279,7 @@ test("cancel during step: run becomes canceled and step error kind is canceled",
1649
1279
  const client = createClient({ redis, prefix });
1650
1280
  setDefaultClient(client);
1651
1281
 
1652
- const wf = defineWorkflow({ name: "cancel-mid-step", queue: "q12" }, async ({ step }) => {
1282
+ const wf = defineWorkflow("cancel-mid-step", {queue: "q12" }, async ({ step }) => {
1653
1283
  await step.run({ name: "slow" }, async () => {
1654
1284
  await new Promise((r) => setTimeout(r, 2000));
1655
1285
  return true;
@@ -1692,7 +1322,7 @@ test("cancel during step: run becomes canceled and step error kind is canceled",
1692
1322
  expect((steps[0]?.error as any)?.kind).toBe("canceled");
1693
1323
 
1694
1324
  // `result()` should reject with CanceledError.
1695
- await expect(handle.result({ timeoutMs: 1000, pollMs: 50 })).rejects.toBeInstanceOf(CanceledError);
1325
+ await expect(handle.result({ timeoutMs: 1000 })).rejects.toBeInstanceOf(CanceledError);
1696
1326
 
1697
1327
  await worker.stop();
1698
1328
  redis.close();
@@ -1707,7 +1337,7 @@ test("terminal run re-queued is ignored (no re-execution)", async () => {
1707
1337
  const queue = "q13";
1708
1338
  const countKey = `${prefix}:t:termCount`;
1709
1339
 
1710
- const wf = defineWorkflow({ name: "term-wf", queue }, async ({ step }) => {
1340
+ const wf = defineWorkflow("term-wf", {queue }, async ({ step }) => {
1711
1341
  await step.run({ name: "do" }, async () => {
1712
1342
  await redis.incr(countKey);
1713
1343
  return true;
@@ -1724,7 +1354,7 @@ test("terminal run re-queued is ignored (no re-execution)", async () => {
1724
1354
  });
1725
1355
 
1726
1356
  const handle = await wf.run({});
1727
- const out = await handle.result({ timeoutMs: 4000, pollMs: 50 });
1357
+ const out = await handle.result({ timeoutMs: 4000 });
1728
1358
  expect(out).toEqual({ ok: true });
1729
1359
 
1730
1360
  const cnt1 = Number((await redis.get(countKey)) ?? "0");
@@ -1754,7 +1384,7 @@ test("lease+reaper: long running step is not duplicated", async () => {
1754
1384
  const queue = "q14";
1755
1385
  const countKey = `${prefix}:t:leaseCount`;
1756
1386
 
1757
- const wf = defineWorkflow({ name: "lease-wf", queue }, async ({ step }) => {
1387
+ const wf = defineWorkflow("lease-wf", {queue }, async ({ step }) => {
1758
1388
  await step.run({ name: "long" }, async () => {
1759
1389
  await redis.incr(countKey);
1760
1390
  await new Promise((r) => setTimeout(r, 700));
@@ -1772,7 +1402,7 @@ test("lease+reaper: long running step is not duplicated", async () => {
1772
1402
  });
1773
1403
 
1774
1404
  const handle = await wf.run({});
1775
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
1405
+ const out = await handle.result({ timeoutMs: 8000 });
1776
1406
  expect(out).toEqual({ ok: true });
1777
1407
 
1778
1408
  const cnt = Number((await redis.get(countKey)) ?? "0");
@@ -1796,11 +1426,8 @@ test(
1796
1426
  const queue = "q15";
1797
1427
  const counterKey = `${prefix}:t:cronLockCount`;
1798
1428
 
1799
- defineWorkflow(
1800
- {
1801
- name: "cron-lock-wf",
1802
- queue,
1803
- triggers: { cron: [{ expression: "*/1 * * * * *" }] },
1429
+ defineWorkflow("cron-lock-wf", {queue,
1430
+ cron: [{ expression: "*/1 * * * * *" }],
1804
1431
  },
1805
1432
  async ({ step }) => {
1806
1433
  await step.run({ name: "tick" }, async () => {
@@ -1839,10 +1466,7 @@ test(
1839
1466
  const childFailOnceKey = `${prefix}:t:childFailOnce`;
1840
1467
  const parentBeforeKey = `${prefix}:t:parentBefore`;
1841
1468
 
1842
- const child = defineWorkflow(
1843
- {
1844
- name: "child-wf",
1845
- queue: "q_child",
1469
+ const child = defineWorkflow("child-wf", {queue: "q_child",
1846
1470
  retries: { maxAttempts: 2 },
1847
1471
  },
1848
1472
  async ({ step, run }) => {
@@ -1864,10 +1488,7 @@ test(
1864
1488
  },
1865
1489
  );
1866
1490
 
1867
- const parent = defineWorkflow(
1868
- {
1869
- name: "parent-wf",
1870
- queue: "q_parent",
1491
+ const parent = defineWorkflow("parent-wf", {queue: "q_parent",
1871
1492
  },
1872
1493
  async ({ step }) => {
1873
1494
  await step.run({ name: "before" }, async () => {
@@ -1877,7 +1498,7 @@ test(
1877
1498
 
1878
1499
  const childOut = await step.run({ name: "child" }, async () => {
1879
1500
  const h = await child.run({});
1880
- return await h.result({ timeoutMs: 10_000, pollMs: 50 });
1501
+ return await h.result({ timeoutMs: 10_000 });
1881
1502
  });
1882
1503
 
1883
1504
  return { ok: true, child: childOut };
@@ -1893,7 +1514,7 @@ test(
1893
1514
  });
1894
1515
 
1895
1516
  const handle = await parent.run({});
1896
- const out = await handle.result({ timeoutMs: 15_000, pollMs: 50 });
1517
+ const out = await handle.result({ timeoutMs: 15_000 });
1897
1518
  expect(out.ok).toBe(true);
1898
1519
  expect(out.child.ok).toBe(true);
1899
1520
  expect(out.child.attempt).toBe(2);
@@ -1925,7 +1546,7 @@ test(
1925
1546
  setDefaultClient(client);
1926
1547
 
1927
1548
  const queue = "q_status";
1928
- const wf = defineWorkflow({ name: "status-wf", queue }, async ({ step }) => {
1549
+ const wf = defineWorkflow("status-wf", {queue }, async ({ step }) => {
1929
1550
  await step.run({ name: "sleep" }, async () => {
1930
1551
  await new Promise((r) => setTimeout(r, 350));
1931
1552
  return true;
@@ -1956,7 +1577,7 @@ test(
1956
1577
  const queuedMembers2 = await redis.zrange(queuedIndex, 0, -1);
1957
1578
  expect(queuedMembers2.includes(handle.id)).toBe(false);
1958
1579
 
1959
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
1580
+ const out = await handle.result({ timeoutMs: 8000 });
1960
1581
  expect(out).toEqual({ ok: true });
1961
1582
 
1962
1583
  const succeededMembers = await redis.zrange(`${prefix}:runs:status:succeeded`, 0, -1);
@@ -1971,7 +1592,7 @@ test(
1971
1592
  );
1972
1593
 
1973
1594
  test(
1974
- "production: registry sync updates triggers (events move, cron removed)",
1595
+ "production: registry sync updates cron definitions",
1975
1596
  async () => {
1976
1597
  const prefix = testPrefix();
1977
1598
  const redis = new RedisClient(redisServer.url);
@@ -1979,27 +1600,13 @@ test(
1979
1600
  setDefaultClient(client);
1980
1601
 
1981
1602
  const name = "update-wf";
1982
- const eventA = "evt.a";
1983
- const eventB = "evt.b";
1984
-
1985
- defineWorkflow(
1986
- {
1987
- name,
1988
- queue: "q_update",
1989
- triggers: {
1990
- events: [eventA],
1991
- cron: [{ expression: "*/1 * * * * *" }],
1992
- },
1993
- },
1994
- async () => ({ ok: true }),
1995
- );
1996
1603
 
1997
- await client.syncRegistry(getDefaultRegistry());
1604
+ defineWorkflow(name, {
1605
+ queue: "q_update",
1606
+ cron: [{ expression: "*/1 * * * * *" }],
1607
+ }, async () => ({ ok: true }));
1998
1608
 
1999
- const subsA1 = await redis.smembers(`${prefix}:event:${eventA}:workflows`);
2000
- const subsB1 = await redis.smembers(`${prefix}:event:${eventB}:workflows`);
2001
- expect(subsA1.includes(name)).toBe(true);
2002
- expect(subsB1.includes(name)).toBe(false);
1609
+ await client.syncRegistry(getDefaultRegistry());
2003
1610
 
2004
1611
  const workflowKey = `${prefix}:workflow:${name}`;
2005
1612
  const cronIdsJson = await redis.hget(workflowKey, "cronIdsJson");
@@ -2011,25 +1618,14 @@ test(
2011
1618
  const cronNext = await redis.zrange(`${prefix}:cron:next`, 0, -1);
2012
1619
  expect(cronNext.includes(cronId)).toBe(true);
2013
1620
 
2014
- // Simulate a new deployment: same workflow name, different triggers.
1621
+ // Simulate a new deployment: same workflow name, cron removed.
2015
1622
  __unstableResetDefaultRegistryForTests();
2016
- defineWorkflow(
2017
- {
2018
- name,
2019
- queue: "q_update",
2020
- triggers: { events: [eventB] },
2021
- },
2022
- async () => ({ ok: true }),
2023
- );
1623
+ defineWorkflow(name, {
1624
+ queue: "q_update",
1625
+ }, async () => ({ ok: true }));
2024
1626
 
2025
1627
  await client.syncRegistry(getDefaultRegistry());
2026
1628
 
2027
- const subsA2 = await redis.smembers(`${prefix}:event:${eventA}:workflows`);
2028
- const subsB2 = await redis.smembers(`${prefix}:event:${eventB}:workflows`);
2029
- expect(subsA2.includes(name)).toBe(false);
2030
- expect(subsB2.includes(name)).toBe(true);
2031
-
2032
- // Old cron removed.
2033
1629
  expect(await redis.hget(`${prefix}:cron:def`, cronId)).toBeNull();
2034
1630
  const cronNext2 = await redis.zrange(`${prefix}:cron:next`, 0, -1);
2035
1631
  expect(cronNext2.includes(cronId)).toBe(false);
@@ -2051,10 +1647,7 @@ test(
2051
1647
  const t1Key = `${prefix}:t:attempt1`;
2052
1648
  const t2Key = `${prefix}:t:attempt2`;
2053
1649
 
2054
- const wf = defineWorkflow(
2055
- {
2056
- name: "backoff-wf",
2057
- queue,
1650
+ const wf = defineWorkflow("backoff-wf", {queue,
2058
1651
  retries: { maxAttempts: 2 },
2059
1652
  },
2060
1653
  async ({ run }) => {
@@ -2073,7 +1666,7 @@ test(
2073
1666
  });
2074
1667
 
2075
1668
  const handle = await wf.run({});
2076
- const out = await handle.result({ timeoutMs: 15_000, pollMs: 50 });
1669
+ const out = await handle.result({ timeoutMs: 15_000 });
2077
1670
  expect(out).toEqual({ ok: true });
2078
1671
 
2079
1672
  const t1 = Number((await redis.get(t1Key)) ?? "0");
@@ -2103,11 +1696,8 @@ test(
2103
1696
  const queue = "q_failover";
2104
1697
  const counterKey = `${prefix}:t:cronFailoverCount`;
2105
1698
 
2106
- defineWorkflow(
2107
- {
2108
- name: "cron-failover-wf",
2109
- queue,
2110
- triggers: { cron: [{ expression: "*/1 * * * * *" }] },
1699
+ defineWorkflow("cron-failover-wf", {queue,
1700
+ cron: [{ expression: "*/1 * * * * *" }],
2111
1701
  },
2112
1702
  async ({ step }) => {
2113
1703
  await step.run({ name: "tick" }, async () => {
@@ -2149,8 +1739,8 @@ test("listRuns: status + workflow filters are applied together", async () => {
2149
1739
 
2150
1740
  const queue = "q_filters";
2151
1741
 
2152
- const succeeds = defineWorkflow({ name: "filters-success", queue }, async () => ({ ok: true }));
2153
- const fails = defineWorkflow({ name: "filters-fail", queue }, async () => {
1742
+ const succeeds = defineWorkflow("filters-success", {queue }, async () => ({ ok: true }));
1743
+ const fails = defineWorkflow("filters-fail", {queue }, async () => {
2154
1744
  throw new Error("expected failure");
2155
1745
  });
2156
1746
 
@@ -2159,7 +1749,7 @@ test("listRuns: status + workflow filters are applied together", async () => {
2159
1749
  const successHandle = await succeeds.run({});
2160
1750
  const failHandle = await fails.run({});
2161
1751
 
2162
- await successHandle.result({ timeoutMs: 4000, pollMs: 50 });
1752
+ await successHandle.result({ timeoutMs: 4000 });
2163
1753
  await waitFor(
2164
1754
  async () => (await client.getRun(failHandle.id))?.status === "failed",
2165
1755
  { timeoutMs: 6000, label: "failed run" },
@@ -2193,20 +1783,14 @@ test("cron trigger ids: same custom id in two workflows does not collide", async
2193
1783
  const redis = new RedisClient(redisServer.url);
2194
1784
  const client = createClient({ redis, prefix });
2195
1785
 
2196
- defineWorkflow(
2197
- {
2198
- name: "cron-id-a",
2199
- queue: "qa",
2200
- triggers: { cron: [{ id: "shared", expression: "*/5 * * * * *" }] },
1786
+ defineWorkflow("cron-id-a", {queue: "qa",
1787
+ cron: [{ id: "shared", expression: "*/5 * * * * *" }],
2201
1788
  },
2202
1789
  async () => ({ ok: true }),
2203
1790
  );
2204
1791
 
2205
- defineWorkflow(
2206
- {
2207
- name: "cron-id-b",
2208
- queue: "qb",
2209
- triggers: { cron: [{ id: "shared", expression: "*/5 * * * * *" }] },
1792
+ defineWorkflow("cron-id-b", {queue: "qb",
1793
+ cron: [{ id: "shared", expression: "*/5 * * * * *" }],
2210
1794
  },
2211
1795
  async () => ({ ok: true }),
2212
1796
  );
@@ -2233,11 +1817,8 @@ test("syncRegistry: cron trigger with explicit null input preserves null payload
2233
1817
 
2234
1818
  const workflowName = "cron-null-input";
2235
1819
 
2236
- defineWorkflow(
2237
- {
2238
- name: workflowName,
2239
- queue: "q-cron-null",
2240
- triggers: { cron: [{ id: "null-input", expression: "*/5 * * * * *", input: null }] },
1820
+ defineWorkflow(workflowName, {queue: "q-cron-null",
1821
+ cron: [{ id: "null-input", expression: "*/5 * * * * *", input: null }],
2241
1822
  },
2242
1823
  async () => ({ ok: true }),
2243
1824
  );
@@ -2267,11 +1848,8 @@ test("syncRegistry: invalid cron expression removes existing next schedule for s
2267
1848
  const workflowKey = `${prefix}:workflow:${workflowName}`;
2268
1849
  const cronNextKey = `${prefix}:cron:next`;
2269
1850
 
2270
- defineWorkflow(
2271
- {
2272
- name: workflowName,
2273
- queue: "q-cron-invalid",
2274
- triggers: { cron: [{ id: "stable", expression: "*/5 * * * * *" }] },
1851
+ defineWorkflow(workflowName, {queue: "q-cron-invalid",
1852
+ cron: [{ id: "stable", expression: "*/5 * * * * *" }],
2275
1853
  },
2276
1854
  async () => ({ ok: true }),
2277
1855
  );
@@ -2286,11 +1864,8 @@ test("syncRegistry: invalid cron expression removes existing next schedule for s
2286
1864
  expect(before.includes(cronId)).toBe(true);
2287
1865
 
2288
1866
  __unstableResetDefaultRegistryForTests();
2289
- defineWorkflow(
2290
- {
2291
- name: workflowName,
2292
- queue: "q-cron-invalid",
2293
- triggers: { cron: [{ id: "stable", expression: "not a valid cron" }] },
1867
+ defineWorkflow(workflowName, {queue: "q-cron-invalid",
1868
+ cron: [{ id: "stable", expression: "not a valid cron" }],
2294
1869
  },
2295
1870
  async () => ({ ok: true }),
2296
1871
  );
@@ -2303,19 +1878,15 @@ test("syncRegistry: invalid cron expression removes existing next schedule for s
2303
1878
  redis.close();
2304
1879
  });
2305
1880
 
2306
- test("syncRegistry: removes stale workflow metadata (events + cron)", async () => {
1881
+ test("syncRegistry: removes stale workflow metadata (cron)", async () => {
2307
1882
  const prefix = testPrefix();
2308
1883
  const redis = new RedisClient(redisServer.url);
2309
1884
  const client = createClient({ redis, prefix });
2310
1885
 
2311
1886
  const workflowName = "stale-workflow";
2312
- const eventName = "stale.event";
2313
1887
 
2314
- defineWorkflow(
2315
- {
2316
- name: workflowName,
2317
- queue: "q-stale",
2318
- triggers: { events: [eventName], cron: [{ id: "stale", expression: "*/10 * * * * *" }] },
1888
+ defineWorkflow(workflowName, {queue: "q-stale",
1889
+ cron: [{ id: "stale", expression: "*/10 * * * * *" }],
2319
1890
  },
2320
1891
  async () => ({ ok: true }),
2321
1892
  );
@@ -2327,7 +1898,6 @@ test("syncRegistry: removes stale workflow metadata (events + cron)", async () =
2327
1898
  expect(cronIds.length).toBe(1);
2328
1899
  const cronId = cronIds[0]!;
2329
1900
 
2330
- expect((await redis.smembers(`${prefix}:event:${eventName}:workflows`)).includes(workflowName)).toBe(true);
2331
1901
  expect(await redis.hget(`${prefix}:cron:def`, cronId)).not.toBeNull();
2332
1902
 
2333
1903
  // Force stale age beyond grace period and sync an empty registry.
@@ -2336,7 +1906,6 @@ test("syncRegistry: removes stale workflow metadata (events + cron)", async () =
2336
1906
  await client.syncRegistry(getDefaultRegistry());
2337
1907
 
2338
1908
  expect((await redis.smembers(`${prefix}:workflows`)).includes(workflowName)).toBe(false);
2339
- expect((await redis.smembers(`${prefix}:event:${eventName}:workflows`)).includes(workflowName)).toBe(false);
2340
1909
  expect(await redis.hget(workflowKey, "queue")).toBeNull();
2341
1910
  expect(await redis.hget(`${prefix}:cron:def`, cronId)).toBeNull();
2342
1911
  const cronNext = await redis.zrange(`${prefix}:cron:next`, 0, -1);
@@ -2351,22 +1920,16 @@ test("syncRegistry: stale cleanup is isolated by owner", async () => {
2351
1920
  const client = createClient({ redis, prefix });
2352
1921
 
2353
1922
  __unstableResetDefaultRegistryForTests();
2354
- defineWorkflow(
2355
- {
2356
- name: "owner-a-wf",
2357
- queue: "qa",
2358
- triggers: { events: ["owner.a"], cron: [{ id: "a", expression: "*/30 * * * * *" }] },
1923
+ defineWorkflow("owner-a-wf", {queue: "qa",
1924
+ cron: [{ id: "a", expression: "*/30 * * * * *" }],
2359
1925
  },
2360
1926
  async () => ({ ok: true }),
2361
1927
  );
2362
1928
  await client.syncRegistry(getDefaultRegistry(), { owner: "svc-a" });
2363
1929
 
2364
1930
  __unstableResetDefaultRegistryForTests();
2365
- defineWorkflow(
2366
- {
2367
- name: "owner-b-wf",
2368
- queue: "qb",
2369
- triggers: { events: ["owner.b"], cron: [{ id: "b", expression: "*/30 * * * * *" }] },
1931
+ defineWorkflow("owner-b-wf", {queue: "qb",
1932
+ cron: [{ id: "b", expression: "*/30 * * * * *" }],
2370
1933
  },
2371
1934
  async () => ({ ok: true }),
2372
1935
  );
@@ -2392,7 +1955,7 @@ test("cancelRun: terminal runs are unchanged", async () => {
2392
1955
  const client = createClient({ redis, prefix });
2393
1956
  setDefaultClient(client);
2394
1957
 
2395
- const wf = defineWorkflow({ name: "cancel-terminal", queue: "q_cancel_terminal" }, async () => ({ ok: true }));
1958
+ const wf = defineWorkflow("cancel-terminal", {queue: "q_cancel_terminal" }, async () => ({ ok: true }));
2396
1959
 
2397
1960
  const worker = await startWorker({
2398
1961
  redis,
@@ -2402,7 +1965,7 @@ test("cancelRun: terminal runs are unchanged", async () => {
2402
1965
  });
2403
1966
 
2404
1967
  const handle = await wf.run({});
2405
- await handle.result({ timeoutMs: 4000, pollMs: 50 });
1968
+ await handle.result({ timeoutMs: 4000 });
2406
1969
 
2407
1970
  const before = await client.getRun(handle.id);
2408
1971
  const canceled = await client.cancelRun(handle.id, { reason: "late" });
@@ -2423,10 +1986,7 @@ test("output serialization: non-JSON output fails once and is not stuck", async
2423
1986
  const client = createClient({ redis, prefix });
2424
1987
  setDefaultClient(client);
2425
1988
 
2426
- const wf = defineWorkflow(
2427
- {
2428
- name: "serialization-fail",
2429
- queue: "q_serial",
1989
+ const wf = defineWorkflow("serialization-fail", {queue: "q_serial",
2430
1990
  retries: { maxAttempts: 3 },
2431
1991
  },
2432
1992
  async () => ({ bad: 1n }),
@@ -2467,10 +2027,7 @@ test("retry step sequence: new steps keep monotonic order after cached steps", a
2467
2027
 
2468
2028
  const failOnceKey = `${prefix}:t:seq-fail-once`;
2469
2029
 
2470
- const wf = defineWorkflow(
2471
- {
2472
- name: "seq-retry",
2473
- queue: "q_seq",
2030
+ const wf = defineWorkflow("seq-retry", {queue: "q_seq",
2474
2031
  retries: { maxAttempts: 2 },
2475
2032
  },
2476
2033
  async ({ step }) => {
@@ -2498,7 +2055,7 @@ test("retry step sequence: new steps keep monotonic order after cached steps", a
2498
2055
  });
2499
2056
 
2500
2057
  const handle = await wf.run({});
2501
- await handle.result({ timeoutMs: 12_000, pollMs: 50 });
2058
+ await handle.result({ timeoutMs: 12_000 });
2502
2059
 
2503
2060
  const rawSteps = await redis.hgetall(`${prefix}:run:${handle.id}:steps`);
2504
2061
  const seq1 = safeJsonParse<any>(rawSteps.step1 ?? null).seq;
@@ -2520,7 +2077,7 @@ test("listRuns: negative offset is clamped to zero", async () => {
2520
2077
  const client = createClient({ redis, prefix });
2521
2078
  setDefaultClient(client);
2522
2079
 
2523
- const wf = defineWorkflow({ name: "offset-wf", queue: "q_offset" }, async () => ({ ok: true }));
2080
+ const wf = defineWorkflow("offset-wf", {queue: "q_offset" }, async () => ({ ok: true }));
2524
2081
  const worker = await startWorker({
2525
2082
  redis,
2526
2083
  prefix,
@@ -2529,13 +2086,13 @@ test("listRuns: negative offset is clamped to zero", async () => {
2529
2086
  });
2530
2087
 
2531
2088
  const h1 = await wf.run({});
2532
- await h1.result({ timeoutMs: 4000, pollMs: 50 });
2089
+ await h1.result({ timeoutMs: 4000 });
2533
2090
  await new Promise((r) => setTimeout(r, 5));
2534
2091
  const h2 = await wf.run({});
2535
- await h2.result({ timeoutMs: 4000, pollMs: 50 });
2092
+ await h2.result({ timeoutMs: 4000 });
2536
2093
  await new Promise((r) => setTimeout(r, 5));
2537
2094
  const h3 = await wf.run({});
2538
- await h3.result({ timeoutMs: 4000, pollMs: 50 });
2095
+ await h3.result({ timeoutMs: 4000 });
2539
2096
 
2540
2097
  const fromZero = await client.listRuns({ workflow: "offset-wf", limit: 1, offset: 0 });
2541
2098
  const fromNegative = await client.listRuns({ workflow: "offset-wf", limit: 1, offset: -1 });
@@ -2577,10 +2134,7 @@ test("error serialization: non-serializable thrown values do not wedge a run", a
2577
2134
  const client = createClient({ redis, prefix });
2578
2135
  setDefaultClient(client);
2579
2136
 
2580
- const wf = defineWorkflow(
2581
- {
2582
- name: "throw-bigint",
2583
- queue: "q_bigint",
2137
+ const wf = defineWorkflow("throw-bigint", {queue: "q_bigint",
2584
2138
  retries: { maxAttempts: 2 },
2585
2139
  },
2586
2140
  async () => {
@@ -2619,20 +2173,14 @@ test("cron trigger ids: delimiter-like custom ids do not collide", async () => {
2619
2173
  const redis = new RedisClient(redisServer.url);
2620
2174
  const client = createClient({ redis, prefix });
2621
2175
 
2622
- defineWorkflow(
2623
- {
2624
- name: "wf:a",
2625
- queue: "qa",
2626
- triggers: { cron: [{ id: "b:c", expression: "*/5 * * * * *" }] },
2176
+ defineWorkflow("wf:a", {queue: "qa",
2177
+ cron: [{ id: "b:c", expression: "*/5 * * * * *" }],
2627
2178
  },
2628
2179
  async () => ({ ok: true }),
2629
2180
  );
2630
2181
 
2631
- defineWorkflow(
2632
- {
2633
- name: "wf:a:b",
2634
- queue: "qb",
2635
- triggers: { cron: [{ id: "c", expression: "*/5 * * * * *" }] },
2182
+ defineWorkflow("wf:a:b", {queue: "qb",
2183
+ cron: [{ id: "c", expression: "*/5 * * * * *" }],
2636
2184
  },
2637
2185
  async () => ({ ok: true }),
2638
2186
  );
@@ -2669,7 +2217,7 @@ test("idempotencyKey: stale pointer is recovered instead of returning missing ru
2669
2217
  const workflowName = "idem-recover";
2670
2218
  const idem = "stale-key";
2671
2219
 
2672
- const wf = defineWorkflow({ name: workflowName, queue }, async () => ({ ok: true }));
2220
+ const wf = defineWorkflow(workflowName, {queue }, async () => ({ ok: true }));
2673
2221
 
2674
2222
  const worker = await startWorker({
2675
2223
  redis,
@@ -2685,7 +2233,7 @@ test("idempotencyKey: stale pointer is recovered instead of returning missing ru
2685
2233
  const handle = await wf.run({}, { idempotencyKey: idem });
2686
2234
  expect(handle.id).not.toBe(staleRunId);
2687
2235
 
2688
- const out = await handle.result({ timeoutMs: 5000, pollMs: 50 });
2236
+ const out = await handle.result({ timeoutMs: 5000 });
2689
2237
  expect(out).toEqual({ ok: true });
2690
2238
 
2691
2239
  const resolved = await redis.get(idempotencyRedisKey);
@@ -2706,7 +2254,7 @@ test("idempotencyKey: partial existing run is repaired and executed", async () =
2706
2254
  const idem = "partial-key";
2707
2255
  const countKey = `${prefix}:t:idem-partial-count`;
2708
2256
 
2709
- const wf = defineWorkflow({ name: workflowName, queue }, async ({ step }) => {
2257
+ const wf = defineWorkflow(workflowName, {queue }, async ({ step }) => {
2710
2258
  await step.run({ name: "do" }, async () => {
2711
2259
  await redis.incr(countKey);
2712
2260
  return true;
@@ -2742,7 +2290,7 @@ test("idempotencyKey: partial existing run is repaired and executed", async () =
2742
2290
  const handle = await wf.run({}, { idempotencyKey: idem });
2743
2291
  expect(handle.id).toBe(runId);
2744
2292
 
2745
- const out = await handle.result({ timeoutMs: 6000, pollMs: 50 });
2293
+ const out = await handle.result({ timeoutMs: 6000 });
2746
2294
  expect(out).toEqual({ ok: true });
2747
2295
 
2748
2296
  const count = Number((await redis.get(countKey)) ?? "0");
@@ -2757,16 +2305,11 @@ test("syncRegistry: duplicate custom cron ids in one workflow are rejected", asy
2757
2305
  const redis = new RedisClient(redisServer.url);
2758
2306
  const client = createClient({ redis, prefix });
2759
2307
 
2760
- defineWorkflow(
2761
- {
2762
- name: "dup-custom-cron-id",
2763
- queue: "q_dup_cron_id",
2764
- triggers: {
2765
- cron: [
2766
- { id: "same", expression: "*/5 * * * * *", input: { n: 1 } },
2767
- { id: "same", expression: "*/7 * * * * *", input: { n: 2 } },
2768
- ],
2769
- },
2308
+ defineWorkflow("dup-custom-cron-id", {queue: "q_dup_cron_id",
2309
+ cron: [
2310
+ { id: "same", expression: "*/5 * * * * *", input: { n: 1 } },
2311
+ { id: "same", expression: "*/7 * * * * *", input: { n: 2 } },
2312
+ ],
2770
2313
  },
2771
2314
  async () => ({ ok: true }),
2772
2315
  );
@@ -2783,7 +2326,7 @@ test("scheduled promoter: stale scheduled entry does not resurrect terminal run"
2783
2326
  setDefaultClient(client);
2784
2327
 
2785
2328
  const queue = "q_sched_guard";
2786
- const wf = defineWorkflow({ name: "sched-guard", queue }, async ({ step }) => {
2329
+ const wf = defineWorkflow("sched-guard", {queue }, async ({ step }) => {
2787
2330
  await step.run({ name: "once" }, async () => true);
2788
2331
  return { ok: true };
2789
2332
  });
@@ -2796,7 +2339,7 @@ test("scheduled promoter: stale scheduled entry does not resurrect terminal run"
2796
2339
  });
2797
2340
 
2798
2341
  const handle = await wf.run({});
2799
- const out = await handle.result({ timeoutMs: 5000, pollMs: 50 });
2342
+ const out = await handle.result({ timeoutMs: 5000 });
2800
2343
  expect(out).toEqual({ ok: true });
2801
2344
 
2802
2345
  const firstState = await client.getRun(handle.id);
@@ -2824,11 +2367,8 @@ test(
2824
2367
  const workflowName = "cron-retry";
2825
2368
  const counterKey = `${prefix}:t:cron-retry-count`;
2826
2369
 
2827
- defineWorkflow(
2828
- {
2829
- name: workflowName,
2830
- queue,
2831
- triggers: { cron: [{ expression: "*/1 * * * * *" }] },
2370
+ defineWorkflow(workflowName, {queue,
2371
+ cron: [{ expression: "*/1 * * * * *" }],
2832
2372
  },
2833
2373
  async ({ step }) => {
2834
2374
  await step.run({ name: "tick" }, async () => {
@@ -2893,11 +2433,8 @@ test(
2893
2433
  const counterKey = `${prefix}:t:cronStaleCount`;
2894
2434
  const cronLockKey = keys.lockCron(prefix);
2895
2435
 
2896
- defineWorkflow(
2897
- {
2898
- name: workflowName,
2899
- queue,
2900
- triggers: { cron: [{ expression: "*/1 * * * * *" }] },
2436
+ defineWorkflow(workflowName, {queue,
2437
+ cron: [{ expression: "*/1 * * * * *" }],
2901
2438
  },
2902
2439
  async ({ step }) => {
2903
2440
  await step.run({ name: "tick" }, async () => {
@@ -2950,8 +2487,8 @@ test("workflow names ending with ':runs' do not collide with workflow run indexe
2950
2487
  setDefaultClient(client);
2951
2488
 
2952
2489
  const queue = "q_keyspace";
2953
- const base = defineWorkflow({ name: "keyspace-base", queue }, async () => ({ ok: "base" }));
2954
- defineWorkflow({ name: "keyspace-base:runs", queue }, async () => ({ ok: "suffix" }));
2490
+ const base = defineWorkflow("keyspace-base", {queue }, async () => ({ ok: "base" }));
2491
+ defineWorkflow("keyspace-base:runs", {queue }, async () => ({ ok: "suffix" }));
2955
2492
 
2956
2493
  expect(keys.workflow(prefix, "keyspace-base:runs")).not.toBe(keys.workflowRuns(prefix, "keyspace-base"));
2957
2494
 
@@ -2963,7 +2500,7 @@ test("workflow names ending with ':runs' do not collide with workflow run indexe
2963
2500
  });
2964
2501
 
2965
2502
  const handle = await base.run({});
2966
- const out = await handle.result({ timeoutMs: 5000, pollMs: 50 });
2503
+ const out = await handle.result({ timeoutMs: 5000 });
2967
2504
  expect(out).toEqual({ ok: "base" });
2968
2505
 
2969
2506
  const suffixMeta = await client.getWorkflowMeta("keyspace-base:runs");
@@ -2980,7 +2517,7 @@ test("cancelRun race: near-finish cancellation settles as canceled", async () =>
2980
2517
  setDefaultClient(client);
2981
2518
 
2982
2519
  const queue = "q_cancel_late";
2983
- const wf = defineWorkflow({ name: "cancel-late", queue }, async ({ step }) => {
2520
+ const wf = defineWorkflow("cancel-late", {queue }, async ({ step }) => {
2984
2521
  await step.run({ name: "short" }, async () => {
2985
2522
  await new Promise((r) => setTimeout(r, 100));
2986
2523
  return true;
@@ -3025,7 +2562,7 @@ test("cancelRun race: near-finish cancellation settles as canceled", async () =>
3025
2562
  const canceled = await cancelPromise;
3026
2563
  expect(canceled).toBe(true);
3027
2564
 
3028
- await expect(handle.result({ timeoutMs: 8000, pollMs: 50 })).rejects.toBeInstanceOf(CanceledError);
2565
+ await expect(handle.result({ timeoutMs: 8000 })).rejects.toBeInstanceOf(CanceledError);
3029
2566
 
3030
2567
  const state = await client.getRun(handle.id);
3031
2568
  expect(state?.status).toBe("canceled");
@@ -3044,7 +2581,7 @@ test("cancelRun race: cancellation requested during finalize wins over success",
3044
2581
  setDefaultClient(client);
3045
2582
 
3046
2583
  const queue = "q_cancel_finalize_race";
3047
- const wf = defineWorkflow({ name: "cancel-finalize-race", queue }, async () => {
2584
+ const wf = defineWorkflow("cancel-finalize-race", {queue }, async () => {
3048
2585
  return { ok: true };
3049
2586
  });
3050
2587
 
@@ -3089,7 +2626,7 @@ test("cancelRun race: cancellation requested during finalize wins over success",
3089
2626
  { timeoutMs: 6000, label: "run canceled in finalize race" },
3090
2627
  );
3091
2628
 
3092
- await expect(handle.result({ timeoutMs: 1000, pollMs: 50 })).rejects.toBeInstanceOf(CanceledError);
2629
+ await expect(handle.result({ timeoutMs: 1000 })).rejects.toBeInstanceOf(CanceledError);
3093
2630
 
3094
2631
  const state = await client.getRun(handle.id);
3095
2632
  expect(state?.status).toBe("canceled");
@@ -3109,7 +2646,7 @@ test(
3109
2646
  setDefaultClient(client);
3110
2647
 
3111
2648
  const queue = "q_stop_abort";
3112
- const wf = defineWorkflow({ name: "stop-abort", queue }, async () => {
2649
+ const wf = defineWorkflow("stop-abort", {queue }, async () => {
3113
2650
  await new Promise<never>(() => {});
3114
2651
  });
3115
2652
 
@@ -3151,4 +2688,4 @@ test(
3151
2688
  }
3152
2689
  },
3153
2690
  { timeout: 20_000 },
3154
- );
2691
+ );