@redflow/client 0.0.1 → 0.0.2

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,7 @@ 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 () => {
563
- const prefix = testPrefix();
564
- const redis = new RedisClient(redisServer.url);
565
- const client = createClient({ redis, prefix });
566
- setDefaultClient(client);
567
-
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")}`;
599
-
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 },
604
- );
605
-
606
- return {
607
- autoCount: auto.length,
608
- customCount: custom.length,
609
- };
610
- });
611
-
612
- const worker = await startWorker({
613
- redis,
614
- prefix,
615
- queues: ["q_emit_parent", "q_emit_a", "q_emit_b"],
616
- concurrency: 2,
617
- runtime: { leaseMs: 500 },
618
- });
619
-
620
- 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 });
624
-
625
- expect(observedIdempotencyKeys.length).toBe(2);
626
- expect(observedIdempotencyKeys[0]).toBe(expectedAutoIdempotencyKey);
627
- expect(observedIdempotencyKeys[1]).toBe("evt-custom");
628
- } finally {
629
- RedflowClient.prototype.emitEvent = originalEmitEvent;
630
- await worker.stop();
631
- redis.close();
632
- }
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
535
 
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
- );
782
-
783
- await new Promise((r) => setTimeout(r, 250));
784
- const counter = Number((await redis.get(counterKey)) ?? "0");
785
- expect(counter).toBe(1);
786
-
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
536
 
798
537
  test("retries: step results are cached across attempts", async () => {
799
538
  const prefix = testPrefix();
@@ -804,10 +543,7 @@ test("retries: step results are cached across attempts", async () => {
804
543
  const step1CountKey = `${prefix}:t:step1Count`;
805
544
  const failOnceKey = `${prefix}:t:failOnce`;
806
545
 
807
- const wf = defineWorkflow(
808
- {
809
- name: "retry-wf",
810
- queue: "q5",
546
+ const wf = defineWorkflow("retry-wf", {queue: "q5",
811
547
  retries: { maxAttempts: 2 },
812
548
  },
813
549
  async ({ step }) => {
@@ -837,7 +573,7 @@ test("retries: step results are cached across attempts", async () => {
837
573
  });
838
574
 
839
575
  const handle = await wf.run({});
840
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
576
+ const out = await handle.result({ timeoutMs: 8000 });
841
577
  expect(out).toEqual({ ok: true });
842
578
 
843
579
  const step1Count = Number((await redis.get(step1CountKey)) ?? "0");
@@ -859,10 +595,7 @@ test("retries: run queued before worker start keeps workflow maxAttempts", async
859
595
 
860
596
  const failOnceKey = `${prefix}:t:retry-before-sync`;
861
597
 
862
- const wf = defineWorkflow(
863
- {
864
- name: "retry-before-sync",
865
- queue: "q5_before_sync",
598
+ const wf = defineWorkflow("retry-before-sync", {queue: "q5_before_sync",
866
599
  retries: { maxAttempts: 2 },
867
600
  },
868
601
  async () => {
@@ -887,7 +620,7 @@ test("retries: run queued before worker start keeps workflow maxAttempts", async
887
620
  runtime: { leaseMs: 500 },
888
621
  });
889
622
 
890
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
623
+ const out = await handle.result({ timeoutMs: 8000 });
891
624
  expect(out).toEqual({ ok: true });
892
625
 
893
626
  const state = await client.getRun(handle.id);
@@ -905,7 +638,7 @@ test("step timeout: run fails and error is recorded", async () => {
905
638
  const client = createClient({ redis, prefix });
906
639
  setDefaultClient(client);
907
640
 
908
- const wf = defineWorkflow({ name: "timeout-wf", queue: "q6" }, async ({ step }) => {
641
+ const wf = defineWorkflow("timeout-wf", {queue: "q6" }, async ({ step }) => {
909
642
  await step.run({ name: "slow", timeoutMs: 80 }, async () => {
910
643
  await new Promise((r) => setTimeout(r, 250));
911
644
  return true;
@@ -949,7 +682,7 @@ test("cancellation: scheduled/queued/running", async () => {
949
682
 
950
683
  const touchedKey = `${prefix}:t:touched`;
951
684
 
952
- const wf = defineWorkflow({ name: "cancel-wf", queue: "q7" }, async ({ step }) => {
685
+ const wf = defineWorkflow("cancel-wf", {queue: "q7" }, async ({ step }) => {
953
686
  await step.run({ name: "hold" }, async () => {
954
687
  await new Promise((r) => setTimeout(r, 600));
955
688
  return true;
@@ -970,7 +703,7 @@ test("cancellation: scheduled/queued/running", async () => {
970
703
  runtime: { leaseMs: 300 },
971
704
  });
972
705
 
973
- const handle = await wf.run({}, { availableAt: new Date(Date.now() + 300) });
706
+ const handle = await wf.run({}, { runAt: new Date(Date.now() + 300) });
974
707
  const canceled = await client.cancelRun(handle.id, { reason: "test" });
975
708
  expect(canceled).toBe(true);
976
709
 
@@ -1042,7 +775,7 @@ test("cancel race: queued cancel before start does not execute handler", async (
1042
775
  const queue = "q7_race";
1043
776
  const sideEffectsKey = `${prefix}:t:cancelRaceSideEffects`;
1044
777
 
1045
- const wf = defineWorkflow({ name: "cancel-race-wf", queue }, async () => {
778
+ const wf = defineWorkflow("cancel-race-wf", {queue }, async () => {
1046
779
  await redis.incr(sideEffectsKey);
1047
780
  return { ok: true };
1048
781
  });
@@ -1195,7 +928,7 @@ test("idempotencyKey: same key returns same run id and executes once", async ()
1195
928
 
1196
929
  const countKey = `${prefix}:t:idemCount`;
1197
930
 
1198
- const wf = defineWorkflow({ name: "idem-wf", queue: "q9" }, async ({ step }) => {
931
+ const wf = defineWorkflow("idem-wf", {queue: "q9" }, async ({ step }) => {
1199
932
  await step.run({ name: "do" }, async () => {
1200
933
  await redis.incr(countKey);
1201
934
  return true;
@@ -1213,7 +946,7 @@ test("idempotencyKey: same key returns same run id and executes once", async ()
1213
946
  const [h1, h2] = await Promise.all([wf.run({}, { idempotencyKey: "k" }), wf.run({}, { idempotencyKey: "k" })]);
1214
947
  expect(h1.id).toBe(h2.id);
1215
948
 
1216
- const out = await h1.result({ timeoutMs: 4000, pollMs: 50 });
949
+ const out = await h1.result({ timeoutMs: 4000 });
1217
950
  expect(out).toEqual({ ok: true });
1218
951
 
1219
952
  // Give the worker a moment to potentially pick duplicates.
@@ -1233,7 +966,7 @@ test("idempotencyKey: delayed TTL refresh cannot fork duplicate runs", async ()
1233
966
 
1234
967
  const countKey = `${prefix}:t:idem-race-fix-count`;
1235
968
 
1236
- const wf = defineWorkflow({ name: "idem-race-fix", queue: "q9_race_fix" }, async ({ step }) => {
969
+ const wf = defineWorkflow("idem-race-fix", {queue: "q9_race_fix" }, async ({ step }) => {
1237
970
  await step.run({ name: "do" }, async () => {
1238
971
  await redis.incr(countKey);
1239
972
  return true;
@@ -1267,8 +1000,8 @@ test("idempotencyKey: delayed TTL refresh cannot fork duplicate runs", async ()
1267
1000
  expect(h1.id).toBe(h2.id);
1268
1001
 
1269
1002
  const [out1, out2] = await Promise.all([
1270
- h1.result({ timeoutMs: 5000, pollMs: 50 }),
1271
- h2.result({ timeoutMs: 5000, pollMs: 50 }),
1003
+ h1.result({ timeoutMs: 5000 }),
1004
+ h2.result({ timeoutMs: 5000 }),
1272
1005
  ]);
1273
1006
  expect(out1).toEqual({ ok: true });
1274
1007
  expect(out2).toEqual({ ok: true });
@@ -1288,7 +1021,7 @@ test("enqueue: producer-side zadd failure does not leave runs orphaned", async (
1288
1021
  const client = createClient({ redis, prefix });
1289
1022
  setDefaultClient(client);
1290
1023
 
1291
- const wf = defineWorkflow({ name: "enqueue-atomic", queue: "q_enqueue_atomic" }, async () => ({ ok: true }));
1024
+ const wf = defineWorkflow("enqueue-atomic", {queue: "q_enqueue_atomic" }, async () => ({ ok: true }));
1292
1025
  await client.syncRegistry(getDefaultRegistry());
1293
1026
 
1294
1027
  const originalZadd = redis.zadd.bind(redis);
@@ -1316,7 +1049,7 @@ test("enqueue: producer-side zadd failure does not leave runs orphaned", async (
1316
1049
  });
1317
1050
 
1318
1051
  try {
1319
- const out = await handle.result({ timeoutMs: 6000, pollMs: 50 });
1052
+ const out = await handle.result({ timeoutMs: 6000 });
1320
1053
  expect(out).toEqual({ ok: true });
1321
1054
  } finally {
1322
1055
  await worker.stop();
@@ -1330,8 +1063,8 @@ test("idempotencyKey: delimiter-like workflow/key pairs do not collide", async (
1330
1063
  const client = createClient({ redis, prefix });
1331
1064
  setDefaultClient(client);
1332
1065
 
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" }));
1066
+ const wfA = defineWorkflow("idem:a", {queue: "q9a" }, async () => ({ workflow: "idem:a" }));
1067
+ const wfB = defineWorkflow("idem:a:b", {queue: "q9b" }, async () => ({ workflow: "idem:a:b" }));
1335
1068
 
1336
1069
  const worker = await startWorker({
1337
1070
  redis,
@@ -1349,8 +1082,8 @@ test("idempotencyKey: delimiter-like workflow/key pairs do not collide", async (
1349
1082
  expect(h1.id).not.toBe(h2.id);
1350
1083
 
1351
1084
  const [out1, out2] = await Promise.all([
1352
- h1.result({ timeoutMs: 5000, pollMs: 50 }),
1353
- h2.result({ timeoutMs: 5000, pollMs: 50 }),
1085
+ h1.result({ timeoutMs: 5000 }),
1086
+ h2.result({ timeoutMs: 5000 }),
1354
1087
  ]);
1355
1088
 
1356
1089
  expect(out1).toEqual({ workflow: "idem:a" });
@@ -1368,7 +1101,7 @@ test("idempotencyTtl: short TTL expires and allows a new run with same key", asy
1368
1101
 
1369
1102
  const countKey = `${prefix}:t:idemTtlCount`;
1370
1103
 
1371
- const wf = defineWorkflow({ name: "idem-ttl-wf", queue: "q_idem_ttl" }, async ({ step }) => {
1104
+ const wf = defineWorkflow("idem-ttl-wf", {queue: "q_idem_ttl" }, async ({ step }) => {
1372
1105
  await step.run({ name: "count" }, async () => {
1373
1106
  await redis.incr(countKey);
1374
1107
  return true;
@@ -1385,7 +1118,7 @@ test("idempotencyTtl: short TTL expires and allows a new run with same key", asy
1385
1118
 
1386
1119
  // First run with 1-second TTL
1387
1120
  const h1 = await wf.run({}, { idempotencyKey: "ttl-key", idempotencyTtl: 1 });
1388
- const out1 = await h1.result({ timeoutMs: 5000, pollMs: 50 });
1121
+ const out1 = await h1.result({ timeoutMs: 5000 });
1389
1122
  expect(out1).toEqual({ ok: true });
1390
1123
 
1391
1124
  // Same key within TTL — returns existing run
@@ -1399,7 +1132,7 @@ test("idempotencyTtl: short TTL expires and allows a new run with same key", asy
1399
1132
  const h3 = await wf.run({}, { idempotencyKey: "ttl-key", idempotencyTtl: 1 });
1400
1133
  expect(h3.id).not.toBe(h1.id);
1401
1134
 
1402
- const out3 = await h3.result({ timeoutMs: 5000, pollMs: 50 });
1135
+ const out3 = await h3.result({ timeoutMs: 5000 });
1403
1136
  expect(out3).toEqual({ ok: true });
1404
1137
 
1405
1138
  // Handler executed twice (once per unique run)
@@ -1410,157 +1143,13 @@ test("idempotencyTtl: short TTL expires and allows a new run with same key", asy
1410
1143
  redis.close();
1411
1144
  });
1412
1145
 
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
1146
  test("input validation: invalid input fails once and is not retried", async () => {
1555
1147
  const prefix = testPrefix();
1556
1148
  const redis = new RedisClient(redisServer.url);
1557
1149
  const client = createClient({ redis, prefix });
1558
1150
  setDefaultClient(client);
1559
1151
 
1560
- defineWorkflow(
1561
- {
1562
- name: "validate-wf",
1563
- queue: "q11",
1152
+ defineWorkflow("validate-wf", {queue: "q11",
1564
1153
  schema: z.object({ x: z.number() }),
1565
1154
  retries: { maxAttempts: 3 },
1566
1155
  },
@@ -1649,7 +1238,7 @@ test("cancel during step: run becomes canceled and step error kind is canceled",
1649
1238
  const client = createClient({ redis, prefix });
1650
1239
  setDefaultClient(client);
1651
1240
 
1652
- const wf = defineWorkflow({ name: "cancel-mid-step", queue: "q12" }, async ({ step }) => {
1241
+ const wf = defineWorkflow("cancel-mid-step", {queue: "q12" }, async ({ step }) => {
1653
1242
  await step.run({ name: "slow" }, async () => {
1654
1243
  await new Promise((r) => setTimeout(r, 2000));
1655
1244
  return true;
@@ -1692,7 +1281,7 @@ test("cancel during step: run becomes canceled and step error kind is canceled",
1692
1281
  expect((steps[0]?.error as any)?.kind).toBe("canceled");
1693
1282
 
1694
1283
  // `result()` should reject with CanceledError.
1695
- await expect(handle.result({ timeoutMs: 1000, pollMs: 50 })).rejects.toBeInstanceOf(CanceledError);
1284
+ await expect(handle.result({ timeoutMs: 1000 })).rejects.toBeInstanceOf(CanceledError);
1696
1285
 
1697
1286
  await worker.stop();
1698
1287
  redis.close();
@@ -1707,7 +1296,7 @@ test("terminal run re-queued is ignored (no re-execution)", async () => {
1707
1296
  const queue = "q13";
1708
1297
  const countKey = `${prefix}:t:termCount`;
1709
1298
 
1710
- const wf = defineWorkflow({ name: "term-wf", queue }, async ({ step }) => {
1299
+ const wf = defineWorkflow("term-wf", {queue }, async ({ step }) => {
1711
1300
  await step.run({ name: "do" }, async () => {
1712
1301
  await redis.incr(countKey);
1713
1302
  return true;
@@ -1724,7 +1313,7 @@ test("terminal run re-queued is ignored (no re-execution)", async () => {
1724
1313
  });
1725
1314
 
1726
1315
  const handle = await wf.run({});
1727
- const out = await handle.result({ timeoutMs: 4000, pollMs: 50 });
1316
+ const out = await handle.result({ timeoutMs: 4000 });
1728
1317
  expect(out).toEqual({ ok: true });
1729
1318
 
1730
1319
  const cnt1 = Number((await redis.get(countKey)) ?? "0");
@@ -1754,7 +1343,7 @@ test("lease+reaper: long running step is not duplicated", async () => {
1754
1343
  const queue = "q14";
1755
1344
  const countKey = `${prefix}:t:leaseCount`;
1756
1345
 
1757
- const wf = defineWorkflow({ name: "lease-wf", queue }, async ({ step }) => {
1346
+ const wf = defineWorkflow("lease-wf", {queue }, async ({ step }) => {
1758
1347
  await step.run({ name: "long" }, async () => {
1759
1348
  await redis.incr(countKey);
1760
1349
  await new Promise((r) => setTimeout(r, 700));
@@ -1772,7 +1361,7 @@ test("lease+reaper: long running step is not duplicated", async () => {
1772
1361
  });
1773
1362
 
1774
1363
  const handle = await wf.run({});
1775
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
1364
+ const out = await handle.result({ timeoutMs: 8000 });
1776
1365
  expect(out).toEqual({ ok: true });
1777
1366
 
1778
1367
  const cnt = Number((await redis.get(countKey)) ?? "0");
@@ -1796,11 +1385,8 @@ test(
1796
1385
  const queue = "q15";
1797
1386
  const counterKey = `${prefix}:t:cronLockCount`;
1798
1387
 
1799
- defineWorkflow(
1800
- {
1801
- name: "cron-lock-wf",
1802
- queue,
1803
- triggers: { cron: [{ expression: "*/1 * * * * *" }] },
1388
+ defineWorkflow("cron-lock-wf", {queue,
1389
+ cron: [{ expression: "*/1 * * * * *" }],
1804
1390
  },
1805
1391
  async ({ step }) => {
1806
1392
  await step.run({ name: "tick" }, async () => {
@@ -1839,10 +1425,7 @@ test(
1839
1425
  const childFailOnceKey = `${prefix}:t:childFailOnce`;
1840
1426
  const parentBeforeKey = `${prefix}:t:parentBefore`;
1841
1427
 
1842
- const child = defineWorkflow(
1843
- {
1844
- name: "child-wf",
1845
- queue: "q_child",
1428
+ const child = defineWorkflow("child-wf", {queue: "q_child",
1846
1429
  retries: { maxAttempts: 2 },
1847
1430
  },
1848
1431
  async ({ step, run }) => {
@@ -1864,10 +1447,7 @@ test(
1864
1447
  },
1865
1448
  );
1866
1449
 
1867
- const parent = defineWorkflow(
1868
- {
1869
- name: "parent-wf",
1870
- queue: "q_parent",
1450
+ const parent = defineWorkflow("parent-wf", {queue: "q_parent",
1871
1451
  },
1872
1452
  async ({ step }) => {
1873
1453
  await step.run({ name: "before" }, async () => {
@@ -1877,7 +1457,7 @@ test(
1877
1457
 
1878
1458
  const childOut = await step.run({ name: "child" }, async () => {
1879
1459
  const h = await child.run({});
1880
- return await h.result({ timeoutMs: 10_000, pollMs: 50 });
1460
+ return await h.result({ timeoutMs: 10_000 });
1881
1461
  });
1882
1462
 
1883
1463
  return { ok: true, child: childOut };
@@ -1893,7 +1473,7 @@ test(
1893
1473
  });
1894
1474
 
1895
1475
  const handle = await parent.run({});
1896
- const out = await handle.result({ timeoutMs: 15_000, pollMs: 50 });
1476
+ const out = await handle.result({ timeoutMs: 15_000 });
1897
1477
  expect(out.ok).toBe(true);
1898
1478
  expect(out.child.ok).toBe(true);
1899
1479
  expect(out.child.attempt).toBe(2);
@@ -1925,7 +1505,7 @@ test(
1925
1505
  setDefaultClient(client);
1926
1506
 
1927
1507
  const queue = "q_status";
1928
- const wf = defineWorkflow({ name: "status-wf", queue }, async ({ step }) => {
1508
+ const wf = defineWorkflow("status-wf", {queue }, async ({ step }) => {
1929
1509
  await step.run({ name: "sleep" }, async () => {
1930
1510
  await new Promise((r) => setTimeout(r, 350));
1931
1511
  return true;
@@ -1956,7 +1536,7 @@ test(
1956
1536
  const queuedMembers2 = await redis.zrange(queuedIndex, 0, -1);
1957
1537
  expect(queuedMembers2.includes(handle.id)).toBe(false);
1958
1538
 
1959
- const out = await handle.result({ timeoutMs: 8000, pollMs: 50 });
1539
+ const out = await handle.result({ timeoutMs: 8000 });
1960
1540
  expect(out).toEqual({ ok: true });
1961
1541
 
1962
1542
  const succeededMembers = await redis.zrange(`${prefix}:runs:status:succeeded`, 0, -1);
@@ -1971,7 +1551,7 @@ test(
1971
1551
  );
1972
1552
 
1973
1553
  test(
1974
- "production: registry sync updates triggers (events move, cron removed)",
1554
+ "production: registry sync updates cron definitions",
1975
1555
  async () => {
1976
1556
  const prefix = testPrefix();
1977
1557
  const redis = new RedisClient(redisServer.url);
@@ -1979,27 +1559,13 @@ test(
1979
1559
  setDefaultClient(client);
1980
1560
 
1981
1561
  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
1562
 
1997
- await client.syncRegistry(getDefaultRegistry());
1563
+ defineWorkflow(name, {
1564
+ queue: "q_update",
1565
+ cron: [{ expression: "*/1 * * * * *" }],
1566
+ }, async () => ({ ok: true }));
1998
1567
 
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);
1568
+ await client.syncRegistry(getDefaultRegistry());
2003
1569
 
2004
1570
  const workflowKey = `${prefix}:workflow:${name}`;
2005
1571
  const cronIdsJson = await redis.hget(workflowKey, "cronIdsJson");
@@ -2011,25 +1577,14 @@ test(
2011
1577
  const cronNext = await redis.zrange(`${prefix}:cron:next`, 0, -1);
2012
1578
  expect(cronNext.includes(cronId)).toBe(true);
2013
1579
 
2014
- // Simulate a new deployment: same workflow name, different triggers.
1580
+ // Simulate a new deployment: same workflow name, cron removed.
2015
1581
  __unstableResetDefaultRegistryForTests();
2016
- defineWorkflow(
2017
- {
2018
- name,
2019
- queue: "q_update",
2020
- triggers: { events: [eventB] },
2021
- },
2022
- async () => ({ ok: true }),
2023
- );
1582
+ defineWorkflow(name, {
1583
+ queue: "q_update",
1584
+ }, async () => ({ ok: true }));
2024
1585
 
2025
1586
  await client.syncRegistry(getDefaultRegistry());
2026
1587
 
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
1588
  expect(await redis.hget(`${prefix}:cron:def`, cronId)).toBeNull();
2034
1589
  const cronNext2 = await redis.zrange(`${prefix}:cron:next`, 0, -1);
2035
1590
  expect(cronNext2.includes(cronId)).toBe(false);
@@ -2051,10 +1606,7 @@ test(
2051
1606
  const t1Key = `${prefix}:t:attempt1`;
2052
1607
  const t2Key = `${prefix}:t:attempt2`;
2053
1608
 
2054
- const wf = defineWorkflow(
2055
- {
2056
- name: "backoff-wf",
2057
- queue,
1609
+ const wf = defineWorkflow("backoff-wf", {queue,
2058
1610
  retries: { maxAttempts: 2 },
2059
1611
  },
2060
1612
  async ({ run }) => {
@@ -2073,7 +1625,7 @@ test(
2073
1625
  });
2074
1626
 
2075
1627
  const handle = await wf.run({});
2076
- const out = await handle.result({ timeoutMs: 15_000, pollMs: 50 });
1628
+ const out = await handle.result({ timeoutMs: 15_000 });
2077
1629
  expect(out).toEqual({ ok: true });
2078
1630
 
2079
1631
  const t1 = Number((await redis.get(t1Key)) ?? "0");
@@ -2103,11 +1655,8 @@ test(
2103
1655
  const queue = "q_failover";
2104
1656
  const counterKey = `${prefix}:t:cronFailoverCount`;
2105
1657
 
2106
- defineWorkflow(
2107
- {
2108
- name: "cron-failover-wf",
2109
- queue,
2110
- triggers: { cron: [{ expression: "*/1 * * * * *" }] },
1658
+ defineWorkflow("cron-failover-wf", {queue,
1659
+ cron: [{ expression: "*/1 * * * * *" }],
2111
1660
  },
2112
1661
  async ({ step }) => {
2113
1662
  await step.run({ name: "tick" }, async () => {
@@ -2149,8 +1698,8 @@ test("listRuns: status + workflow filters are applied together", async () => {
2149
1698
 
2150
1699
  const queue = "q_filters";
2151
1700
 
2152
- const succeeds = defineWorkflow({ name: "filters-success", queue }, async () => ({ ok: true }));
2153
- const fails = defineWorkflow({ name: "filters-fail", queue }, async () => {
1701
+ const succeeds = defineWorkflow("filters-success", {queue }, async () => ({ ok: true }));
1702
+ const fails = defineWorkflow("filters-fail", {queue }, async () => {
2154
1703
  throw new Error("expected failure");
2155
1704
  });
2156
1705
 
@@ -2159,7 +1708,7 @@ test("listRuns: status + workflow filters are applied together", async () => {
2159
1708
  const successHandle = await succeeds.run({});
2160
1709
  const failHandle = await fails.run({});
2161
1710
 
2162
- await successHandle.result({ timeoutMs: 4000, pollMs: 50 });
1711
+ await successHandle.result({ timeoutMs: 4000 });
2163
1712
  await waitFor(
2164
1713
  async () => (await client.getRun(failHandle.id))?.status === "failed",
2165
1714
  { timeoutMs: 6000, label: "failed run" },
@@ -2193,20 +1742,14 @@ test("cron trigger ids: same custom id in two workflows does not collide", async
2193
1742
  const redis = new RedisClient(redisServer.url);
2194
1743
  const client = createClient({ redis, prefix });
2195
1744
 
2196
- defineWorkflow(
2197
- {
2198
- name: "cron-id-a",
2199
- queue: "qa",
2200
- triggers: { cron: [{ id: "shared", expression: "*/5 * * * * *" }] },
1745
+ defineWorkflow("cron-id-a", {queue: "qa",
1746
+ cron: [{ id: "shared", expression: "*/5 * * * * *" }],
2201
1747
  },
2202
1748
  async () => ({ ok: true }),
2203
1749
  );
2204
1750
 
2205
- defineWorkflow(
2206
- {
2207
- name: "cron-id-b",
2208
- queue: "qb",
2209
- triggers: { cron: [{ id: "shared", expression: "*/5 * * * * *" }] },
1751
+ defineWorkflow("cron-id-b", {queue: "qb",
1752
+ cron: [{ id: "shared", expression: "*/5 * * * * *" }],
2210
1753
  },
2211
1754
  async () => ({ ok: true }),
2212
1755
  );
@@ -2233,11 +1776,8 @@ test("syncRegistry: cron trigger with explicit null input preserves null payload
2233
1776
 
2234
1777
  const workflowName = "cron-null-input";
2235
1778
 
2236
- defineWorkflow(
2237
- {
2238
- name: workflowName,
2239
- queue: "q-cron-null",
2240
- triggers: { cron: [{ id: "null-input", expression: "*/5 * * * * *", input: null }] },
1779
+ defineWorkflow(workflowName, {queue: "q-cron-null",
1780
+ cron: [{ id: "null-input", expression: "*/5 * * * * *", input: null }],
2241
1781
  },
2242
1782
  async () => ({ ok: true }),
2243
1783
  );
@@ -2267,11 +1807,8 @@ test("syncRegistry: invalid cron expression removes existing next schedule for s
2267
1807
  const workflowKey = `${prefix}:workflow:${workflowName}`;
2268
1808
  const cronNextKey = `${prefix}:cron:next`;
2269
1809
 
2270
- defineWorkflow(
2271
- {
2272
- name: workflowName,
2273
- queue: "q-cron-invalid",
2274
- triggers: { cron: [{ id: "stable", expression: "*/5 * * * * *" }] },
1810
+ defineWorkflow(workflowName, {queue: "q-cron-invalid",
1811
+ cron: [{ id: "stable", expression: "*/5 * * * * *" }],
2275
1812
  },
2276
1813
  async () => ({ ok: true }),
2277
1814
  );
@@ -2286,11 +1823,8 @@ test("syncRegistry: invalid cron expression removes existing next schedule for s
2286
1823
  expect(before.includes(cronId)).toBe(true);
2287
1824
 
2288
1825
  __unstableResetDefaultRegistryForTests();
2289
- defineWorkflow(
2290
- {
2291
- name: workflowName,
2292
- queue: "q-cron-invalid",
2293
- triggers: { cron: [{ id: "stable", expression: "not a valid cron" }] },
1826
+ defineWorkflow(workflowName, {queue: "q-cron-invalid",
1827
+ cron: [{ id: "stable", expression: "not a valid cron" }],
2294
1828
  },
2295
1829
  async () => ({ ok: true }),
2296
1830
  );
@@ -2303,19 +1837,15 @@ test("syncRegistry: invalid cron expression removes existing next schedule for s
2303
1837
  redis.close();
2304
1838
  });
2305
1839
 
2306
- test("syncRegistry: removes stale workflow metadata (events + cron)", async () => {
1840
+ test("syncRegistry: removes stale workflow metadata (cron)", async () => {
2307
1841
  const prefix = testPrefix();
2308
1842
  const redis = new RedisClient(redisServer.url);
2309
1843
  const client = createClient({ redis, prefix });
2310
1844
 
2311
1845
  const workflowName = "stale-workflow";
2312
- const eventName = "stale.event";
2313
1846
 
2314
- defineWorkflow(
2315
- {
2316
- name: workflowName,
2317
- queue: "q-stale",
2318
- triggers: { events: [eventName], cron: [{ id: "stale", expression: "*/10 * * * * *" }] },
1847
+ defineWorkflow(workflowName, {queue: "q-stale",
1848
+ cron: [{ id: "stale", expression: "*/10 * * * * *" }],
2319
1849
  },
2320
1850
  async () => ({ ok: true }),
2321
1851
  );
@@ -2327,7 +1857,6 @@ test("syncRegistry: removes stale workflow metadata (events + cron)", async () =
2327
1857
  expect(cronIds.length).toBe(1);
2328
1858
  const cronId = cronIds[0]!;
2329
1859
 
2330
- expect((await redis.smembers(`${prefix}:event:${eventName}:workflows`)).includes(workflowName)).toBe(true);
2331
1860
  expect(await redis.hget(`${prefix}:cron:def`, cronId)).not.toBeNull();
2332
1861
 
2333
1862
  // Force stale age beyond grace period and sync an empty registry.
@@ -2336,7 +1865,6 @@ test("syncRegistry: removes stale workflow metadata (events + cron)", async () =
2336
1865
  await client.syncRegistry(getDefaultRegistry());
2337
1866
 
2338
1867
  expect((await redis.smembers(`${prefix}:workflows`)).includes(workflowName)).toBe(false);
2339
- expect((await redis.smembers(`${prefix}:event:${eventName}:workflows`)).includes(workflowName)).toBe(false);
2340
1868
  expect(await redis.hget(workflowKey, "queue")).toBeNull();
2341
1869
  expect(await redis.hget(`${prefix}:cron:def`, cronId)).toBeNull();
2342
1870
  const cronNext = await redis.zrange(`${prefix}:cron:next`, 0, -1);
@@ -2351,22 +1879,16 @@ test("syncRegistry: stale cleanup is isolated by owner", async () => {
2351
1879
  const client = createClient({ redis, prefix });
2352
1880
 
2353
1881
  __unstableResetDefaultRegistryForTests();
2354
- defineWorkflow(
2355
- {
2356
- name: "owner-a-wf",
2357
- queue: "qa",
2358
- triggers: { events: ["owner.a"], cron: [{ id: "a", expression: "*/30 * * * * *" }] },
1882
+ defineWorkflow("owner-a-wf", {queue: "qa",
1883
+ cron: [{ id: "a", expression: "*/30 * * * * *" }],
2359
1884
  },
2360
1885
  async () => ({ ok: true }),
2361
1886
  );
2362
1887
  await client.syncRegistry(getDefaultRegistry(), { owner: "svc-a" });
2363
1888
 
2364
1889
  __unstableResetDefaultRegistryForTests();
2365
- defineWorkflow(
2366
- {
2367
- name: "owner-b-wf",
2368
- queue: "qb",
2369
- triggers: { events: ["owner.b"], cron: [{ id: "b", expression: "*/30 * * * * *" }] },
1890
+ defineWorkflow("owner-b-wf", {queue: "qb",
1891
+ cron: [{ id: "b", expression: "*/30 * * * * *" }],
2370
1892
  },
2371
1893
  async () => ({ ok: true }),
2372
1894
  );
@@ -2392,7 +1914,7 @@ test("cancelRun: terminal runs are unchanged", async () => {
2392
1914
  const client = createClient({ redis, prefix });
2393
1915
  setDefaultClient(client);
2394
1916
 
2395
- const wf = defineWorkflow({ name: "cancel-terminal", queue: "q_cancel_terminal" }, async () => ({ ok: true }));
1917
+ const wf = defineWorkflow("cancel-terminal", {queue: "q_cancel_terminal" }, async () => ({ ok: true }));
2396
1918
 
2397
1919
  const worker = await startWorker({
2398
1920
  redis,
@@ -2402,7 +1924,7 @@ test("cancelRun: terminal runs are unchanged", async () => {
2402
1924
  });
2403
1925
 
2404
1926
  const handle = await wf.run({});
2405
- await handle.result({ timeoutMs: 4000, pollMs: 50 });
1927
+ await handle.result({ timeoutMs: 4000 });
2406
1928
 
2407
1929
  const before = await client.getRun(handle.id);
2408
1930
  const canceled = await client.cancelRun(handle.id, { reason: "late" });
@@ -2423,10 +1945,7 @@ test("output serialization: non-JSON output fails once and is not stuck", async
2423
1945
  const client = createClient({ redis, prefix });
2424
1946
  setDefaultClient(client);
2425
1947
 
2426
- const wf = defineWorkflow(
2427
- {
2428
- name: "serialization-fail",
2429
- queue: "q_serial",
1948
+ const wf = defineWorkflow("serialization-fail", {queue: "q_serial",
2430
1949
  retries: { maxAttempts: 3 },
2431
1950
  },
2432
1951
  async () => ({ bad: 1n }),
@@ -2467,10 +1986,7 @@ test("retry step sequence: new steps keep monotonic order after cached steps", a
2467
1986
 
2468
1987
  const failOnceKey = `${prefix}:t:seq-fail-once`;
2469
1988
 
2470
- const wf = defineWorkflow(
2471
- {
2472
- name: "seq-retry",
2473
- queue: "q_seq",
1989
+ const wf = defineWorkflow("seq-retry", {queue: "q_seq",
2474
1990
  retries: { maxAttempts: 2 },
2475
1991
  },
2476
1992
  async ({ step }) => {
@@ -2498,7 +2014,7 @@ test("retry step sequence: new steps keep monotonic order after cached steps", a
2498
2014
  });
2499
2015
 
2500
2016
  const handle = await wf.run({});
2501
- await handle.result({ timeoutMs: 12_000, pollMs: 50 });
2017
+ await handle.result({ timeoutMs: 12_000 });
2502
2018
 
2503
2019
  const rawSteps = await redis.hgetall(`${prefix}:run:${handle.id}:steps`);
2504
2020
  const seq1 = safeJsonParse<any>(rawSteps.step1 ?? null).seq;
@@ -2520,7 +2036,7 @@ test("listRuns: negative offset is clamped to zero", async () => {
2520
2036
  const client = createClient({ redis, prefix });
2521
2037
  setDefaultClient(client);
2522
2038
 
2523
- const wf = defineWorkflow({ name: "offset-wf", queue: "q_offset" }, async () => ({ ok: true }));
2039
+ const wf = defineWorkflow("offset-wf", {queue: "q_offset" }, async () => ({ ok: true }));
2524
2040
  const worker = await startWorker({
2525
2041
  redis,
2526
2042
  prefix,
@@ -2529,13 +2045,13 @@ test("listRuns: negative offset is clamped to zero", async () => {
2529
2045
  });
2530
2046
 
2531
2047
  const h1 = await wf.run({});
2532
- await h1.result({ timeoutMs: 4000, pollMs: 50 });
2048
+ await h1.result({ timeoutMs: 4000 });
2533
2049
  await new Promise((r) => setTimeout(r, 5));
2534
2050
  const h2 = await wf.run({});
2535
- await h2.result({ timeoutMs: 4000, pollMs: 50 });
2051
+ await h2.result({ timeoutMs: 4000 });
2536
2052
  await new Promise((r) => setTimeout(r, 5));
2537
2053
  const h3 = await wf.run({});
2538
- await h3.result({ timeoutMs: 4000, pollMs: 50 });
2054
+ await h3.result({ timeoutMs: 4000 });
2539
2055
 
2540
2056
  const fromZero = await client.listRuns({ workflow: "offset-wf", limit: 1, offset: 0 });
2541
2057
  const fromNegative = await client.listRuns({ workflow: "offset-wf", limit: 1, offset: -1 });
@@ -2577,10 +2093,7 @@ test("error serialization: non-serializable thrown values do not wedge a run", a
2577
2093
  const client = createClient({ redis, prefix });
2578
2094
  setDefaultClient(client);
2579
2095
 
2580
- const wf = defineWorkflow(
2581
- {
2582
- name: "throw-bigint",
2583
- queue: "q_bigint",
2096
+ const wf = defineWorkflow("throw-bigint", {queue: "q_bigint",
2584
2097
  retries: { maxAttempts: 2 },
2585
2098
  },
2586
2099
  async () => {
@@ -2619,20 +2132,14 @@ test("cron trigger ids: delimiter-like custom ids do not collide", async () => {
2619
2132
  const redis = new RedisClient(redisServer.url);
2620
2133
  const client = createClient({ redis, prefix });
2621
2134
 
2622
- defineWorkflow(
2623
- {
2624
- name: "wf:a",
2625
- queue: "qa",
2626
- triggers: { cron: [{ id: "b:c", expression: "*/5 * * * * *" }] },
2135
+ defineWorkflow("wf:a", {queue: "qa",
2136
+ cron: [{ id: "b:c", expression: "*/5 * * * * *" }],
2627
2137
  },
2628
2138
  async () => ({ ok: true }),
2629
2139
  );
2630
2140
 
2631
- defineWorkflow(
2632
- {
2633
- name: "wf:a:b",
2634
- queue: "qb",
2635
- triggers: { cron: [{ id: "c", expression: "*/5 * * * * *" }] },
2141
+ defineWorkflow("wf:a:b", {queue: "qb",
2142
+ cron: [{ id: "c", expression: "*/5 * * * * *" }],
2636
2143
  },
2637
2144
  async () => ({ ok: true }),
2638
2145
  );
@@ -2669,7 +2176,7 @@ test("idempotencyKey: stale pointer is recovered instead of returning missing ru
2669
2176
  const workflowName = "idem-recover";
2670
2177
  const idem = "stale-key";
2671
2178
 
2672
- const wf = defineWorkflow({ name: workflowName, queue }, async () => ({ ok: true }));
2179
+ const wf = defineWorkflow(workflowName, {queue }, async () => ({ ok: true }));
2673
2180
 
2674
2181
  const worker = await startWorker({
2675
2182
  redis,
@@ -2685,7 +2192,7 @@ test("idempotencyKey: stale pointer is recovered instead of returning missing ru
2685
2192
  const handle = await wf.run({}, { idempotencyKey: idem });
2686
2193
  expect(handle.id).not.toBe(staleRunId);
2687
2194
 
2688
- const out = await handle.result({ timeoutMs: 5000, pollMs: 50 });
2195
+ const out = await handle.result({ timeoutMs: 5000 });
2689
2196
  expect(out).toEqual({ ok: true });
2690
2197
 
2691
2198
  const resolved = await redis.get(idempotencyRedisKey);
@@ -2706,7 +2213,7 @@ test("idempotencyKey: partial existing run is repaired and executed", async () =
2706
2213
  const idem = "partial-key";
2707
2214
  const countKey = `${prefix}:t:idem-partial-count`;
2708
2215
 
2709
- const wf = defineWorkflow({ name: workflowName, queue }, async ({ step }) => {
2216
+ const wf = defineWorkflow(workflowName, {queue }, async ({ step }) => {
2710
2217
  await step.run({ name: "do" }, async () => {
2711
2218
  await redis.incr(countKey);
2712
2219
  return true;
@@ -2742,7 +2249,7 @@ test("idempotencyKey: partial existing run is repaired and executed", async () =
2742
2249
  const handle = await wf.run({}, { idempotencyKey: idem });
2743
2250
  expect(handle.id).toBe(runId);
2744
2251
 
2745
- const out = await handle.result({ timeoutMs: 6000, pollMs: 50 });
2252
+ const out = await handle.result({ timeoutMs: 6000 });
2746
2253
  expect(out).toEqual({ ok: true });
2747
2254
 
2748
2255
  const count = Number((await redis.get(countKey)) ?? "0");
@@ -2757,16 +2264,11 @@ test("syncRegistry: duplicate custom cron ids in one workflow are rejected", asy
2757
2264
  const redis = new RedisClient(redisServer.url);
2758
2265
  const client = createClient({ redis, prefix });
2759
2266
 
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
- },
2267
+ defineWorkflow("dup-custom-cron-id", {queue: "q_dup_cron_id",
2268
+ cron: [
2269
+ { id: "same", expression: "*/5 * * * * *", input: { n: 1 } },
2270
+ { id: "same", expression: "*/7 * * * * *", input: { n: 2 } },
2271
+ ],
2770
2272
  },
2771
2273
  async () => ({ ok: true }),
2772
2274
  );
@@ -2783,7 +2285,7 @@ test("scheduled promoter: stale scheduled entry does not resurrect terminal run"
2783
2285
  setDefaultClient(client);
2784
2286
 
2785
2287
  const queue = "q_sched_guard";
2786
- const wf = defineWorkflow({ name: "sched-guard", queue }, async ({ step }) => {
2288
+ const wf = defineWorkflow("sched-guard", {queue }, async ({ step }) => {
2787
2289
  await step.run({ name: "once" }, async () => true);
2788
2290
  return { ok: true };
2789
2291
  });
@@ -2796,7 +2298,7 @@ test("scheduled promoter: stale scheduled entry does not resurrect terminal run"
2796
2298
  });
2797
2299
 
2798
2300
  const handle = await wf.run({});
2799
- const out = await handle.result({ timeoutMs: 5000, pollMs: 50 });
2301
+ const out = await handle.result({ timeoutMs: 5000 });
2800
2302
  expect(out).toEqual({ ok: true });
2801
2303
 
2802
2304
  const firstState = await client.getRun(handle.id);
@@ -2824,11 +2326,8 @@ test(
2824
2326
  const workflowName = "cron-retry";
2825
2327
  const counterKey = `${prefix}:t:cron-retry-count`;
2826
2328
 
2827
- defineWorkflow(
2828
- {
2829
- name: workflowName,
2830
- queue,
2831
- triggers: { cron: [{ expression: "*/1 * * * * *" }] },
2329
+ defineWorkflow(workflowName, {queue,
2330
+ cron: [{ expression: "*/1 * * * * *" }],
2832
2331
  },
2833
2332
  async ({ step }) => {
2834
2333
  await step.run({ name: "tick" }, async () => {
@@ -2893,11 +2392,8 @@ test(
2893
2392
  const counterKey = `${prefix}:t:cronStaleCount`;
2894
2393
  const cronLockKey = keys.lockCron(prefix);
2895
2394
 
2896
- defineWorkflow(
2897
- {
2898
- name: workflowName,
2899
- queue,
2900
- triggers: { cron: [{ expression: "*/1 * * * * *" }] },
2395
+ defineWorkflow(workflowName, {queue,
2396
+ cron: [{ expression: "*/1 * * * * *" }],
2901
2397
  },
2902
2398
  async ({ step }) => {
2903
2399
  await step.run({ name: "tick" }, async () => {
@@ -2950,8 +2446,8 @@ test("workflow names ending with ':runs' do not collide with workflow run indexe
2950
2446
  setDefaultClient(client);
2951
2447
 
2952
2448
  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" }));
2449
+ const base = defineWorkflow("keyspace-base", {queue }, async () => ({ ok: "base" }));
2450
+ defineWorkflow("keyspace-base:runs", {queue }, async () => ({ ok: "suffix" }));
2955
2451
 
2956
2452
  expect(keys.workflow(prefix, "keyspace-base:runs")).not.toBe(keys.workflowRuns(prefix, "keyspace-base"));
2957
2453
 
@@ -2963,7 +2459,7 @@ test("workflow names ending with ':runs' do not collide with workflow run indexe
2963
2459
  });
2964
2460
 
2965
2461
  const handle = await base.run({});
2966
- const out = await handle.result({ timeoutMs: 5000, pollMs: 50 });
2462
+ const out = await handle.result({ timeoutMs: 5000 });
2967
2463
  expect(out).toEqual({ ok: "base" });
2968
2464
 
2969
2465
  const suffixMeta = await client.getWorkflowMeta("keyspace-base:runs");
@@ -2980,7 +2476,7 @@ test("cancelRun race: near-finish cancellation settles as canceled", async () =>
2980
2476
  setDefaultClient(client);
2981
2477
 
2982
2478
  const queue = "q_cancel_late";
2983
- const wf = defineWorkflow({ name: "cancel-late", queue }, async ({ step }) => {
2479
+ const wf = defineWorkflow("cancel-late", {queue }, async ({ step }) => {
2984
2480
  await step.run({ name: "short" }, async () => {
2985
2481
  await new Promise((r) => setTimeout(r, 100));
2986
2482
  return true;
@@ -3025,7 +2521,7 @@ test("cancelRun race: near-finish cancellation settles as canceled", async () =>
3025
2521
  const canceled = await cancelPromise;
3026
2522
  expect(canceled).toBe(true);
3027
2523
 
3028
- await expect(handle.result({ timeoutMs: 8000, pollMs: 50 })).rejects.toBeInstanceOf(CanceledError);
2524
+ await expect(handle.result({ timeoutMs: 8000 })).rejects.toBeInstanceOf(CanceledError);
3029
2525
 
3030
2526
  const state = await client.getRun(handle.id);
3031
2527
  expect(state?.status).toBe("canceled");
@@ -3044,7 +2540,7 @@ test("cancelRun race: cancellation requested during finalize wins over success",
3044
2540
  setDefaultClient(client);
3045
2541
 
3046
2542
  const queue = "q_cancel_finalize_race";
3047
- const wf = defineWorkflow({ name: "cancel-finalize-race", queue }, async () => {
2543
+ const wf = defineWorkflow("cancel-finalize-race", {queue }, async () => {
3048
2544
  return { ok: true };
3049
2545
  });
3050
2546
 
@@ -3089,7 +2585,7 @@ test("cancelRun race: cancellation requested during finalize wins over success",
3089
2585
  { timeoutMs: 6000, label: "run canceled in finalize race" },
3090
2586
  );
3091
2587
 
3092
- await expect(handle.result({ timeoutMs: 1000, pollMs: 50 })).rejects.toBeInstanceOf(CanceledError);
2588
+ await expect(handle.result({ timeoutMs: 1000 })).rejects.toBeInstanceOf(CanceledError);
3093
2589
 
3094
2590
  const state = await client.getRun(handle.id);
3095
2591
  expect(state?.status).toBe("canceled");
@@ -3109,7 +2605,7 @@ test(
3109
2605
  setDefaultClient(client);
3110
2606
 
3111
2607
  const queue = "q_stop_abort";
3112
- const wf = defineWorkflow({ name: "stop-abort", queue }, async () => {
2608
+ const wf = defineWorkflow("stop-abort", {queue }, async () => {
3113
2609
  await new Promise<never>(() => {});
3114
2610
  });
3115
2611
 
@@ -3151,4 +2647,4 @@ test(
3151
2647
  }
3152
2648
  },
3153
2649
  { timeout: 20_000 },
3154
- );
2650
+ );