@temporal-contract/worker 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +25 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/activity.cjs +8 -0
- package/dist/activity.d.cts +2 -0
- package/dist/activity.d.mts +2 -0
- package/dist/activity.mjs +3 -0
- package/dist/errors-CqX81ysy.d.cts +302 -0
- package/dist/errors-oc-Iiwmu.d.mts +302 -0
- package/dist/handler-B3KY0uDx.cjs +492 -0
- package/dist/handler-aA2RFdV7.mjs +409 -0
- package/dist/workflow.cjs +11 -0
- package/dist/workflow.d.cts +2 -0
- package/dist/workflow.d.mts +2 -0
- package/dist/workflow.mjs +3 -0
- package/package.json +72 -0
- package/src/activity.ts +15 -0
- package/src/errors.ts +164 -0
- package/src/handler.spec.ts +525 -0
- package/src/handler.ts +621 -0
- package/src/workflow.ts +20 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { declareActivitiesHandler, declareWorkflow } from "./handler.js";
|
|
4
|
+
import type { ActivityImplementations } from "./handler.js";
|
|
5
|
+
import type { ContractDefinition, WorkflowDefinition } from "@temporal-contract/contract";
|
|
6
|
+
import {
|
|
7
|
+
ActivityDefinitionNotFoundError,
|
|
8
|
+
ActivityInputValidationError,
|
|
9
|
+
ActivityOutputValidationError,
|
|
10
|
+
WorkflowInputValidationError,
|
|
11
|
+
WorkflowOutputValidationError,
|
|
12
|
+
} from "./errors.js";
|
|
13
|
+
|
|
14
|
+
// Mock Temporal workflow functions
|
|
15
|
+
vi.mock("@temporalio/workflow", () => ({
|
|
16
|
+
proxyActivities: vi.fn(() => ({})),
|
|
17
|
+
workflowInfo: vi.fn(() => ({
|
|
18
|
+
workflowId: "test-workflow",
|
|
19
|
+
runId: "test-run",
|
|
20
|
+
workflowType: "testWorkflow",
|
|
21
|
+
namespace: "default",
|
|
22
|
+
taskQueue: "test-queue",
|
|
23
|
+
})),
|
|
24
|
+
setHandler: vi.fn(),
|
|
25
|
+
defineSignal: vi.fn((name) => ({ name, type: "signal" })),
|
|
26
|
+
defineQuery: vi.fn((name) => ({ name, type: "query" })),
|
|
27
|
+
defineUpdate: vi.fn((name) => ({ name, type: "update" })),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
describe("Worker Package", () => {
|
|
31
|
+
describe("declareActivitiesHandler", () => {
|
|
32
|
+
it("should create an activities handler with validation", () => {
|
|
33
|
+
const contract = {
|
|
34
|
+
taskQueue: "test-queue",
|
|
35
|
+
workflows: {
|
|
36
|
+
testWorkflow: {
|
|
37
|
+
input: z.object({ value: z.string() }),
|
|
38
|
+
output: z.object({ result: z.string() }),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
activities: {
|
|
42
|
+
sendEmail: {
|
|
43
|
+
input: z.object({ to: z.string(), subject: z.string(), body: z.string() }),
|
|
44
|
+
output: z.object({ sent: z.boolean() }),
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
} satisfies ContractDefinition;
|
|
48
|
+
|
|
49
|
+
const handler = declareActivitiesHandler({
|
|
50
|
+
contract,
|
|
51
|
+
activities: {
|
|
52
|
+
sendEmail: async (args) => {
|
|
53
|
+
expect(args.to).toBeDefined();
|
|
54
|
+
return { sent: true };
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(handler.contract).toBe(contract);
|
|
60
|
+
expect(handler.activities["sendEmail"]).toBeDefined();
|
|
61
|
+
expect(typeof handler.activities["sendEmail"]).toBe("function");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should validate activity input with Zod", async () => {
|
|
65
|
+
const contract = {
|
|
66
|
+
taskQueue: "test-queue",
|
|
67
|
+
workflows: {},
|
|
68
|
+
activities: {
|
|
69
|
+
processPayment: {
|
|
70
|
+
input: z.object({ amount: z.number(), currency: z.string() }),
|
|
71
|
+
output: z.object({ transactionId: z.string() }),
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
} satisfies ContractDefinition;
|
|
75
|
+
|
|
76
|
+
const handler = declareActivitiesHandler({
|
|
77
|
+
contract,
|
|
78
|
+
activities: {
|
|
79
|
+
processPayment: async (args) => {
|
|
80
|
+
return { transactionId: `tx-${args.amount}` };
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Valid input should work - Temporal passes as array
|
|
86
|
+
const result = await handler.activities["processPayment"]!({ amount: 100, currency: "USD" });
|
|
87
|
+
expect(result).toEqual(expect.objectContaining({ transactionId: "tx-100" }));
|
|
88
|
+
|
|
89
|
+
// Invalid input should throw
|
|
90
|
+
await expect(
|
|
91
|
+
handler.activities["processPayment"]!({ amount: "invalid" as unknown, currency: "USD" }),
|
|
92
|
+
).rejects.toThrow();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should validate activity output with Zod", async () => {
|
|
96
|
+
const contract = {
|
|
97
|
+
taskQueue: "test-queue",
|
|
98
|
+
workflows: {},
|
|
99
|
+
activities: {
|
|
100
|
+
fetchData: {
|
|
101
|
+
input: z.object({ id: z.string() }),
|
|
102
|
+
output: z.object({ data: z.string(), timestamp: z.number() }),
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
} satisfies ContractDefinition;
|
|
106
|
+
|
|
107
|
+
const handler = declareActivitiesHandler({
|
|
108
|
+
contract,
|
|
109
|
+
activities: {
|
|
110
|
+
fetchData: async (_args): Promise<{ data: string; timestamp: number }> => {
|
|
111
|
+
return { data: "test", timestamp: "invalid" as unknown as number };
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Invalid output should throw
|
|
117
|
+
await expect(handler.activities["fetchData"]!({ id: "123" })).rejects.toThrow();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should handle workflow-specific activities", () => {
|
|
121
|
+
const contract = {
|
|
122
|
+
taskQueue: "test-queue",
|
|
123
|
+
workflows: {
|
|
124
|
+
processOrder: {
|
|
125
|
+
input: z.object({ orderId: z.string() }),
|
|
126
|
+
output: z.object({ status: z.string() }),
|
|
127
|
+
activities: {
|
|
128
|
+
validateInventory: {
|
|
129
|
+
input: z.object({ orderId: z.string() }),
|
|
130
|
+
output: z.object({ available: z.boolean() }),
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
} satisfies ContractDefinition;
|
|
136
|
+
|
|
137
|
+
const handler = declareActivitiesHandler({
|
|
138
|
+
contract,
|
|
139
|
+
activities: {
|
|
140
|
+
validateInventory: async (_args) => {
|
|
141
|
+
return { available: true };
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(handler.activities["validateInventory"]).toBeDefined();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("declareWorkflow", () => {
|
|
151
|
+
const testWorkflowDef = {
|
|
152
|
+
input: z.object({ orderId: z.string(), amount: z.number() }),
|
|
153
|
+
output: z.object({ status: z.string(), total: z.number() }),
|
|
154
|
+
} satisfies WorkflowDefinition;
|
|
155
|
+
|
|
156
|
+
const testContract = {
|
|
157
|
+
taskQueue: "test-queue",
|
|
158
|
+
workflows: {
|
|
159
|
+
processOrder: testWorkflowDef,
|
|
160
|
+
},
|
|
161
|
+
} satisfies ContractDefinition;
|
|
162
|
+
|
|
163
|
+
it("should create a workflow with validation", () => {
|
|
164
|
+
const workflow = declareWorkflow({
|
|
165
|
+
workflowName: "processOrder",
|
|
166
|
+
contract: testContract,
|
|
167
|
+
implementation: async (_context, args) => {
|
|
168
|
+
return { status: "completed", total: args.amount };
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(workflow).toBeDefined();
|
|
173
|
+
expect(typeof workflow).toBe("function");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should validate workflow input", async () => {
|
|
177
|
+
const workflow = declareWorkflow({
|
|
178
|
+
workflowName: "processOrder",
|
|
179
|
+
contract: testContract,
|
|
180
|
+
implementation: async (_context, args) => {
|
|
181
|
+
return { status: "completed", total: args.amount };
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Valid input (Temporal passes as array)
|
|
186
|
+
const result = await workflow([{ orderId: "123", amount: 100 }] as unknown as {
|
|
187
|
+
orderId: string;
|
|
188
|
+
amount: number;
|
|
189
|
+
});
|
|
190
|
+
expect(result).toEqual(expect.objectContaining({ status: "completed", total: 100 }));
|
|
191
|
+
|
|
192
|
+
// Invalid input should throw
|
|
193
|
+
await expect(
|
|
194
|
+
workflow([{ orderId: 123, amount: "invalid" }] as unknown as {
|
|
195
|
+
orderId: string;
|
|
196
|
+
amount: number;
|
|
197
|
+
}),
|
|
198
|
+
).rejects.toThrow();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should validate workflow output", async () => {
|
|
202
|
+
const workflow = declareWorkflow({
|
|
203
|
+
workflowName: "processOrder",
|
|
204
|
+
contract: testContract,
|
|
205
|
+
implementation: async (_context, _args): Promise<{ status: string; total: number }> => {
|
|
206
|
+
return { status: "completed", total: "invalid" } as unknown as {
|
|
207
|
+
status: string;
|
|
208
|
+
total: number;
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await expect(
|
|
214
|
+
workflow([{ orderId: "123", amount: 100 }] as unknown as {
|
|
215
|
+
orderId: string;
|
|
216
|
+
amount: number;
|
|
217
|
+
}),
|
|
218
|
+
).rejects.toThrow();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should register signal handlers", async () => {
|
|
222
|
+
const workflowDef = {
|
|
223
|
+
...testWorkflowDef,
|
|
224
|
+
signals: {
|
|
225
|
+
cancel: {
|
|
226
|
+
input: z.object({ reason: z.string() }),
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
} satisfies WorkflowDefinition;
|
|
230
|
+
|
|
231
|
+
const cancelHandler = vi.fn();
|
|
232
|
+
|
|
233
|
+
const contractWithSignals = {
|
|
234
|
+
taskQueue: "test-queue",
|
|
235
|
+
workflows: {
|
|
236
|
+
processOrder: workflowDef,
|
|
237
|
+
},
|
|
238
|
+
} satisfies ContractDefinition;
|
|
239
|
+
|
|
240
|
+
const workflow = declareWorkflow({
|
|
241
|
+
workflowName: "processOrder",
|
|
242
|
+
contract: contractWithSignals,
|
|
243
|
+
implementation: async (_context, args) => {
|
|
244
|
+
return { status: "completed", total: args.amount };
|
|
245
|
+
},
|
|
246
|
+
signals: {
|
|
247
|
+
cancel: cancelHandler,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(workflow).toBeDefined();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should support query handlers", () => {
|
|
255
|
+
const workflowDef = {
|
|
256
|
+
...testWorkflowDef,
|
|
257
|
+
queries: {
|
|
258
|
+
getStatus: {
|
|
259
|
+
input: z.object({}),
|
|
260
|
+
output: z.object({ status: z.string(), progress: z.number() }),
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
} satisfies WorkflowDefinition;
|
|
264
|
+
|
|
265
|
+
const queryHandler = vi.fn().mockReturnValue({ status: "running", progress: 50 });
|
|
266
|
+
|
|
267
|
+
const contractWithQueries = {
|
|
268
|
+
taskQueue: "test-queue",
|
|
269
|
+
workflows: {
|
|
270
|
+
processOrder: workflowDef,
|
|
271
|
+
},
|
|
272
|
+
} satisfies ContractDefinition;
|
|
273
|
+
|
|
274
|
+
const workflow = declareWorkflow({
|
|
275
|
+
workflowName: "processOrder",
|
|
276
|
+
contract: contractWithQueries,
|
|
277
|
+
implementation: async (_context, args) => {
|
|
278
|
+
return { status: "completed", total: args.amount };
|
|
279
|
+
},
|
|
280
|
+
queries: {
|
|
281
|
+
getStatus: queryHandler,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
expect(workflow).toBeDefined();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should support update handlers", () => {
|
|
289
|
+
const workflowDef = {
|
|
290
|
+
...testWorkflowDef,
|
|
291
|
+
updates: {
|
|
292
|
+
updateDiscount: {
|
|
293
|
+
input: z.object({ percentage: z.number() }),
|
|
294
|
+
output: z.object({ newTotal: z.number() }),
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
} satisfies WorkflowDefinition;
|
|
298
|
+
|
|
299
|
+
const updateHandler = vi.fn().mockResolvedValue({ newTotal: 90 });
|
|
300
|
+
|
|
301
|
+
const contractWithUpdates = {
|
|
302
|
+
taskQueue: "test-queue",
|
|
303
|
+
workflows: {
|
|
304
|
+
processOrder: workflowDef,
|
|
305
|
+
},
|
|
306
|
+
} satisfies ContractDefinition;
|
|
307
|
+
|
|
308
|
+
const workflow = declareWorkflow({
|
|
309
|
+
workflowName: "processOrder",
|
|
310
|
+
contract: contractWithUpdates,
|
|
311
|
+
implementation: async (_context, args) => {
|
|
312
|
+
return { status: "completed", total: args.amount };
|
|
313
|
+
},
|
|
314
|
+
updates: {
|
|
315
|
+
updateDiscount: updateHandler,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(workflow).toBeDefined();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("should handle single parameter (not array)", async () => {
|
|
323
|
+
const workflow = declareWorkflow({
|
|
324
|
+
workflowName: "processOrder",
|
|
325
|
+
contract: testContract,
|
|
326
|
+
implementation: async (_context, args) => {
|
|
327
|
+
expect(args.orderId).toBe("123");
|
|
328
|
+
expect(args.amount).toBe(100);
|
|
329
|
+
return { status: "completed", total: args.amount };
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Single parameter (should extract from array)
|
|
334
|
+
await workflow([{ orderId: "123", amount: 100 }] as unknown as {
|
|
335
|
+
orderId: string;
|
|
336
|
+
amount: number;
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
describe("Error Handling", () => {
|
|
342
|
+
it("should throw ActivityDefinitionNotFoundError with available definitions", () => {
|
|
343
|
+
const contract = {
|
|
344
|
+
taskQueue: "test-queue",
|
|
345
|
+
workflows: {},
|
|
346
|
+
activities: {
|
|
347
|
+
sendEmail: {
|
|
348
|
+
input: z.object({ to: z.string() }),
|
|
349
|
+
output: z.object({ sent: z.boolean() }),
|
|
350
|
+
},
|
|
351
|
+
processPayment: {
|
|
352
|
+
input: z.object({ amount: z.number() }),
|
|
353
|
+
output: z.object({ transactionId: z.string() }),
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
} satisfies ContractDefinition;
|
|
357
|
+
|
|
358
|
+
const testActivities = {
|
|
359
|
+
sendEmail: async () => ({ sent: true }),
|
|
360
|
+
unknownActivity: async () => ({ result: true }), // Activity not in contract
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
expect(() => {
|
|
364
|
+
declareActivitiesHandler({
|
|
365
|
+
contract,
|
|
366
|
+
activities: testActivities as unknown as ActivityImplementations<typeof contract>,
|
|
367
|
+
});
|
|
368
|
+
}).toThrow(ActivityDefinitionNotFoundError);
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
declareActivitiesHandler({
|
|
372
|
+
contract,
|
|
373
|
+
activities: testActivities as unknown as ActivityImplementations<typeof contract>,
|
|
374
|
+
});
|
|
375
|
+
} catch (error) {
|
|
376
|
+
if (error instanceof ActivityDefinitionNotFoundError) {
|
|
377
|
+
expect(error.activityName).toBe("unknownActivity");
|
|
378
|
+
expect(error.availableDefinitions).toEqual(["sendEmail", "processPayment"]);
|
|
379
|
+
expect(error.message).toContain("unknownActivity");
|
|
380
|
+
expect(error.message).toContain("sendEmail");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("should throw ActivityInputValidationError with Zod details", async () => {
|
|
386
|
+
const contract = {
|
|
387
|
+
taskQueue: "test-queue",
|
|
388
|
+
workflows: {},
|
|
389
|
+
activities: {
|
|
390
|
+
processPayment: {
|
|
391
|
+
input: z.object({ amount: z.number().positive(), currency: z.string() }),
|
|
392
|
+
output: z.object({ transactionId: z.string() }),
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
} satisfies ContractDefinition;
|
|
396
|
+
|
|
397
|
+
const handler = declareActivitiesHandler({
|
|
398
|
+
contract,
|
|
399
|
+
activities: {
|
|
400
|
+
processPayment: async (args) => {
|
|
401
|
+
return { transactionId: `tx-${args.amount}` };
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
await handler.activities["processPayment"]!({
|
|
408
|
+
amount: -100,
|
|
409
|
+
currency: "USD",
|
|
410
|
+
});
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (error instanceof ActivityInputValidationError) {
|
|
413
|
+
expect(error.activityName).toBe("processPayment");
|
|
414
|
+
expect(error.zodError).toBeDefined();
|
|
415
|
+
expect(error.message).toContain("processPayment");
|
|
416
|
+
expect(error.message).toContain("input validation failed");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("should throw ActivityOutputValidationError with Zod details", async () => {
|
|
422
|
+
const contract = {
|
|
423
|
+
taskQueue: "test-queue",
|
|
424
|
+
workflows: {},
|
|
425
|
+
activities: {
|
|
426
|
+
fetchData: {
|
|
427
|
+
input: z.object({ id: z.string() }),
|
|
428
|
+
output: z.object({ data: z.string(), timestamp: z.number() }),
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
} satisfies ContractDefinition;
|
|
432
|
+
|
|
433
|
+
const handler = declareActivitiesHandler({
|
|
434
|
+
contract,
|
|
435
|
+
activities: {
|
|
436
|
+
fetchData: async (): Promise<{ data: string; timestamp: number }> => {
|
|
437
|
+
return { data: "test", timestamp: "invalid" as unknown as number };
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
await handler.activities["fetchData"]!({ id: "123" });
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (error instanceof ActivityOutputValidationError) {
|
|
446
|
+
expect(error.activityName).toBe("fetchData");
|
|
447
|
+
expect(error.zodError).toBeDefined();
|
|
448
|
+
expect(error.message).toContain("fetchData");
|
|
449
|
+
expect(error.message).toContain("output validation failed");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("should throw WorkflowInputValidationError", async () => {
|
|
455
|
+
const workflowDef = {
|
|
456
|
+
input: z.object({ orderId: z.string().uuid(), amount: z.number().positive() }),
|
|
457
|
+
output: z.object({ status: z.string() }),
|
|
458
|
+
} satisfies WorkflowDefinition;
|
|
459
|
+
|
|
460
|
+
const contract = {
|
|
461
|
+
taskQueue: "test-queue",
|
|
462
|
+
workflows: {
|
|
463
|
+
processOrder: workflowDef,
|
|
464
|
+
},
|
|
465
|
+
} satisfies ContractDefinition;
|
|
466
|
+
|
|
467
|
+
const workflow = declareWorkflow({
|
|
468
|
+
workflowName: "processOrder",
|
|
469
|
+
contract: contract,
|
|
470
|
+
implementation: async () => {
|
|
471
|
+
return { status: "completed" };
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
await workflow([{ orderId: "not-a-uuid", amount: 100 }] as unknown as {
|
|
477
|
+
orderId: string;
|
|
478
|
+
amount: number;
|
|
479
|
+
});
|
|
480
|
+
} catch (error) {
|
|
481
|
+
if (error instanceof WorkflowInputValidationError) {
|
|
482
|
+
expect(error.workflowName).toBe("processOrder");
|
|
483
|
+
expect(error.zodError).toBeDefined();
|
|
484
|
+
expect(error.message).toContain("input validation failed");
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("should throw WorkflowOutputValidationError", async () => {
|
|
490
|
+
const workflowDef = {
|
|
491
|
+
input: z.object({ orderId: z.string() }),
|
|
492
|
+
output: z.object({ status: z.enum(["completed", "failed", "pending"]), total: z.number() }),
|
|
493
|
+
} satisfies WorkflowDefinition;
|
|
494
|
+
|
|
495
|
+
const contract = {
|
|
496
|
+
taskQueue: "test-queue",
|
|
497
|
+
workflows: {
|
|
498
|
+
processOrder: workflowDef,
|
|
499
|
+
},
|
|
500
|
+
} satisfies ContractDefinition;
|
|
501
|
+
|
|
502
|
+
const workflow = declareWorkflow({
|
|
503
|
+
workflowName: "processOrder",
|
|
504
|
+
contract: contract,
|
|
505
|
+
implementation: async () => {
|
|
506
|
+
// Return invalid status to trigger validation error
|
|
507
|
+
return { status: "invalid-status", total: 100 } as unknown as {
|
|
508
|
+
status: "completed" | "failed" | "pending";
|
|
509
|
+
total: number;
|
|
510
|
+
};
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
await workflow([{ orderId: "123" }] as unknown as { orderId: string });
|
|
516
|
+
} catch (error) {
|
|
517
|
+
if (error instanceof WorkflowOutputValidationError) {
|
|
518
|
+
expect(error.workflowName).toBe("processOrder");
|
|
519
|
+
expect(error.zodError).toBeDefined();
|
|
520
|
+
expect(error.message).toContain("output validation failed");
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
});
|