@sonamu-kit/tasks 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/.oxlintrc.json +3 -0
  2. package/AGENTS.md +21 -0
  3. package/dist/backend.d.ts +126 -107
  4. package/dist/backend.d.ts.map +1 -1
  5. package/dist/backend.js +4 -1
  6. package/dist/backend.js.map +1 -1
  7. package/dist/client.d.ts +145 -132
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +219 -213
  10. package/dist/client.js.map +1 -1
  11. package/dist/config.d.ts +15 -8
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +22 -17
  14. package/dist/config.js.map +1 -1
  15. package/dist/core/duration.d.ts +5 -4
  16. package/dist/core/duration.d.ts.map +1 -1
  17. package/dist/core/duration.js +54 -59
  18. package/dist/core/duration.js.map +1 -1
  19. package/dist/core/error.d.ts +10 -7
  20. package/dist/core/error.d.ts.map +1 -1
  21. package/dist/core/error.js +21 -21
  22. package/dist/core/error.js.map +1 -1
  23. package/dist/core/json.d.ts +8 -3
  24. package/dist/core/json.d.ts.map +1 -1
  25. package/dist/core/result.d.ts +10 -14
  26. package/dist/core/result.d.ts.map +1 -1
  27. package/dist/core/result.js +21 -16
  28. package/dist/core/result.js.map +1 -1
  29. package/dist/core/retry.d.ts +37 -31
  30. package/dist/core/retry.d.ts.map +1 -1
  31. package/dist/core/retry.js +44 -51
  32. package/dist/core/retry.js.map +1 -1
  33. package/dist/core/schema.d.ts +57 -53
  34. package/dist/core/schema.d.ts.map +1 -1
  35. package/dist/core/step.d.ts +28 -78
  36. package/dist/core/step.d.ts.map +1 -1
  37. package/dist/core/step.js +53 -63
  38. package/dist/core/step.js.map +1 -1
  39. package/dist/core/workflow.d.ts +33 -61
  40. package/dist/core/workflow.d.ts.map +1 -1
  41. package/dist/core/workflow.js +31 -41
  42. package/dist/core/workflow.js.map +1 -1
  43. package/dist/database/backend.d.ts +53 -46
  44. package/dist/database/backend.d.ts.map +1 -1
  45. package/dist/database/backend.js +544 -577
  46. package/dist/database/backend.js.map +1 -1
  47. package/dist/database/base.js +48 -25
  48. package/dist/database/base.js.map +1 -1
  49. package/dist/database/migrations/20251212000000_0_init.d.ts +10 -0
  50. package/dist/database/migrations/20251212000000_0_init.d.ts.map +1 -0
  51. package/dist/database/migrations/20251212000000_0_init.js +8 -4
  52. package/dist/database/migrations/20251212000000_0_init.js.map +1 -1
  53. package/dist/database/migrations/20251212000000_1_tables.d.ts +10 -0
  54. package/dist/database/migrations/20251212000000_1_tables.d.ts.map +1 -0
  55. package/dist/database/migrations/20251212000000_1_tables.js +81 -83
  56. package/dist/database/migrations/20251212000000_1_tables.js.map +1 -1
  57. package/dist/database/migrations/20251212000000_2_fk.d.ts +10 -0
  58. package/dist/database/migrations/20251212000000_2_fk.d.ts.map +1 -0
  59. package/dist/database/migrations/20251212000000_2_fk.js +20 -43
  60. package/dist/database/migrations/20251212000000_2_fk.js.map +1 -1
  61. package/dist/database/migrations/20251212000000_3_indexes.d.ts +10 -0
  62. package/dist/database/migrations/20251212000000_3_indexes.d.ts.map +1 -0
  63. package/dist/database/migrations/20251212000000_3_indexes.js +88 -102
  64. package/dist/database/migrations/20251212000000_3_indexes.js.map +1 -1
  65. package/dist/database/pubsub.d.ts +7 -16
  66. package/dist/database/pubsub.d.ts.map +1 -1
  67. package/dist/database/pubsub.js +75 -73
  68. package/dist/database/pubsub.js.map +1 -1
  69. package/dist/execution.d.ts +20 -59
  70. package/dist/execution.d.ts.map +1 -1
  71. package/dist/execution.js +175 -188
  72. package/dist/execution.js.map +1 -1
  73. package/dist/index.d.ts +5 -8
  74. package/dist/index.js +5 -5
  75. package/dist/internal.d.ts +12 -13
  76. package/dist/internal.js +4 -4
  77. package/dist/registry.d.ts +33 -27
  78. package/dist/registry.d.ts.map +1 -1
  79. package/dist/registry.js +58 -49
  80. package/dist/registry.js.map +1 -1
  81. package/dist/worker.d.ts +57 -50
  82. package/dist/worker.d.ts.map +1 -1
  83. package/dist/worker.js +194 -199
  84. package/dist/worker.js.map +1 -1
  85. package/dist/workflow.d.ts +26 -30
  86. package/dist/workflow.d.ts.map +1 -1
  87. package/dist/workflow.js +20 -15
  88. package/dist/workflow.js.map +1 -1
  89. package/nodemon.json +1 -1
  90. package/package.json +17 -19
  91. package/src/backend.ts +25 -9
  92. package/src/chaos.test.ts +3 -1
  93. package/src/client.test.ts +2 -0
  94. package/src/client.ts +30 -8
  95. package/src/config.test.ts +1 -0
  96. package/src/config.ts +3 -2
  97. package/src/core/duration.test.ts +2 -1
  98. package/src/core/duration.ts +1 -1
  99. package/src/core/error.test.ts +1 -0
  100. package/src/core/error.ts +1 -1
  101. package/src/core/result.test.ts +1 -0
  102. package/src/core/retry.test.ts +3 -2
  103. package/src/core/retry.ts +1 -1
  104. package/src/core/schema.ts +2 -2
  105. package/src/core/step.test.ts +2 -1
  106. package/src/core/step.ts +4 -3
  107. package/src/core/workflow.test.ts +2 -1
  108. package/src/core/workflow.ts +4 -3
  109. package/src/database/backend.test.ts +1 -0
  110. package/src/database/backend.testsuite.ts +44 -40
  111. package/src/database/backend.ts +207 -25
  112. package/src/database/base.test.ts +41 -0
  113. package/src/database/base.ts +51 -2
  114. package/src/database/migrations/20251212000000_0_init.ts +2 -1
  115. package/src/database/migrations/20251212000000_1_tables.ts +2 -1
  116. package/src/database/migrations/20251212000000_2_fk.ts +2 -1
  117. package/src/database/migrations/20251212000000_3_indexes.ts +2 -1
  118. package/src/database/pubsub.test.ts +6 -3
  119. package/src/database/pubsub.ts +55 -33
  120. package/src/execution.test.ts +2 -0
  121. package/src/execution.ts +49 -10
  122. package/src/internal.ts +15 -15
  123. package/src/practices/01-remote-workflow.ts +1 -0
  124. package/src/registry.test.ts +1 -0
  125. package/src/registry.ts +1 -1
  126. package/src/testing/connection.ts +3 -1
  127. package/src/worker.test.ts +2 -0
  128. package/src/worker.ts +30 -9
  129. package/src/workflow.test.ts +1 -0
  130. package/src/workflow.ts +3 -3
  131. package/templates/openworkflow.config.ts +2 -1
  132. package/tsdown.config.ts +31 -0
  133. package/.swcrc +0 -17
  134. package/dist/chaos.test.d.ts +0 -2
  135. package/dist/chaos.test.d.ts.map +0 -1
  136. package/dist/chaos.test.js +0 -92
  137. package/dist/chaos.test.js.map +0 -1
  138. package/dist/client.test.d.ts +0 -2
  139. package/dist/client.test.d.ts.map +0 -1
  140. package/dist/client.test.js +0 -340
  141. package/dist/client.test.js.map +0 -1
  142. package/dist/config.test.d.ts +0 -2
  143. package/dist/config.test.d.ts.map +0 -1
  144. package/dist/config.test.js +0 -24
  145. package/dist/config.test.js.map +0 -1
  146. package/dist/core/duration.test.d.ts +0 -2
  147. package/dist/core/duration.test.d.ts.map +0 -1
  148. package/dist/core/duration.test.js +0 -265
  149. package/dist/core/duration.test.js.map +0 -1
  150. package/dist/core/error.test.d.ts +0 -2
  151. package/dist/core/error.test.d.ts.map +0 -1
  152. package/dist/core/error.test.js +0 -63
  153. package/dist/core/error.test.js.map +0 -1
  154. package/dist/core/json.js +0 -3
  155. package/dist/core/json.js.map +0 -1
  156. package/dist/core/result.test.d.ts +0 -2
  157. package/dist/core/result.test.d.ts.map +0 -1
  158. package/dist/core/result.test.js +0 -19
  159. package/dist/core/result.test.js.map +0 -1
  160. package/dist/core/retry.test.d.ts +0 -2
  161. package/dist/core/retry.test.d.ts.map +0 -1
  162. package/dist/core/retry.test.js +0 -198
  163. package/dist/core/retry.test.js.map +0 -1
  164. package/dist/core/schema.js +0 -4
  165. package/dist/core/schema.js.map +0 -1
  166. package/dist/core/step.test.d.ts +0 -2
  167. package/dist/core/step.test.d.ts.map +0 -1
  168. package/dist/core/step.test.js +0 -356
  169. package/dist/core/step.test.js.map +0 -1
  170. package/dist/core/workflow.test.d.ts +0 -2
  171. package/dist/core/workflow.test.d.ts.map +0 -1
  172. package/dist/core/workflow.test.js +0 -172
  173. package/dist/core/workflow.test.js.map +0 -1
  174. package/dist/database/backend.test.d.ts +0 -2
  175. package/dist/database/backend.test.d.ts.map +0 -1
  176. package/dist/database/backend.test.js +0 -19
  177. package/dist/database/backend.test.js.map +0 -1
  178. package/dist/database/backend.testsuite.d.ts +0 -20
  179. package/dist/database/backend.testsuite.d.ts.map +0 -1
  180. package/dist/database/backend.testsuite.js +0 -1280
  181. package/dist/database/backend.testsuite.js.map +0 -1
  182. package/dist/database/base.d.ts +0 -12
  183. package/dist/database/base.d.ts.map +0 -1
  184. package/dist/database/pubsub.test.d.ts +0 -2
  185. package/dist/database/pubsub.test.d.ts.map +0 -1
  186. package/dist/database/pubsub.test.js +0 -86
  187. package/dist/database/pubsub.test.js.map +0 -1
  188. package/dist/execution.test.d.ts +0 -2
  189. package/dist/execution.test.d.ts.map +0 -1
  190. package/dist/execution.test.js +0 -662
  191. package/dist/execution.test.js.map +0 -1
  192. package/dist/index.d.ts.map +0 -1
  193. package/dist/index.js.map +0 -1
  194. package/dist/internal.d.ts.map +0 -1
  195. package/dist/internal.js.map +0 -1
  196. package/dist/practices/01-remote-workflow.d.ts +0 -2
  197. package/dist/practices/01-remote-workflow.d.ts.map +0 -1
  198. package/dist/practices/01-remote-workflow.js +0 -70
  199. package/dist/practices/01-remote-workflow.js.map +0 -1
  200. package/dist/registry.test.d.ts +0 -2
  201. package/dist/registry.test.d.ts.map +0 -1
  202. package/dist/registry.test.js +0 -95
  203. package/dist/registry.test.js.map +0 -1
  204. package/dist/testing/connection.d.ts +0 -7
  205. package/dist/testing/connection.d.ts.map +0 -1
  206. package/dist/testing/connection.js +0 -39
  207. package/dist/testing/connection.js.map +0 -1
  208. package/dist/worker.test.d.ts +0 -2
  209. package/dist/worker.test.d.ts.map +0 -1
  210. package/dist/worker.test.js +0 -1164
  211. package/dist/worker.test.js.map +0 -1
  212. package/dist/workflow.test.d.ts +0 -2
  213. package/dist/workflow.test.d.ts.map +0 -1
  214. package/dist/workflow.test.js +0 -73
  215. package/dist/workflow.test.js.map +0 -1
@@ -1,1164 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { afterEach, beforeEach, describe, expect, test } from "vitest";
3
- import { declareWorkflow, OpenWorkflow } from "./client.js";
4
- import { BackendPostgres } from "./database/backend.js";
5
- import { KNEX_GLOBAL_CONFIG } from "./testing/connection.js";
6
- describe("Worker", ()=>{
7
- let backend;
8
- beforeEach(async ()=>{
9
- backend = new BackendPostgres(KNEX_GLOBAL_CONFIG, {
10
- namespaceId: randomUUID(),
11
- runMigrations: false
12
- });
13
- await backend.initialize();
14
- });
15
- afterEach(async ()=>{
16
- await backend.stop();
17
- });
18
- test("passes workflow input to handlers (known slow test)", async ()=>{
19
- const client = new OpenWorkflow({
20
- backend
21
- });
22
- const workflow = client.defineWorkflow({
23
- name: "context"
24
- }, ({ input })=>input);
25
- const worker = client.newWorker();
26
- const payload = {
27
- value: 10
28
- };
29
- const handle = await workflow.run(payload);
30
- await worker.tick();
31
- const result = await handle.result();
32
- expect(result).toEqual(payload);
33
- });
34
- test("processes workflow runs to completion (known slow test)", async ()=>{
35
- const client = new OpenWorkflow({
36
- backend
37
- });
38
- const workflow = client.defineWorkflow({
39
- name: "process"
40
- }, ({ input })=>input.value * 2);
41
- const worker = client.newWorker();
42
- const handle = await workflow.run({
43
- value: 21
44
- });
45
- await worker.tick();
46
- const result = await handle.result();
47
- expect(result).toBe(42);
48
- });
49
- test("step.run reuses cached results (known slow test)", async ()=>{
50
- const client = new OpenWorkflow({
51
- backend
52
- });
53
- let executionCount = 0;
54
- const workflow = client.defineWorkflow({
55
- name: "cached-step"
56
- }, async ({ step })=>{
57
- const first = await step.run({
58
- name: "once"
59
- }, ()=>{
60
- executionCount++;
61
- return "value";
62
- });
63
- const second = await step.run({
64
- name: "once"
65
- }, ()=>{
66
- executionCount++;
67
- return "should-not-run";
68
- });
69
- return {
70
- first,
71
- second
72
- };
73
- });
74
- const worker = client.newWorker();
75
- const handle = await workflow.run();
76
- await worker.tick();
77
- const result = await handle.result();
78
- expect(result).toEqual({
79
- first: "value",
80
- second: "value"
81
- });
82
- expect(executionCount).toBe(1);
83
- });
84
- test("marks workflow for retry when definition is missing", async ()=>{
85
- const client = new OpenWorkflow({
86
- backend
87
- });
88
- const workflowRun = await backend.createWorkflowRun({
89
- workflowName: "missing",
90
- version: null,
91
- idempotencyKey: null,
92
- config: {},
93
- context: null,
94
- input: null,
95
- availableAt: null,
96
- deadlineAt: null
97
- });
98
- const worker = client.newWorker();
99
- await worker.tick();
100
- const updated = await backend.getWorkflowRun({
101
- workflowRunId: workflowRun.id
102
- });
103
- expect(updated?.status).toBe("pending");
104
- expect(updated?.error).toBeDefined();
105
- expect(updated?.availableAt).not.toBeNull();
106
- });
107
- test("retries failed workflows automatically (known slow test)", async ()=>{
108
- const client = new OpenWorkflow({
109
- backend
110
- });
111
- let attemptCount = 0;
112
- const workflow = client.defineWorkflow({
113
- name: "retry-test"
114
- }, ()=>{
115
- attemptCount++;
116
- if (attemptCount < 2) {
117
- throw new Error(`Attempt ${String(attemptCount)} failed`);
118
- }
119
- return {
120
- success: true,
121
- attempts: attemptCount
122
- };
123
- });
124
- const worker = client.newWorker();
125
- // run the workflow
126
- const handle = await workflow.run();
127
- // first attempt - will fail and reschedule
128
- await worker.tick();
129
- await sleep(100); // wait for worker to finish
130
- expect(attemptCount).toBe(1);
131
- await sleep(1100); // wait for backoff delay
132
- // second attempt - will succeed
133
- await worker.tick();
134
- await sleep(100); // wait for worker to finish
135
- expect(attemptCount).toBe(2);
136
- const result = await handle.result();
137
- expect(result).toEqual({
138
- success: true,
139
- attempts: 2
140
- });
141
- });
142
- test("tick is a no-op when no work is available", async ()=>{
143
- const client = new OpenWorkflow({
144
- backend
145
- });
146
- client.defineWorkflow({
147
- name: "noop"
148
- }, ()=>null);
149
- const worker = client.newWorker();
150
- await worker.tick(); // no runs queued
151
- });
152
- test("handles step functions that return undefined (known slow test)", async ()=>{
153
- const client = new OpenWorkflow({
154
- backend
155
- });
156
- const workflow = client.defineWorkflow({
157
- name: "undefined-steps"
158
- }, async ({ step })=>{
159
- await step.run({
160
- name: "step-1"
161
- }, ()=>{
162
- return; // explicit undefined
163
- });
164
- await step.run({
165
- name: "step-2"
166
- }, ()=>{
167
- // implicit undefined
168
- });
169
- return {
170
- success: true
171
- };
172
- });
173
- const worker = client.newWorker();
174
- const handle = await workflow.run();
175
- await worker.tick();
176
- const result = await handle.result();
177
- expect(result).toEqual({
178
- success: true
179
- });
180
- });
181
- test("executes steps synchronously within workflow (known slow test)", async ()=>{
182
- const client = new OpenWorkflow({
183
- backend
184
- });
185
- const executionOrder = [];
186
- const workflow = client.defineWorkflow({
187
- name: "sync-steps"
188
- }, async ({ step })=>{
189
- executionOrder.push("start");
190
- await step.run({
191
- name: "step1"
192
- }, ()=>{
193
- executionOrder.push("step1");
194
- return 1;
195
- });
196
- executionOrder.push("between");
197
- await step.run({
198
- name: "step2"
199
- }, ()=>{
200
- executionOrder.push("step2");
201
- return 2;
202
- });
203
- executionOrder.push("end");
204
- return executionOrder;
205
- });
206
- const worker = client.newWorker();
207
- const handle = await workflow.run();
208
- await worker.tick();
209
- const result = await handle.result();
210
- expect(result).toEqual([
211
- "start",
212
- "step1",
213
- "between",
214
- "step2",
215
- "end"
216
- ]);
217
- });
218
- test("executes parallel steps with Promise.all (known slow test)", async ()=>{
219
- const client = new OpenWorkflow({
220
- backend
221
- });
222
- const executionTimes = {};
223
- const workflow = client.defineWorkflow({
224
- name: "parallel"
225
- }, async ({ step })=>{
226
- const start = Date.now();
227
- const [a, b, c] = await Promise.all([
228
- step.run({
229
- name: "step-a"
230
- }, ()=>{
231
- executionTimes["step-a"] = Date.now() - start;
232
- return "a";
233
- }),
234
- step.run({
235
- name: "step-b"
236
- }, ()=>{
237
- executionTimes["step-b"] = Date.now() - start;
238
- return "b";
239
- }),
240
- step.run({
241
- name: "step-c"
242
- }, ()=>{
243
- executionTimes["step-c"] = Date.now() - start;
244
- return "c";
245
- })
246
- ]);
247
- return {
248
- a,
249
- b,
250
- c
251
- };
252
- });
253
- const worker = client.newWorker();
254
- const handle = await workflow.run();
255
- await worker.tick();
256
- const result = await handle.result();
257
- expect(result).toEqual({
258
- a: "a",
259
- b: "b",
260
- c: "c"
261
- });
262
- // steps should execute at roughly the same time (within 100ms)
263
- const times = Object.values(executionTimes);
264
- const maxTime = Math.max(...times);
265
- const minTime = Math.min(...times);
266
- expect(maxTime - minTime).toBeLessThan(100);
267
- });
268
- test("respects worker concurrency limit", async ()=>{
269
- const client = new OpenWorkflow({
270
- backend
271
- });
272
- const workflow = client.defineWorkflow({
273
- name: "concurrency-test"
274
- }, ()=>{
275
- return "done";
276
- });
277
- const worker = client.newWorker({
278
- concurrency: 2
279
- });
280
- // create 5 workflow runs, though only 2 (concurrency limit) should be
281
- // completed per tick
282
- const handles = await Promise.all([
283
- workflow.run(),
284
- workflow.run(),
285
- workflow.run(),
286
- workflow.run(),
287
- workflow.run()
288
- ]);
289
- await worker.tick();
290
- await sleep(100);
291
- let completed = 0;
292
- for (const handle of handles){
293
- const run = await backend.getWorkflowRun({
294
- workflowRunId: handle.workflowRun.id
295
- });
296
- if (run?.status === "completed") completed++;
297
- }
298
- expect(completed).toBe(2);
299
- });
300
- test("worker starts, processes work, and stops gracefully (known slow test)", async ()=>{
301
- const client = new OpenWorkflow({
302
- backend
303
- });
304
- const workflow = client.defineWorkflow({
305
- name: "lifecycle"
306
- }, ()=>{
307
- return "complete";
308
- });
309
- const worker = client.newWorker();
310
- await worker.start();
311
- const handle = await workflow.run();
312
- await sleep(200);
313
- await worker.stop();
314
- const result = await handle.result();
315
- expect(result).toBe("complete");
316
- });
317
- test("recovers from crashes during parallel step execution (known slow test)", async ()=>{
318
- const client = new OpenWorkflow({
319
- backend
320
- });
321
- let attemptCount = 0;
322
- const workflow = client.defineWorkflow({
323
- name: "crash-recovery"
324
- }, async ({ step })=>{
325
- attemptCount++;
326
- const [a, b] = await Promise.all([
327
- step.run({
328
- name: "step-a"
329
- }, ()=>{
330
- if (attemptCount > 1) return "x"; // should not happen since "a" will be cached
331
- return "a";
332
- }),
333
- step.run({
334
- name: "step-b"
335
- }, ()=>{
336
- if (attemptCount === 1) throw new Error("Simulated crash");
337
- return "b";
338
- })
339
- ]);
340
- return {
341
- a,
342
- b,
343
- attempts: attemptCount
344
- };
345
- });
346
- const worker = client.newWorker();
347
- const handle = await workflow.run();
348
- // first attempt will fail
349
- await worker.tick();
350
- await sleep(100);
351
- expect(attemptCount).toBe(1);
352
- // wait for backoff
353
- await sleep(1100);
354
- // second attempt should succeed
355
- await worker.tick();
356
- await sleep(100);
357
- const result = await handle.result();
358
- expect(result).toEqual({
359
- a: "a",
360
- b: "b",
361
- attempts: 2
362
- });
363
- expect(attemptCount).toBe(2);
364
- });
365
- test("reclaims workflow run when heartbeat stops (known slow test)", async ()=>{
366
- const client = new OpenWorkflow({
367
- backend
368
- });
369
- const workflow = client.defineWorkflow({
370
- name: "heartbeat-test"
371
- }, ()=>"done");
372
- const handle = await workflow.run();
373
- const workerId = randomUUID();
374
- const claimed = await backend.claimWorkflowRun({
375
- workerId,
376
- leaseDurationMs: 50
377
- });
378
- expect(claimed).not.toBeNull();
379
- // let lease expire before starting worker
380
- await sleep(100);
381
- // worker should be able to reclaim
382
- const worker = client.newWorker();
383
- await worker.tick();
384
- const result = await handle.result();
385
- expect(result).toBe("done");
386
- });
387
- test("tick() returns count of claimed workflows", async ()=>{
388
- const client = new OpenWorkflow({
389
- backend
390
- });
391
- const workflow = client.defineWorkflow({
392
- name: "count-test"
393
- }, ()=>"result");
394
- // enqueue 3 workflows
395
- await workflow.run();
396
- await workflow.run();
397
- await workflow.run();
398
- const worker = client.newWorker({
399
- concurrency: 5
400
- });
401
- // first tick should claim 3 workflows (all available)
402
- const claimed = await worker.tick();
403
- expect(claimed).toBe(3);
404
- // second tick should claim 0 (all already claimed)
405
- const claimedAgain = await worker.tick();
406
- expect(claimedAgain).toBe(0);
407
- await worker.stop();
408
- });
409
- test("tick() respects concurrency limit", async ()=>{
410
- const client = new OpenWorkflow({
411
- backend
412
- });
413
- const workflow = client.defineWorkflow({
414
- name: "concurrency-test"
415
- }, async ()=>{
416
- await sleep(100);
417
- return "done";
418
- });
419
- // enqueue 10 workflows
420
- for(let i = 0; i < 10; i++){
421
- await workflow.run();
422
- }
423
- const worker = client.newWorker({
424
- concurrency: 3
425
- });
426
- // first tick should claim exactly 3 (concurrency limit)
427
- const claimed = await worker.tick();
428
- expect(claimed).toBe(3);
429
- // second tick should claim 0 (all slots occupied)
430
- const claimedAgain = await worker.tick();
431
- expect(claimedAgain).toBe(0);
432
- await worker.stop();
433
- });
434
- test("worker only sleeps between claims when no work is available (known slow test)", async ()=>{
435
- const client = new OpenWorkflow({
436
- backend
437
- });
438
- const workflow = client.defineWorkflow({
439
- name: "adaptive-test"
440
- }, async ({ step })=>{
441
- await step.run({
442
- name: "step-1"
443
- }, ()=>"done");
444
- return "complete";
445
- });
446
- // enqueue many workflows
447
- const handles = [];
448
- for(let i = 0; i < 20; i++){
449
- handles.push(await workflow.run());
450
- }
451
- const worker = client.newWorker({
452
- concurrency: 5
453
- });
454
- const startTime = Date.now();
455
- await worker.start();
456
- // wait for all workflows to complete
457
- await Promise.all(handles.map((h)=>h.result()));
458
- await worker.stop();
459
- const duration = Date.now() - startTime;
460
- // with this conditional sleep, all workflows should complete quickly
461
- // without it (with 100ms sleep between ticks), it would take much longer
462
- expect(duration).toBeLessThan(3000); // should complete in under 3 seconds
463
- });
464
- test("only failed steps re-execute on retry (known slow test)", async ()=>{
465
- const client = new OpenWorkflow({
466
- backend
467
- });
468
- const executionCounts = {
469
- stepA: 0,
470
- stepB: 0,
471
- stepC: 0
472
- };
473
- const workflow = client.defineWorkflow({
474
- name: "mixed-retry"
475
- }, async ({ step })=>{
476
- const a = await step.run({
477
- name: "step-a"
478
- }, ()=>{
479
- executionCounts.stepA++;
480
- return "a-result";
481
- });
482
- const b = await step.run({
483
- name: "step-b"
484
- }, ()=>{
485
- executionCounts.stepB++;
486
- if (executionCounts.stepB === 1) {
487
- throw new Error("Step B fails on first attempt");
488
- }
489
- return "b-result";
490
- });
491
- const c = await step.run({
492
- name: "step-c"
493
- }, ()=>{
494
- executionCounts.stepC++;
495
- return "c-result";
496
- });
497
- return {
498
- a,
499
- b,
500
- c
501
- };
502
- });
503
- const worker = client.newWorker();
504
- const handle = await workflow.run();
505
- // first workflow attempt
506
- // - step-a succeeds
507
- // - step-b fails
508
- // - step-c never runs (workflow fails at step-b)
509
- await worker.tick();
510
- await sleep(100);
511
- expect(executionCounts.stepA).toBe(1);
512
- expect(executionCounts.stepB).toBe(1);
513
- expect(executionCounts.stepC).toBe(0);
514
- // wait for backoff
515
- await sleep(1100);
516
- // second workflow attempt
517
- // - step-a should be cached (not re-executed)
518
- // - step-b should be re-executed (failed previously)
519
- // - step-c should execute for first time
520
- await worker.tick();
521
- await sleep(100);
522
- expect(executionCounts.stepA).toBe(1); // still 1, was cached
523
- expect(executionCounts.stepB).toBe(2); // incremented, was retried
524
- expect(executionCounts.stepC).toBe(1); // incremented, first execution
525
- const result = await handle.result();
526
- expect(result).toEqual({
527
- a: "a-result",
528
- b: "b-result",
529
- c: "c-result"
530
- });
531
- });
532
- test("step.sleep postpones workflow execution (known slow test)", async ()=>{
533
- const client = new OpenWorkflow({
534
- backend
535
- });
536
- let stepCount = 0;
537
- const workflow = client.defineWorkflow({
538
- name: "sleep-test"
539
- }, async ({ step })=>{
540
- const before = await step.run({
541
- name: "before-sleep"
542
- }, ()=>{
543
- stepCount++;
544
- return "before";
545
- });
546
- await step.sleep("pause", "100ms");
547
- const after = await step.run({
548
- name: "after-sleep"
549
- }, ()=>{
550
- stepCount++;
551
- return "after";
552
- });
553
- return {
554
- before,
555
- after
556
- };
557
- });
558
- const worker = client.newWorker();
559
- const handle = await workflow.run();
560
- // first execution - runs before-sleep, then sleeps
561
- await worker.tick();
562
- await sleep(50); // wait for processing
563
- expect(stepCount).toBe(1);
564
- // verify workflow was postponed with sleeping status
565
- const slept = await backend.getWorkflowRun({
566
- workflowRunId: handle.workflowRun.id
567
- });
568
- expect(slept?.status).toBe("sleeping");
569
- expect(slept?.workerId).toBeNull(); // released during sleep
570
- expect(slept?.availableAt).not.toBeNull();
571
- if (!slept?.availableAt) throw new Error("availableAt should be set");
572
- const delayMs = slept.availableAt.getTime() - Date.now();
573
- expect(delayMs).toBeGreaterThan(0);
574
- expect(delayMs).toBeLessThan(150); // should be ~100ms
575
- // verify sleep step is in "running" state during sleep
576
- const attempts = await backend.listStepAttempts({
577
- workflowRunId: handle.workflowRun.id
578
- });
579
- const sleepStep = attempts.data.find((a)=>a.stepName === "pause");
580
- expect(sleepStep?.status).toBe("running");
581
- // wait for sleep duration
582
- await sleep(150);
583
- // second execution (after sleep)
584
- await worker.tick();
585
- await sleep(50); // wait for processing
586
- expect(stepCount).toBe(2);
587
- // verify sleep step is now "completed"
588
- const refreshedAttempts = await backend.listStepAttempts({
589
- workflowRunId: handle.workflowRun.id
590
- });
591
- const completedSleepStep = refreshedAttempts.data.find((a)=>a.stepName === "pause");
592
- expect(completedSleepStep?.status).toBe("completed");
593
- const result = await handle.result();
594
- expect(result).toEqual({
595
- before: "before",
596
- after: "after"
597
- });
598
- });
599
- test("step.sleep is cached on replay", async ()=>{
600
- const client = new OpenWorkflow({
601
- backend
602
- });
603
- let step1Count = 0;
604
- let step2Count = 0;
605
- const workflow = client.defineWorkflow({
606
- name: "sleep-cache-test"
607
- }, async ({ step })=>{
608
- await step.run({
609
- name: "step-1"
610
- }, ()=>{
611
- step1Count++;
612
- return "one";
613
- });
614
- // this should only postpone once
615
- await step.sleep("wait", "50ms");
616
- await step.run({
617
- name: "step-2"
618
- }, ()=>{
619
- step2Count++;
620
- return "two";
621
- });
622
- return "done";
623
- });
624
- const worker = client.newWorker();
625
- const handle = await workflow.run();
626
- // first attempt: execute step-1, then sleep (step-2 not executed)
627
- await worker.tick();
628
- await sleep(50);
629
- expect(step1Count).toBe(1);
630
- expect(step2Count).toBe(0);
631
- await sleep(100); // wait for sleep to complete
632
- // second attempt: step-1 is cached (not re-executed), sleep is cached, step-2 executes
633
- await worker.tick();
634
- await sleep(50);
635
- expect(step1Count).toBe(1); // still 1, was cached
636
- expect(step2Count).toBe(1); // now 1, executed after sleep
637
- const result = await handle.result();
638
- expect(result).toBe("done");
639
- });
640
- test("step.sleep throws error for invalid duration format", async ()=>{
641
- const client = new OpenWorkflow({
642
- backend
643
- });
644
- const workflow = client.defineWorkflow({
645
- name: "invalid-duration"
646
- }, async ({ step })=>{
647
- // @ts-expect-error - testing invalid duration
648
- await step.sleep("bad", "invalid");
649
- return "should-not-reach";
650
- });
651
- const worker = client.newWorker();
652
- const handle = await workflow.run();
653
- await worker.tick();
654
- await sleep(100);
655
- const failed = await backend.getWorkflowRun({
656
- workflowRunId: handle.workflowRun.id
657
- });
658
- expect(failed?.status).toBe("pending"); // should be retrying
659
- expect(failed?.error).toBeDefined();
660
- expect(failed?.error?.message).toContain("Invalid duration format");
661
- });
662
- test("step.sleep handles multiple sequential sleeps (known slow test)", async ()=>{
663
- const client = new OpenWorkflow({
664
- backend
665
- });
666
- let executionCount = 0;
667
- const workflow = client.defineWorkflow({
668
- name: "sequential-sleeps"
669
- }, async ({ step })=>{
670
- executionCount++;
671
- await step.run({
672
- name: "step-1"
673
- }, ()=>"one");
674
- await step.sleep("sleep-1", "50ms");
675
- await step.run({
676
- name: "step-2"
677
- }, ()=>"two");
678
- await step.sleep("sleep-2", "50ms");
679
- await step.run({
680
- name: "step-3"
681
- }, ()=>"three");
682
- return "done";
683
- });
684
- const worker = client.newWorker();
685
- const handle = await workflow.run();
686
- // first execution: step-1, then sleep-1
687
- await worker.tick();
688
- await sleep(50);
689
- expect(executionCount).toBe(1);
690
- // verify first sleep is running
691
- const attempts1 = await backend.listStepAttempts({
692
- workflowRunId: handle.workflowRun.id
693
- });
694
- expect(attempts1.data.find((a)=>a.stepName === "sleep-1")?.status).toBe("running");
695
- // wait for first sleep
696
- await sleep(100);
697
- // second execution: sleep-1 completed, step-2, then sleep-2
698
- await worker.tick();
699
- await sleep(50);
700
- expect(executionCount).toBe(2);
701
- // verify second sleep is running
702
- const attempts2 = await backend.listStepAttempts({
703
- workflowRunId: handle.workflowRun.id
704
- });
705
- expect(attempts2.data.find((a)=>a.stepName === "sleep-1")?.status).toBe("completed");
706
- expect(attempts2.data.find((a)=>a.stepName === "sleep-2")?.status).toBe("running");
707
- // wait for second sleep
708
- await sleep(100);
709
- // third execution: sleep-2 completed, step-3, complete
710
- await worker.tick();
711
- await sleep(50);
712
- expect(executionCount).toBe(3);
713
- const result = await handle.result();
714
- expect(result).toBe("done");
715
- // verify all steps completed
716
- const finalAttempts = await backend.listStepAttempts({
717
- workflowRunId: handle.workflowRun.id
718
- });
719
- expect(finalAttempts.data.length).toBe(5); // 3 regular steps + 2 sleeps
720
- expect(finalAttempts.data.every((a)=>a.status === "completed")).toBe(true);
721
- });
722
- test("sleeping workflows can be claimed after availableAt", async ()=>{
723
- const client = new OpenWorkflow({
724
- backend
725
- });
726
- const workflow = client.defineWorkflow({
727
- name: "sleeping-claim-test"
728
- }, async ({ step })=>{
729
- await step.run({
730
- name: "before"
731
- }, ()=>"before");
732
- await step.sleep("wait", "100ms");
733
- await step.run({
734
- name: "after"
735
- }, ()=>"after");
736
- return "done";
737
- });
738
- const worker = client.newWorker();
739
- const handle = await workflow.run();
740
- // first execution - sleep
741
- await worker.tick();
742
- await sleep(50);
743
- // verify workflow is in sleeping state
744
- const sleeping = await backend.getWorkflowRun({
745
- workflowRunId: handle.workflowRun.id
746
- });
747
- expect(sleeping?.status).toBe("sleeping");
748
- expect(sleeping?.workerId).toBeNull();
749
- // wait for sleep duration
750
- await sleep(100);
751
- // verify workflow can be claimed again
752
- const claimed = await backend.claimWorkflowRun({
753
- workerId: "test-worker",
754
- leaseDurationMs: 30_000
755
- });
756
- expect(claimed?.id).toBe(handle.workflowRun.id);
757
- expect(claimed?.status).toBe("running");
758
- expect(claimed?.workerId).toBe("test-worker");
759
- });
760
- test("sleep is not skipped when worker crashes after creating sleep step but before marking workflow as sleeping (known slow test)", async ()=>{
761
- const client = new OpenWorkflow({
762
- backend
763
- });
764
- let executionCount = 0;
765
- let beforeSleepCount = 0;
766
- let afterSleepCount = 0;
767
- const workflow = client.defineWorkflow({
768
- name: "crash-during-sleep"
769
- }, async ({ step })=>{
770
- executionCount++;
771
- await step.run({
772
- name: "before-sleep"
773
- }, ()=>{
774
- beforeSleepCount++;
775
- return "before";
776
- });
777
- // this sleep should NOT be skipped even if crash happens
778
- await step.sleep("critical-pause", "200ms");
779
- await step.run({
780
- name: "after-sleep"
781
- }, ()=>{
782
- afterSleepCount++;
783
- return "after";
784
- });
785
- return {
786
- executionCount,
787
- beforeSleepCount,
788
- afterSleepCount
789
- };
790
- });
791
- const handle = await workflow.run();
792
- // first worker processes the workflow until sleep
793
- const worker1 = client.newWorker();
794
- await worker1.tick();
795
- await sleep(100);
796
- const workflowAfterFirst = await backend.getWorkflowRun({
797
- workflowRunId: handle.workflowRun.id
798
- });
799
- expect(workflowAfterFirst?.status).toBe("sleeping");
800
- const attemptsAfterFirst = await backend.listStepAttempts({
801
- workflowRunId: handle.workflowRun.id
802
- });
803
- const sleepStep = attemptsAfterFirst.data.find((a)=>a.stepName === "critical-pause");
804
- expect(sleepStep).toBeDefined();
805
- expect(sleepStep?.kind).toBe("sleep");
806
- expect(sleepStep?.status).toBe("running");
807
- await sleep(50); // only 50ms of the 200ms sleep
808
- // if there's a running sleep step, the workflow should be properly
809
- // transitioned to sleeping
810
- const worker2 = client.newWorker();
811
- await worker2.tick();
812
- // after-sleep step should NOT have executed yet
813
- expect(afterSleepCount).toBe(0);
814
- // wait for the full sleep duration to elapse then check to make sure
815
- // workflow is claimable and resume
816
- await sleep(200);
817
- await worker2.tick();
818
- await sleep(100);
819
- expect(afterSleepCount).toBe(1);
820
- const result = await handle.result();
821
- expect(result.afterSleepCount).toBe(1);
822
- });
823
- test("version enables conditional code paths (known slow test)", async ()=>{
824
- const client = new OpenWorkflow({
825
- backend
826
- });
827
- const workflow = client.defineWorkflow({
828
- name: "conditional-workflow",
829
- version: "v2"
830
- }, async ({ version, step })=>{
831
- return version === "v1" ? await step.run({
832
- name: "old-step"
833
- }, ()=>"old-logic") : await step.run({
834
- name: "new-step"
835
- }, ()=>"new-logic");
836
- });
837
- const worker = client.newWorker();
838
- const handle = await workflow.run();
839
- await worker.tick();
840
- const result = await handle.result();
841
- expect(result).toBe("new-logic");
842
- });
843
- test("workflow version is null when not specified", async ()=>{
844
- const client = new OpenWorkflow({
845
- backend
846
- });
847
- const workflow = client.defineWorkflow({
848
- name: "unversioned-workflow"
849
- }, async ({ version, step })=>{
850
- const result = await step.run({
851
- name: "check-version"
852
- }, ()=>{
853
- return {
854
- version
855
- };
856
- });
857
- return result;
858
- });
859
- const worker = client.newWorker();
860
- const handle = await workflow.run();
861
- await worker.tick();
862
- const result = await handle.result();
863
- expect(result.version).toBeNull();
864
- });
865
- test("cancels a pending workflow", async ()=>{
866
- const client = new OpenWorkflow({
867
- backend
868
- });
869
- const workflow = client.defineWorkflow({
870
- name: "cancel-pending"
871
- }, async ({ step })=>{
872
- await step.run({
873
- name: "step-1"
874
- }, ()=>"result");
875
- return {
876
- completed: true
877
- };
878
- });
879
- const handle = await workflow.run();
880
- // cancel before worker processes it
881
- await handle.cancel();
882
- const workflowRun = await backend.getWorkflowRun({
883
- workflowRunId: handle.workflowRun.id
884
- });
885
- expect(workflowRun?.status).toBe("canceled");
886
- expect(workflowRun?.finishedAt).not.toBeNull();
887
- expect(workflowRun?.availableAt).toBeNull();
888
- expect(workflowRun?.workerId).toBeNull();
889
- });
890
- test("cancels a sleeping workflow", async ()=>{
891
- const client = new OpenWorkflow({
892
- backend
893
- });
894
- const workflow = client.defineWorkflow({
895
- name: "cancel-sleeping"
896
- }, async ({ step })=>{
897
- await step.sleep("sleep-1", "1h");
898
- return {
899
- completed: true
900
- };
901
- });
902
- const worker = client.newWorker();
903
- const handle = await workflow.run();
904
- await worker.tick();
905
- // cancel while sleeping
906
- await handle.cancel();
907
- const canceled = await backend.getWorkflowRun({
908
- workflowRunId: handle.workflowRun.id
909
- });
910
- expect(canceled?.status).toBe("canceled");
911
- expect(canceled?.finishedAt).not.toBeNull();
912
- expect(canceled?.availableAt).toBeNull();
913
- expect(canceled?.workerId).toBeNull();
914
- });
915
- test("cannot cancel a completed workflow", async ()=>{
916
- const client = new OpenWorkflow({
917
- backend
918
- });
919
- const workflow = client.defineWorkflow({
920
- name: "cancel-completed"
921
- }, ()=>({
922
- completed: true
923
- }));
924
- const worker = client.newWorker();
925
- const handle = await workflow.run();
926
- await worker.tick();
927
- const result = await handle.result();
928
- expect(result.completed).toBe(true);
929
- // try to cancel after success
930
- await expect(handle.cancel()).rejects.toThrow(/Cannot cancel workflow run .* with status completed/);
931
- });
932
- test("cannot cancel a failed workflow", async ()=>{
933
- const client = new OpenWorkflow({
934
- backend
935
- });
936
- const workflow = client.defineWorkflow({
937
- name: "cancel-failed"
938
- }, ()=>{
939
- throw new Error("intentional failure");
940
- });
941
- const worker = client.newWorker();
942
- const handle = await workflow.run({
943
- value: 1
944
- }, {
945
- deadlineAt: new Date()
946
- });
947
- await worker.tick();
948
- // wait for it to fail due to deadline
949
- await sleep(100);
950
- const failed = await backend.getWorkflowRun({
951
- workflowRunId: handle.workflowRun.id
952
- });
953
- expect(failed?.status).toBe("failed");
954
- // try to cancel after failure
955
- await expect(handle.cancel()).rejects.toThrow(/Cannot cancel workflow run .* with status failed/);
956
- });
957
- test("cannot cancel non-existent workflow", async ()=>{
958
- await expect(backend.cancelWorkflowRun({
959
- workflowRunId: "non-existent-id"
960
- })).rejects.toThrow(/Workflow run non-existent-id does not exist/);
961
- });
962
- test("worker handles when canceled workflow during execution", async ()=>{
963
- const client = new OpenWorkflow({
964
- backend
965
- });
966
- let stepExecuted = false;
967
- const workflow = client.defineWorkflow({
968
- name: "cancel-during-execution"
969
- }, async ({ step })=>{
970
- await step.run({
971
- name: "step-1"
972
- }, async ()=>{
973
- stepExecuted = true;
974
- // simulate some work
975
- await sleep(50);
976
- return "result";
977
- });
978
- return {
979
- completed: true
980
- };
981
- });
982
- const worker = client.newWorker();
983
- const handle = await workflow.run();
984
- // start processing in the background
985
- const tickPromise = worker.tick();
986
- await sleep(25);
987
- // cancel while step is executing
988
- await handle.cancel();
989
- // wait for tick to complete
990
- await tickPromise;
991
- // step should have been executed but workflow should be canceled
992
- expect(stepExecuted).toBe(true);
993
- const canceled = await backend.getWorkflowRun({
994
- workflowRunId: handle.workflowRun.id
995
- });
996
- expect(canceled?.status).toBe("canceled");
997
- });
998
- test("result() rejects for canceled workflows", async ()=>{
999
- const client = new OpenWorkflow({
1000
- backend
1001
- });
1002
- const workflow = client.defineWorkflow({
1003
- name: "cancel-result"
1004
- }, async ({ step })=>{
1005
- await step.sleep("sleep-1", "1h");
1006
- return {
1007
- completed: true
1008
- };
1009
- });
1010
- const handle = await workflow.run();
1011
- await handle.cancel();
1012
- await expect(handle.result()).rejects.toThrow(/Workflow cancel-result was canceled/);
1013
- });
1014
- describe("version matching", ()=>{
1015
- test("worker matches workflow runs by version", async ()=>{
1016
- const client = new OpenWorkflow({
1017
- backend
1018
- });
1019
- client.defineWorkflow({
1020
- name: "versioned-workflow",
1021
- version: "v1"
1022
- }, async ({ step })=>{
1023
- return await step.run({
1024
- name: "compute"
1025
- }, ()=>"v1-result");
1026
- });
1027
- client.defineWorkflow({
1028
- name: "versioned-workflow",
1029
- version: "v2"
1030
- }, async ({ step })=>{
1031
- return await step.run({
1032
- name: "compute"
1033
- }, ()=>"v2-result");
1034
- });
1035
- const worker = client.newWorker({
1036
- concurrency: 2
1037
- });
1038
- const v1Spec = declareWorkflow({
1039
- name: "versioned-workflow",
1040
- version: "v1"
1041
- });
1042
- const v2Spec = declareWorkflow({
1043
- name: "versioned-workflow",
1044
- version: "v2"
1045
- });
1046
- const handleV1 = await client.runWorkflow(v1Spec);
1047
- const handleV2 = await client.runWorkflow(v2Spec);
1048
- await worker.tick();
1049
- await sleep(100); // wait for background execution
1050
- const resultV1 = await handleV1.result();
1051
- const resultV2 = await handleV2.result();
1052
- expect(resultV1).toBe("v1-result");
1053
- expect(resultV2).toBe("v2-result");
1054
- });
1055
- test("worker fails workflow run when version is not registered", async ()=>{
1056
- const client = new OpenWorkflow({
1057
- backend
1058
- });
1059
- client.defineWorkflow({
1060
- name: "version-check",
1061
- version: "v1"
1062
- }, ()=>"v1-result");
1063
- const worker = client.newWorker();
1064
- const workflowRun = await backend.createWorkflowRun({
1065
- workflowName: "version-check",
1066
- version: "v2",
1067
- idempotencyKey: null,
1068
- config: {},
1069
- context: null,
1070
- input: null,
1071
- availableAt: null,
1072
- deadlineAt: null
1073
- });
1074
- await worker.tick();
1075
- const updated = await backend.getWorkflowRun({
1076
- workflowRunId: workflowRun.id
1077
- });
1078
- expect(updated?.status).toBe("pending");
1079
- expect(updated?.error).toEqual({
1080
- message: 'Workflow "version-check" (version: v2) is not registered'
1081
- });
1082
- });
1083
- test("unversioned workflow does not match versioned run", async ()=>{
1084
- const client = new OpenWorkflow({
1085
- backend
1086
- });
1087
- client.defineWorkflow({
1088
- name: "version-mismatch"
1089
- }, ()=>"unversioned-result");
1090
- const worker = client.newWorker();
1091
- const workflowRun = await backend.createWorkflowRun({
1092
- workflowName: "version-mismatch",
1093
- version: "v1",
1094
- idempotencyKey: null,
1095
- config: {},
1096
- context: null,
1097
- input: null,
1098
- availableAt: null,
1099
- deadlineAt: null
1100
- });
1101
- await worker.tick();
1102
- const updated = await backend.getWorkflowRun({
1103
- workflowRunId: workflowRun.id
1104
- });
1105
- expect(updated?.status).toBe("pending");
1106
- expect(updated?.error).toEqual({
1107
- message: 'Workflow "version-mismatch" (version: v1) is not registered'
1108
- });
1109
- });
1110
- test("versioned workflow does not match unversioned run", async ()=>{
1111
- const client = new OpenWorkflow({
1112
- backend
1113
- });
1114
- client.defineWorkflow({
1115
- name: "version-required",
1116
- version: "v1"
1117
- }, ()=>"v1-result");
1118
- const worker = client.newWorker();
1119
- const workflowRun = await backend.createWorkflowRun({
1120
- workflowName: "version-required",
1121
- version: null,
1122
- idempotencyKey: null,
1123
- config: {},
1124
- context: null,
1125
- input: null,
1126
- availableAt: null,
1127
- deadlineAt: null
1128
- });
1129
- await worker.tick();
1130
- const updated = await backend.getWorkflowRun({
1131
- workflowRunId: workflowRun.id
1132
- });
1133
- expect(updated?.status).toBe("pending");
1134
- expect(updated?.error).toEqual({
1135
- message: 'Workflow "version-required" is not registered'
1136
- });
1137
- });
1138
- test("workflow receives run's version, not registered version", async ()=>{
1139
- // this test verifies that the version passed to the workflow function
1140
- // is the one from the workflow run, not the registered workflow
1141
- const client = new OpenWorkflow({
1142
- backend
1143
- });
1144
- const workflow = client.defineWorkflow({
1145
- name: "version-in-handler",
1146
- version: "v1"
1147
- }, async ({ version, step })=>{
1148
- return await step.run({
1149
- name: "get-version"
1150
- }, ()=>version);
1151
- });
1152
- const worker = client.newWorker();
1153
- const handle = await workflow.run();
1154
- await worker.tick();
1155
- const result = await handle.result();
1156
- expect(result).toBe("v1");
1157
- });
1158
- });
1159
- });
1160
- function sleep(ms) {
1161
- return new Promise((resolve)=>setTimeout(resolve, ms));
1162
- }
1163
-
1164
- //# sourceMappingURL=worker.test.js.map