@temporal-contract/client 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -3
- package/.turbo/turbo-build.log +0 -17
- package/CHANGELOG.md +0 -9
- package/src/client.spec.ts +0 -564
- package/src/client.ts +0 -366
- package/src/errors.ts +0 -91
- package/src/index.ts +0 -9
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@temporal-contract/client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Client utilities for consuming temporal-contract workflows",
|
|
6
6
|
"homepage": "https://github.com/btravers/temporal-contract#readme",
|
|
@@ -36,9 +36,12 @@
|
|
|
36
36
|
},
|
|
37
37
|
"./package.json": "./package.json"
|
|
38
38
|
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
39
42
|
"dependencies": {
|
|
40
43
|
"@standard-schema/spec": "1.0.0",
|
|
41
|
-
"@temporal-contract/contract": "0.0.
|
|
44
|
+
"@temporal-contract/contract": "0.0.3"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
47
|
"@temporalio/client": "1.13.2",
|
|
@@ -48,7 +51,7 @@
|
|
|
48
51
|
"typescript": "5.9.3",
|
|
49
52
|
"vitest": "4.0.15",
|
|
50
53
|
"zod": "4.1.13",
|
|
51
|
-
"@temporal-contract/tsconfig": "0.0.
|
|
54
|
+
"@temporal-contract/tsconfig": "0.0.3"
|
|
52
55
|
},
|
|
53
56
|
"peerDependencies": {
|
|
54
57
|
"@temporalio/client": ">=1.13.0 <2.0.0"
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @temporal-contract/client@0.0.2 build /home/runner/work/temporal-contract/temporal-contract/packages/client
|
|
3
|
-
> tsdown src/index.ts --format cjs,esm --dts --clean
|
|
4
|
-
|
|
5
|
-
[34mℹ[39m tsdown [2mv0.17.2[22m powered by rolldown [2mv1.0.0-beta.53[22m
|
|
6
|
-
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
7
|
-
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
8
|
-
[34mℹ[39m Build start
|
|
9
|
-
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1mindex.cjs[22m [2m8.83 kB[22m [2m│ gzip: 1.84 kB[22m
|
|
10
|
-
[34mℹ[39m [33m[CJS][39m 1 files, total: 8.83 kB
|
|
11
|
-
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32m[1mindex.d.cts[22m[39m [2m7.02 kB[22m [2m│ gzip: 1.67 kB[22m
|
|
12
|
-
[34mℹ[39m [33m[CJS][39m 1 files, total: 7.02 kB
|
|
13
|
-
[32m✔[39m Build complete in [32m4263ms[39m
|
|
14
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mindex.mjs[22m [2m8.60 kB[22m [2m│ gzip: 1.81 kB[22m
|
|
15
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m7.02 kB[22m [2m│ gzip: 1.67 kB[22m
|
|
16
|
-
[34mℹ[39m [34m[ESM][39m 2 files, total: 15.62 kB
|
|
17
|
-
[32m✔[39m Build complete in [32m4270ms[39m
|
package/CHANGELOG.md
DELETED
package/src/client.spec.ts
DELETED
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { defineContract } from "@temporal-contract/contract";
|
|
4
|
-
import { TypedClient } from "./client.js";
|
|
5
|
-
import {
|
|
6
|
-
WorkflowNotFoundError,
|
|
7
|
-
WorkflowValidationError,
|
|
8
|
-
QueryValidationError,
|
|
9
|
-
SignalValidationError,
|
|
10
|
-
UpdateValidationError,
|
|
11
|
-
} from "./errors.js";
|
|
12
|
-
|
|
13
|
-
// Create mock workflow object
|
|
14
|
-
const createMockWorkflow = () => ({
|
|
15
|
-
start: vi.fn(),
|
|
16
|
-
execute: vi.fn(),
|
|
17
|
-
getHandle: vi.fn(),
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// Mock Temporal Client
|
|
21
|
-
const mockWorkflow = createMockWorkflow();
|
|
22
|
-
|
|
23
|
-
vi.mock("@temporalio/client", () => ({
|
|
24
|
-
Client: class {
|
|
25
|
-
workflow = mockWorkflow;
|
|
26
|
-
},
|
|
27
|
-
WorkflowHandle: vi.fn(),
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
describe("TypedClient", () => {
|
|
31
|
-
const testContract = defineContract({
|
|
32
|
-
taskQueue: "test-queue",
|
|
33
|
-
workflows: {
|
|
34
|
-
testWorkflow: {
|
|
35
|
-
input: z.tuple([z.string(), z.number()]),
|
|
36
|
-
output: z.object({ result: z.string() }),
|
|
37
|
-
queries: {
|
|
38
|
-
getStatus: {
|
|
39
|
-
input: z.tuple([]),
|
|
40
|
-
output: z.string(),
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
signals: {
|
|
44
|
-
updateProgress: {
|
|
45
|
-
input: z.tuple([z.number()]),
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
updates: {
|
|
49
|
-
setConfig: {
|
|
50
|
-
input: z.tuple([z.object({ value: z.string() })]),
|
|
51
|
-
output: z.boolean(),
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
simpleWorkflow: {
|
|
56
|
-
input: z.tuple([z.string()]),
|
|
57
|
-
output: z.string(),
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
let typedClient: TypedClient<typeof testContract>;
|
|
63
|
-
|
|
64
|
-
beforeEach(() => {
|
|
65
|
-
vi.clearAllMocks();
|
|
66
|
-
typedClient = TypedClient.create(testContract, { namespace: "default" });
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe("TypedClient.create", () => {
|
|
70
|
-
it("should create a typed client instance", () => {
|
|
71
|
-
expect(typedClient).toBeInstanceOf(TypedClient);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe("startWorkflow", () => {
|
|
76
|
-
it("should start a workflow with valid input", async () => {
|
|
77
|
-
const mockHandle = {
|
|
78
|
-
workflowId: "test-123",
|
|
79
|
-
result: vi.fn().mockResolvedValue({ result: "success" }),
|
|
80
|
-
query: vi.fn(),
|
|
81
|
-
signal: vi.fn(),
|
|
82
|
-
executeUpdate: vi.fn(),
|
|
83
|
-
terminate: vi.fn(),
|
|
84
|
-
cancel: vi.fn(),
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
mockWorkflow.start.mockResolvedValue(mockHandle);
|
|
88
|
-
|
|
89
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
90
|
-
workflowId: "test-123",
|
|
91
|
-
args: ["hello", 42],
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
expect(mockWorkflow.start).toHaveBeenCalledWith("testWorkflow", {
|
|
95
|
-
workflowId: "test-123",
|
|
96
|
-
taskQueue: "test-queue",
|
|
97
|
-
args: [["hello", 42]],
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
expect(handle.workflowId).toBe("test-123");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("should throw WorkflowNotFoundError for unknown workflow", async () => {
|
|
104
|
-
await expect(
|
|
105
|
-
// @ts-expect-error Testing invalid workflow name
|
|
106
|
-
typedClient.startWorkflow("unknownWorkflow", {
|
|
107
|
-
workflowId: "test-123",
|
|
108
|
-
args: ["test"],
|
|
109
|
-
}),
|
|
110
|
-
).rejects.toThrow(WorkflowNotFoundError);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("should throw WorkflowValidationError for invalid input", async () => {
|
|
114
|
-
await expect(
|
|
115
|
-
typedClient.startWorkflow("testWorkflow", {
|
|
116
|
-
workflowId: "test-123",
|
|
117
|
-
// @ts-expect-error Testing invalid input
|
|
118
|
-
args: ["hello", "not-a-number"],
|
|
119
|
-
}),
|
|
120
|
-
).rejects.toThrow(WorkflowValidationError);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("should pass Temporal options to underlying client", async () => {
|
|
124
|
-
const mockHandle = {
|
|
125
|
-
workflowId: "test-123",
|
|
126
|
-
result: vi.fn().mockResolvedValue({ result: "success" }),
|
|
127
|
-
query: vi.fn(),
|
|
128
|
-
signal: vi.fn(),
|
|
129
|
-
executeUpdate: vi.fn(),
|
|
130
|
-
terminate: vi.fn(),
|
|
131
|
-
cancel: vi.fn(),
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
mockWorkflow.start.mockResolvedValue(mockHandle);
|
|
135
|
-
|
|
136
|
-
await typedClient.startWorkflow("testWorkflow", {
|
|
137
|
-
workflowId: "test-123",
|
|
138
|
-
args: ["hello", 42],
|
|
139
|
-
workflowExecutionTimeout: "1 day",
|
|
140
|
-
retry: { maximumAttempts: 3 },
|
|
141
|
-
memo: { userId: "user-123" },
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
expect(mockWorkflow.start).toHaveBeenCalledWith("testWorkflow", {
|
|
145
|
-
workflowId: "test-123",
|
|
146
|
-
taskQueue: "test-queue",
|
|
147
|
-
args: [["hello", 42]],
|
|
148
|
-
workflowExecutionTimeout: "1 day",
|
|
149
|
-
retry: { maximumAttempts: 3 },
|
|
150
|
-
memo: { userId: "user-123" },
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe("executeWorkflow", () => {
|
|
156
|
-
it("should execute a workflow with valid input and output", async () => {
|
|
157
|
-
mockWorkflow.execute.mockResolvedValue({ result: "success" });
|
|
158
|
-
|
|
159
|
-
const result = await typedClient.executeWorkflow("testWorkflow", {
|
|
160
|
-
workflowId: "test-123",
|
|
161
|
-
args: ["hello", 42],
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
expect(mockWorkflow.execute).toHaveBeenCalledWith("testWorkflow", {
|
|
165
|
-
workflowId: "test-123",
|
|
166
|
-
taskQueue: "test-queue",
|
|
167
|
-
args: [["hello", 42]],
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
expect(result).toEqual({ result: "success" });
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("should throw WorkflowNotFoundError for unknown workflow", async () => {
|
|
174
|
-
await expect(
|
|
175
|
-
// @ts-expect-error Testing invalid workflow name
|
|
176
|
-
typedClient.executeWorkflow("unknownWorkflow", {
|
|
177
|
-
workflowId: "test-123",
|
|
178
|
-
args: ["test"],
|
|
179
|
-
}),
|
|
180
|
-
).rejects.toThrow(WorkflowNotFoundError);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("should throw WorkflowValidationError for invalid input", async () => {
|
|
184
|
-
await expect(
|
|
185
|
-
typedClient.executeWorkflow("testWorkflow", {
|
|
186
|
-
workflowId: "test-123",
|
|
187
|
-
// @ts-expect-error Testing invalid input
|
|
188
|
-
args: ["hello", "not-a-number"],
|
|
189
|
-
}),
|
|
190
|
-
).rejects.toThrow(WorkflowValidationError);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("should throw WorkflowValidationError for invalid output", async () => {
|
|
194
|
-
mockWorkflow.execute.mockResolvedValue({ wrongField: "value" });
|
|
195
|
-
|
|
196
|
-
await expect(
|
|
197
|
-
typedClient.executeWorkflow("testWorkflow", {
|
|
198
|
-
workflowId: "test-123",
|
|
199
|
-
args: ["hello", 42],
|
|
200
|
-
}),
|
|
201
|
-
).rejects.toThrow(WorkflowValidationError);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it("should pass Temporal options to underlying client", async () => {
|
|
205
|
-
mockWorkflow.execute.mockResolvedValue({ result: "success" });
|
|
206
|
-
|
|
207
|
-
await typedClient.executeWorkflow("testWorkflow", {
|
|
208
|
-
workflowId: "test-123",
|
|
209
|
-
args: ["hello", 42],
|
|
210
|
-
workflowExecutionTimeout: "1 day",
|
|
211
|
-
cronSchedule: "0 0 * * *",
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
expect(mockWorkflow.execute).toHaveBeenCalledWith("testWorkflow", {
|
|
215
|
-
workflowId: "test-123",
|
|
216
|
-
taskQueue: "test-queue",
|
|
217
|
-
args: [["hello", 42]],
|
|
218
|
-
workflowExecutionTimeout: "1 day",
|
|
219
|
-
cronSchedule: "0 0 * * *",
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
describe("getHandle", () => {
|
|
225
|
-
it("should get a handle to an existing workflow", async () => {
|
|
226
|
-
const mockHandle = {
|
|
227
|
-
workflowId: "test-123",
|
|
228
|
-
result: vi.fn().mockResolvedValue({ result: "success" }),
|
|
229
|
-
query: vi.fn(),
|
|
230
|
-
signal: vi.fn(),
|
|
231
|
-
executeUpdate: vi.fn(),
|
|
232
|
-
terminate: vi.fn(),
|
|
233
|
-
cancel: vi.fn(),
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
mockWorkflow.getHandle.mockReturnValue(mockHandle);
|
|
237
|
-
|
|
238
|
-
const handle = await typedClient.getHandle("testWorkflow", "test-123");
|
|
239
|
-
|
|
240
|
-
expect(mockWorkflow.getHandle).toHaveBeenCalledWith("test-123");
|
|
241
|
-
expect(handle.workflowId).toBe("test-123");
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("should throw WorkflowNotFoundError for unknown workflow", async () => {
|
|
245
|
-
await expect(
|
|
246
|
-
// @ts-expect-error Testing invalid workflow name
|
|
247
|
-
typedClient.getHandle("unknownWorkflow", "test-123"),
|
|
248
|
-
).rejects.toThrow(WorkflowNotFoundError);
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
describe("TypedWorkflowHandle", () => {
|
|
253
|
-
let mockHandle: {
|
|
254
|
-
workflowId: string;
|
|
255
|
-
result: ReturnType<typeof vi.fn>;
|
|
256
|
-
query: ReturnType<typeof vi.fn>;
|
|
257
|
-
signal: ReturnType<typeof vi.fn>;
|
|
258
|
-
executeUpdate: ReturnType<typeof vi.fn>;
|
|
259
|
-
terminate: ReturnType<typeof vi.fn>;
|
|
260
|
-
cancel: ReturnType<typeof vi.fn>;
|
|
261
|
-
describe?: ReturnType<typeof vi.fn>;
|
|
262
|
-
fetchHistory?: ReturnType<typeof vi.fn>;
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
beforeEach(async () => {
|
|
266
|
-
mockHandle = {
|
|
267
|
-
workflowId: "test-123",
|
|
268
|
-
result: vi.fn().mockResolvedValue({ result: "success" }),
|
|
269
|
-
query: vi.fn(),
|
|
270
|
-
signal: vi.fn(),
|
|
271
|
-
executeUpdate: vi.fn(),
|
|
272
|
-
terminate: vi.fn(),
|
|
273
|
-
cancel: vi.fn(),
|
|
274
|
-
describe: vi.fn(),
|
|
275
|
-
fetchHistory: vi.fn(),
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
mockWorkflow.start.mockResolvedValue(mockHandle);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
describe("result", () => {
|
|
282
|
-
it("should return validated workflow result", async () => {
|
|
283
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
284
|
-
workflowId: "test-123",
|
|
285
|
-
args: ["hello", 42],
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
const result = await handle.result();
|
|
289
|
-
|
|
290
|
-
expect(mockHandle.result).toHaveBeenCalled();
|
|
291
|
-
expect(result).toEqual({ result: "success" });
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it("should throw WorkflowValidationError for invalid output", async () => {
|
|
295
|
-
mockHandle.result.mockResolvedValue({ wrongField: "value" });
|
|
296
|
-
|
|
297
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
298
|
-
workflowId: "test-123",
|
|
299
|
-
args: ["hello", 42],
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
await expect(handle.result()).rejects.toThrow(WorkflowValidationError);
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
describe("queries", () => {
|
|
307
|
-
it("should execute typed query with valid input and output", async () => {
|
|
308
|
-
mockHandle.query.mockResolvedValue("running");
|
|
309
|
-
|
|
310
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
311
|
-
workflowId: "test-123",
|
|
312
|
-
args: ["hello", 42],
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
const status = await handle.queries.getStatus([]);
|
|
316
|
-
|
|
317
|
-
expect(mockHandle.query).toHaveBeenCalledWith("getStatus", []);
|
|
318
|
-
expect(status).toBe("running");
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it("should throw QueryValidationError for invalid input", async () => {
|
|
322
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
323
|
-
workflowId: "test-123",
|
|
324
|
-
args: ["hello", 42],
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
await expect(
|
|
328
|
-
// @ts-expect-error Testing invalid query input
|
|
329
|
-
handle.queries.getStatus(["invalid"]),
|
|
330
|
-
).rejects.toThrow(QueryValidationError);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it("should throw QueryValidationError for invalid output", async () => {
|
|
334
|
-
mockHandle.query.mockResolvedValue(123);
|
|
335
|
-
|
|
336
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
337
|
-
workflowId: "test-123",
|
|
338
|
-
args: ["hello", 42],
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
await expect(handle.queries.getStatus([])).rejects.toThrow(QueryValidationError);
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
describe("signals", () => {
|
|
346
|
-
it("should send typed signal with valid input", async () => {
|
|
347
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
348
|
-
workflowId: "test-123",
|
|
349
|
-
args: ["hello", 42],
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
await handle.signals.updateProgress([50]);
|
|
353
|
-
|
|
354
|
-
expect(mockHandle.signal).toHaveBeenCalledWith("updateProgress", [50]);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it("should throw SignalValidationError for invalid input", async () => {
|
|
358
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
359
|
-
workflowId: "test-123",
|
|
360
|
-
args: ["hello", 42],
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
await expect(
|
|
364
|
-
// @ts-expect-error Testing invalid signal input
|
|
365
|
-
handle.signals.updateProgress(["not-a-number"]),
|
|
366
|
-
).rejects.toThrow(SignalValidationError);
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
describe("updates", () => {
|
|
371
|
-
it("should execute typed update with valid input and output", async () => {
|
|
372
|
-
mockHandle.executeUpdate.mockResolvedValue(true);
|
|
373
|
-
|
|
374
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
375
|
-
workflowId: "test-123",
|
|
376
|
-
args: ["hello", 42],
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
const result = await handle.updates.setConfig([{ value: "new-config" }]);
|
|
380
|
-
|
|
381
|
-
expect(mockHandle.executeUpdate).toHaveBeenCalledWith("setConfig", {
|
|
382
|
-
args: [[{ value: "new-config" }]],
|
|
383
|
-
});
|
|
384
|
-
expect(result).toBe(true);
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it("should throw UpdateValidationError for invalid input", async () => {
|
|
388
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
389
|
-
workflowId: "test-123",
|
|
390
|
-
args: ["hello", 42],
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
await expect(
|
|
394
|
-
// @ts-expect-error Testing invalid update input
|
|
395
|
-
handle.updates.setConfig([{ wrongField: "value" }]),
|
|
396
|
-
).rejects.toThrow(UpdateValidationError);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
it("should throw UpdateValidationError for invalid output", async () => {
|
|
400
|
-
mockHandle.executeUpdate.mockResolvedValue("not-a-boolean");
|
|
401
|
-
|
|
402
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
403
|
-
workflowId: "test-123",
|
|
404
|
-
args: ["hello", 42],
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
await expect(handle.updates.setConfig([{ value: "new-config" }])).rejects.toThrow(
|
|
408
|
-
UpdateValidationError,
|
|
409
|
-
);
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
describe("terminate", () => {
|
|
414
|
-
it("should terminate workflow", async () => {
|
|
415
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
416
|
-
workflowId: "test-123",
|
|
417
|
-
args: ["hello", 42],
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
await handle.terminate("test reason");
|
|
421
|
-
|
|
422
|
-
expect(mockHandle.terminate).toHaveBeenCalledWith("test reason");
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
it("should terminate workflow without reason", async () => {
|
|
426
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
427
|
-
workflowId: "test-123",
|
|
428
|
-
args: ["hello", 42],
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
await handle.terminate();
|
|
432
|
-
|
|
433
|
-
expect(mockHandle.terminate).toHaveBeenCalledWith(undefined);
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
describe("cancel", () => {
|
|
438
|
-
it("should cancel workflow", async () => {
|
|
439
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
440
|
-
workflowId: "test-123",
|
|
441
|
-
args: ["hello", 42],
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
await handle.cancel();
|
|
445
|
-
|
|
446
|
-
expect(mockHandle.cancel).toHaveBeenCalled();
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
describe("describe", () => {
|
|
451
|
-
it("should call describe on underlying handle", async () => {
|
|
452
|
-
const mockDescription = {
|
|
453
|
-
workflowExecutionInfo: {
|
|
454
|
-
workflowExecution: { workflowId: "test-123", runId: "run-123" },
|
|
455
|
-
type: { name: "testWorkflow" },
|
|
456
|
-
startTime: new Date(),
|
|
457
|
-
status: "RUNNING" as const,
|
|
458
|
-
},
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
mockHandle.describe = vi.fn().mockResolvedValue(mockDescription);
|
|
462
|
-
|
|
463
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
464
|
-
workflowId: "test-123",
|
|
465
|
-
args: ["hello", 42],
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
const description = await handle.describe();
|
|
469
|
-
|
|
470
|
-
expect(mockHandle.describe).toHaveBeenCalled();
|
|
471
|
-
expect(description).toEqual(mockDescription);
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
describe("fetchHistory", () => {
|
|
476
|
-
it("should call fetchHistory on underlying handle", async () => {
|
|
477
|
-
const mockHistoryIterator = (async function* () {
|
|
478
|
-
yield { eventId: 1n, eventType: "WorkflowExecutionStarted" };
|
|
479
|
-
yield { eventId: 2n, eventType: "WorkflowTaskScheduled" };
|
|
480
|
-
})();
|
|
481
|
-
|
|
482
|
-
mockHandle.fetchHistory = vi.fn().mockReturnValue(mockHistoryIterator);
|
|
483
|
-
|
|
484
|
-
const handle = await typedClient.startWorkflow("testWorkflow", {
|
|
485
|
-
workflowId: "test-123",
|
|
486
|
-
args: ["hello", 42],
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
const history = handle.fetchHistory();
|
|
490
|
-
|
|
491
|
-
expect(mockHandle.fetchHistory).toHaveBeenCalled();
|
|
492
|
-
expect(history).toBeDefined();
|
|
493
|
-
});
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
describe("edge cases", () => {
|
|
498
|
-
it("should handle workflow with no queries", async () => {
|
|
499
|
-
const mockHandle = {
|
|
500
|
-
workflowId: "simple-123",
|
|
501
|
-
result: vi.fn().mockResolvedValue("done"),
|
|
502
|
-
query: vi.fn(),
|
|
503
|
-
signal: vi.fn(),
|
|
504
|
-
executeUpdate: vi.fn(),
|
|
505
|
-
terminate: vi.fn(),
|
|
506
|
-
cancel: vi.fn(),
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
mockWorkflow.start.mockResolvedValue(mockHandle);
|
|
510
|
-
|
|
511
|
-
const handle = await typedClient.startWorkflow("simpleWorkflow", {
|
|
512
|
-
workflowId: "simple-123",
|
|
513
|
-
args: ["test"],
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
expect(handle.queries).toBeDefined();
|
|
517
|
-
expect(Object.keys(handle.queries)).toHaveLength(0);
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
it("should handle workflow with no signals", async () => {
|
|
521
|
-
const mockHandle = {
|
|
522
|
-
workflowId: "simple-123",
|
|
523
|
-
result: vi.fn().mockResolvedValue("done"),
|
|
524
|
-
query: vi.fn(),
|
|
525
|
-
signal: vi.fn(),
|
|
526
|
-
executeUpdate: vi.fn(),
|
|
527
|
-
terminate: vi.fn(),
|
|
528
|
-
cancel: vi.fn(),
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
mockWorkflow.start.mockResolvedValue(mockHandle);
|
|
532
|
-
|
|
533
|
-
const handle = await typedClient.startWorkflow("simpleWorkflow", {
|
|
534
|
-
workflowId: "simple-123",
|
|
535
|
-
args: ["test"],
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
expect(handle.signals).toBeDefined();
|
|
539
|
-
expect(Object.keys(handle.signals)).toHaveLength(0);
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
it("should handle workflow with no updates", async () => {
|
|
543
|
-
const mockHandle = {
|
|
544
|
-
workflowId: "simple-123",
|
|
545
|
-
result: vi.fn().mockResolvedValue("done"),
|
|
546
|
-
query: vi.fn(),
|
|
547
|
-
signal: vi.fn(),
|
|
548
|
-
executeUpdate: vi.fn(),
|
|
549
|
-
terminate: vi.fn(),
|
|
550
|
-
cancel: vi.fn(),
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
mockWorkflow.start.mockResolvedValue(mockHandle);
|
|
554
|
-
|
|
555
|
-
const handle = await typedClient.startWorkflow("simpleWorkflow", {
|
|
556
|
-
workflowId: "simple-123",
|
|
557
|
-
args: ["test"],
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
expect(handle.updates).toBeDefined();
|
|
561
|
-
expect(Object.keys(handle.updates)).toHaveLength(0);
|
|
562
|
-
});
|
|
563
|
-
});
|
|
564
|
-
});
|
package/src/client.ts
DELETED
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
import { Client, WorkflowHandle } from "@temporalio/client";
|
|
2
|
-
import type { ClientOptions, WorkflowStartOptions, WorkflowOptions } from "@temporalio/client";
|
|
3
|
-
import type {
|
|
4
|
-
ClientInferInput,
|
|
5
|
-
ClientInferOutput,
|
|
6
|
-
ClientInferWorkflowQueries,
|
|
7
|
-
ClientInferWorkflowSignals,
|
|
8
|
-
ClientInferWorkflowUpdates,
|
|
9
|
-
ContractDefinition,
|
|
10
|
-
WorkflowDefinition,
|
|
11
|
-
QueryDefinition,
|
|
12
|
-
SignalDefinition,
|
|
13
|
-
UpdateDefinition,
|
|
14
|
-
} from "@temporal-contract/contract";
|
|
15
|
-
import {
|
|
16
|
-
WorkflowNotFoundError,
|
|
17
|
-
WorkflowValidationError,
|
|
18
|
-
QueryValidationError,
|
|
19
|
-
SignalValidationError,
|
|
20
|
-
UpdateValidationError,
|
|
21
|
-
} from "./errors.js";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Extended options for starting workflows with Temporal-specific features
|
|
25
|
-
* Combines required workflowId with optional Temporal workflow options
|
|
26
|
-
*/
|
|
27
|
-
export type TypedWorkflowStartOptions = Pick<
|
|
28
|
-
WorkflowStartOptions,
|
|
29
|
-
| "workflowId"
|
|
30
|
-
| "workflowIdReusePolicy"
|
|
31
|
-
| "workflowExecutionTimeout"
|
|
32
|
-
| "workflowRunTimeout"
|
|
33
|
-
| "workflowTaskTimeout"
|
|
34
|
-
| "retry"
|
|
35
|
-
| "memo"
|
|
36
|
-
| "searchAttributes"
|
|
37
|
-
| "cronSchedule"
|
|
38
|
-
> &
|
|
39
|
-
Pick<WorkflowOptions, "workflowId">;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Typed workflow handle with validated results, queries, signals and updates
|
|
43
|
-
*/
|
|
44
|
-
export interface TypedWorkflowHandle<TWorkflow extends WorkflowDefinition> {
|
|
45
|
-
workflowId: string;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Type-safe queries based on workflow definition
|
|
49
|
-
*/
|
|
50
|
-
queries: ClientInferWorkflowQueries<TWorkflow>;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Type-safe signals based on workflow definition
|
|
54
|
-
*/
|
|
55
|
-
signals: ClientInferWorkflowSignals<TWorkflow>;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Type-safe updates based on workflow definition
|
|
59
|
-
*/
|
|
60
|
-
updates: ClientInferWorkflowUpdates<TWorkflow>;
|
|
61
|
-
|
|
62
|
-
result: () => Promise<ClientInferOutput<TWorkflow>>;
|
|
63
|
-
terminate: (reason?: string) => Promise<void>;
|
|
64
|
-
cancel: () => Promise<void>;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get workflow execution description including status and metadata
|
|
68
|
-
*
|
|
69
|
-
* @example
|
|
70
|
-
* ```ts
|
|
71
|
-
* const handle = await client.getHandle('processOrder', 'order-123');
|
|
72
|
-
* const description = await handle.describe();
|
|
73
|
-
* console.log(description.workflowExecutionInfo.status); // RUNNING, COMPLETED, etc.
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
describe: () => ReturnType<WorkflowHandle["describe"]>;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Fetch the workflow execution history
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```ts
|
|
83
|
-
* const handle = await client.getHandle('processOrder', 'order-123');
|
|
84
|
-
* const history = handle.fetchHistory();
|
|
85
|
-
* for await (const event of history) {
|
|
86
|
-
* console.log(event);
|
|
87
|
-
* }
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
fetchHistory: () => ReturnType<WorkflowHandle["fetchHistory"]>;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Typed Temporal client based on a contract
|
|
95
|
-
*
|
|
96
|
-
* Provides type-safe methods to start and execute workflows
|
|
97
|
-
* defined in the contract.
|
|
98
|
-
*/
|
|
99
|
-
export class TypedClient<TContract extends ContractDefinition> {
|
|
100
|
-
private constructor(
|
|
101
|
-
private readonly contract: TContract,
|
|
102
|
-
private readonly client: Client,
|
|
103
|
-
) {}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Create a typed Temporal client from a contract
|
|
107
|
-
*
|
|
108
|
-
* @example
|
|
109
|
-
* ```ts
|
|
110
|
-
* const connection = await Connection.connect();
|
|
111
|
-
* const client = TypedClient.create(myContract, {
|
|
112
|
-
* connection,
|
|
113
|
-
* namespace: 'default',
|
|
114
|
-
* });
|
|
115
|
-
*
|
|
116
|
-
* const result = await client.executeWorkflow('processOrder', {
|
|
117
|
-
* workflowId: 'order-123',
|
|
118
|
-
* args: [...],
|
|
119
|
-
* });
|
|
120
|
-
* ```
|
|
121
|
-
*/
|
|
122
|
-
static create<TContract extends ContractDefinition>(
|
|
123
|
-
contract: TContract,
|
|
124
|
-
options: ClientOptions,
|
|
125
|
-
): TypedClient<TContract> {
|
|
126
|
-
const client = new Client(options);
|
|
127
|
-
return new TypedClient(contract, client);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Start a workflow and return a typed handle
|
|
132
|
-
*
|
|
133
|
-
* @example
|
|
134
|
-
* ```ts
|
|
135
|
-
* const handle = await client.startWorkflow('processOrder', {
|
|
136
|
-
* workflowId: 'order-123',
|
|
137
|
-
* args: ['ORD-123', 'CUST-456', [{ productId: 'PROD-1', quantity: 2 }]],
|
|
138
|
-
* workflowExecutionTimeout: '1 day',
|
|
139
|
-
* retry: { maximumAttempts: 3 },
|
|
140
|
-
* });
|
|
141
|
-
*
|
|
142
|
-
* const result = await handle.result();
|
|
143
|
-
* ```
|
|
144
|
-
*/
|
|
145
|
-
async startWorkflow<TWorkflowName extends keyof TContract["workflows"]>(
|
|
146
|
-
workflowName: TWorkflowName,
|
|
147
|
-
{
|
|
148
|
-
args,
|
|
149
|
-
...temporalOptions
|
|
150
|
-
}: TypedWorkflowStartOptions & {
|
|
151
|
-
args: ClientInferInput<TContract["workflows"][TWorkflowName]>;
|
|
152
|
-
},
|
|
153
|
-
): Promise<TypedWorkflowHandle<TContract["workflows"][TWorkflowName]>> {
|
|
154
|
-
const definition = this.contract.workflows[workflowName as string];
|
|
155
|
-
|
|
156
|
-
if (!definition) {
|
|
157
|
-
throw new WorkflowNotFoundError(
|
|
158
|
-
String(workflowName),
|
|
159
|
-
Object.keys(this.contract.workflows) as string[],
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Validate input with Standard Schema
|
|
164
|
-
const inputResult = await definition.input["~standard"].validate(args);
|
|
165
|
-
if (inputResult.issues) {
|
|
166
|
-
throw new WorkflowValidationError(String(workflowName), "input", inputResult.issues);
|
|
167
|
-
}
|
|
168
|
-
const validatedInput = inputResult.value as ClientInferInput<
|
|
169
|
-
TContract["workflows"][TWorkflowName]
|
|
170
|
-
>;
|
|
171
|
-
|
|
172
|
-
// Start workflow (Temporal expects args as array, so wrap single parameter)
|
|
173
|
-
const handle = await this.client.workflow.start(workflowName as string, {
|
|
174
|
-
...temporalOptions,
|
|
175
|
-
taskQueue: this.contract.taskQueue,
|
|
176
|
-
args: [validatedInput],
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
return this.createTypedHandle(handle, definition) as TypedWorkflowHandle<
|
|
180
|
-
TContract["workflows"][TWorkflowName]
|
|
181
|
-
>;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Execute a workflow (start and wait for result)
|
|
186
|
-
*
|
|
187
|
-
* @example
|
|
188
|
-
* ```ts
|
|
189
|
-
* const result = await client.executeWorkflow('processOrder', {
|
|
190
|
-
* workflowId: 'order-123',
|
|
191
|
-
* args: ['ORD-123', 'CUST-456', [{ productId: 'PROD-1', quantity: 2 }]],
|
|
192
|
-
* workflowExecutionTimeout: '1 day',
|
|
193
|
-
* retry: { maximumAttempts: 3 },
|
|
194
|
-
* });
|
|
195
|
-
*
|
|
196
|
-
* console.log(result.status); // fully typed!
|
|
197
|
-
* ```
|
|
198
|
-
*/
|
|
199
|
-
async executeWorkflow<TWorkflowName extends keyof TContract["workflows"]>(
|
|
200
|
-
workflowName: TWorkflowName,
|
|
201
|
-
{
|
|
202
|
-
args,
|
|
203
|
-
...temporalOptions
|
|
204
|
-
}: TypedWorkflowStartOptions & {
|
|
205
|
-
args: ClientInferInput<TContract["workflows"][TWorkflowName]>;
|
|
206
|
-
},
|
|
207
|
-
): Promise<ClientInferOutput<TContract["workflows"][TWorkflowName]>> {
|
|
208
|
-
const definition = this.contract.workflows[workflowName as string];
|
|
209
|
-
|
|
210
|
-
if (!definition) {
|
|
211
|
-
throw new WorkflowNotFoundError(
|
|
212
|
-
String(workflowName),
|
|
213
|
-
Object.keys(this.contract.workflows) as string[],
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Validate input with Standard Schema
|
|
218
|
-
const inputResult = await definition.input["~standard"].validate(args);
|
|
219
|
-
if (inputResult.issues) {
|
|
220
|
-
throw new WorkflowValidationError(String(workflowName), "input", inputResult.issues);
|
|
221
|
-
}
|
|
222
|
-
const validatedInput = inputResult.value as ClientInferInput<
|
|
223
|
-
TContract["workflows"][TWorkflowName]
|
|
224
|
-
>;
|
|
225
|
-
|
|
226
|
-
// Execute workflow (Temporal expects args as array, so wrap single parameter)
|
|
227
|
-
const result = await this.client.workflow.execute(workflowName as string, {
|
|
228
|
-
...temporalOptions,
|
|
229
|
-
taskQueue: this.contract.taskQueue,
|
|
230
|
-
args: [validatedInput],
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// Validate output with Standard Schema
|
|
234
|
-
const outputResult = await definition.output["~standard"].validate(result);
|
|
235
|
-
if (outputResult.issues) {
|
|
236
|
-
throw new WorkflowValidationError(String(workflowName), "output", outputResult.issues);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return outputResult.value as ClientInferOutput<TContract["workflows"][TWorkflowName]>;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Get a handle to an existing workflow
|
|
244
|
-
*
|
|
245
|
-
* @example
|
|
246
|
-
* ```ts
|
|
247
|
-
* const handle = await client.getHandle('processOrder', 'order-123');
|
|
248
|
-
* const result = await handle.result();
|
|
249
|
-
* ```
|
|
250
|
-
*/
|
|
251
|
-
async getHandle<TWorkflowName extends keyof TContract["workflows"]>(
|
|
252
|
-
workflowName: TWorkflowName,
|
|
253
|
-
workflowId: string,
|
|
254
|
-
): Promise<TypedWorkflowHandle<TContract["workflows"][TWorkflowName]>> {
|
|
255
|
-
const definition = this.contract.workflows[workflowName as string];
|
|
256
|
-
|
|
257
|
-
if (!definition) {
|
|
258
|
-
throw new WorkflowNotFoundError(
|
|
259
|
-
String(workflowName),
|
|
260
|
-
Object.keys(this.contract.workflows) as string[],
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const handle = this.client.workflow.getHandle(workflowId);
|
|
265
|
-
return this.createTypedHandle(handle, definition) as TypedWorkflowHandle<
|
|
266
|
-
TContract["workflows"][TWorkflowName]
|
|
267
|
-
>;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private createTypedHandle<TWorkflow extends WorkflowDefinition>(
|
|
271
|
-
handle: WorkflowHandle,
|
|
272
|
-
definition: TWorkflow,
|
|
273
|
-
): TypedWorkflowHandle<TWorkflow> {
|
|
274
|
-
// Create typed queries proxy
|
|
275
|
-
const queries = {} as ClientInferWorkflowQueries<TWorkflow>;
|
|
276
|
-
for (const [queryName, queryDef] of Object.entries(definition.queries ?? {}) as Array<
|
|
277
|
-
[string, QueryDefinition]
|
|
278
|
-
>) {
|
|
279
|
-
(queries as Record<string, unknown>)[queryName] = async (
|
|
280
|
-
args: ClientInferInput<typeof queryDef>,
|
|
281
|
-
) => {
|
|
282
|
-
const inputResult = await queryDef.input["~standard"].validate(args);
|
|
283
|
-
if (inputResult.issues) {
|
|
284
|
-
throw new QueryValidationError(queryName, "input", inputResult.issues);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const result = await handle.query(queryName as string, inputResult.value);
|
|
288
|
-
|
|
289
|
-
const outputResult = await queryDef.output["~standard"].validate(result);
|
|
290
|
-
if (outputResult.issues) {
|
|
291
|
-
throw new QueryValidationError(queryName, "output", outputResult.issues);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return outputResult.value;
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Create typed signals proxy
|
|
299
|
-
const signals = {} as ClientInferWorkflowSignals<TWorkflow>;
|
|
300
|
-
for (const [signalName, signalDef] of Object.entries(definition.signals ?? {}) as Array<
|
|
301
|
-
[string, SignalDefinition]
|
|
302
|
-
>) {
|
|
303
|
-
(signals as Record<string, unknown>)[signalName] = async (
|
|
304
|
-
args: ClientInferInput<typeof signalDef>,
|
|
305
|
-
) => {
|
|
306
|
-
const inputResult = await signalDef.input["~standard"].validate(args);
|
|
307
|
-
if (inputResult.issues) {
|
|
308
|
-
throw new SignalValidationError(signalName, inputResult.issues);
|
|
309
|
-
}
|
|
310
|
-
await handle.signal(signalName as string, inputResult.value);
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Create typed updates proxy
|
|
315
|
-
const updates = {} as ClientInferWorkflowUpdates<TWorkflow>;
|
|
316
|
-
for (const [updateName, updateDef] of Object.entries(definition.updates ?? {}) as Array<
|
|
317
|
-
[string, UpdateDefinition]
|
|
318
|
-
>) {
|
|
319
|
-
(updates as Record<string, unknown>)[updateName] = async (
|
|
320
|
-
args: ClientInferInput<typeof updateDef>,
|
|
321
|
-
) => {
|
|
322
|
-
const inputResult = await updateDef.input["~standard"].validate(args);
|
|
323
|
-
if (inputResult.issues) {
|
|
324
|
-
throw new UpdateValidationError(updateName, "input", inputResult.issues);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const result = await handle.executeUpdate(updateName as string, {
|
|
328
|
-
args: [inputResult.value],
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
const outputResult = await updateDef.output["~standard"].validate(result);
|
|
332
|
-
if (outputResult.issues) {
|
|
333
|
-
throw new UpdateValidationError(updateName, "output", outputResult.issues);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return outputResult.value;
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const typedHandle: TypedWorkflowHandle<TWorkflow> = {
|
|
341
|
-
workflowId: handle.workflowId,
|
|
342
|
-
queries,
|
|
343
|
-
signals,
|
|
344
|
-
updates,
|
|
345
|
-
result: async () => {
|
|
346
|
-
const result = await handle.result();
|
|
347
|
-
// Validate output with Standard Schema
|
|
348
|
-
const outputResult = await definition.output["~standard"].validate(result);
|
|
349
|
-
if (outputResult.issues) {
|
|
350
|
-
throw new WorkflowValidationError(handle.workflowId, "output", outputResult.issues);
|
|
351
|
-
}
|
|
352
|
-
return outputResult.value as ClientInferOutput<TWorkflow>;
|
|
353
|
-
},
|
|
354
|
-
terminate: async (reason?: string) => {
|
|
355
|
-
await handle.terminate(reason);
|
|
356
|
-
},
|
|
357
|
-
cancel: async () => {
|
|
358
|
-
await handle.cancel();
|
|
359
|
-
},
|
|
360
|
-
describe: () => handle.describe(),
|
|
361
|
-
fetchHistory: () => handle.fetchHistory(),
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
return typedHandle;
|
|
365
|
-
}
|
|
366
|
-
}
|
package/src/errors.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Base error class for typed client errors
|
|
5
|
-
*/
|
|
6
|
-
export class TypedClientError extends Error {
|
|
7
|
-
constructor(message: string) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.name = "TypedClientError";
|
|
10
|
-
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
11
|
-
if (Error.captureStackTrace) {
|
|
12
|
-
Error.captureStackTrace(this, this.constructor);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Error thrown when a workflow is not found in the contract
|
|
19
|
-
*/
|
|
20
|
-
export class WorkflowNotFoundError extends TypedClientError {
|
|
21
|
-
constructor(
|
|
22
|
-
public readonly workflowName: string,
|
|
23
|
-
public readonly availableWorkflows: readonly string[] = [],
|
|
24
|
-
) {
|
|
25
|
-
const message =
|
|
26
|
-
availableWorkflows.length > 0
|
|
27
|
-
? `Workflow "${workflowName}" not found in contract. Available workflows: ${availableWorkflows.join(", ")}`
|
|
28
|
-
: `Workflow "${workflowName}" not found in contract`;
|
|
29
|
-
super(message);
|
|
30
|
-
this.name = "WorkflowNotFoundError";
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Error thrown when workflow input or output validation fails
|
|
36
|
-
*/
|
|
37
|
-
export class WorkflowValidationError extends TypedClientError {
|
|
38
|
-
constructor(
|
|
39
|
-
public readonly workflowName: string,
|
|
40
|
-
public readonly phase: "input" | "output",
|
|
41
|
-
public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
|
|
42
|
-
) {
|
|
43
|
-
const message = issues.map((issue) => issue.message).join("; ");
|
|
44
|
-
super(`Validation failed for workflow "${workflowName}" ${phase}: ${message}`);
|
|
45
|
-
this.name = "WorkflowValidationError";
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Error thrown when query input or output validation fails
|
|
51
|
-
*/
|
|
52
|
-
export class QueryValidationError extends TypedClientError {
|
|
53
|
-
constructor(
|
|
54
|
-
public readonly queryName: string,
|
|
55
|
-
public readonly phase: "input" | "output",
|
|
56
|
-
public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
|
|
57
|
-
) {
|
|
58
|
-
const message = issues.map((issue) => issue.message).join("; ");
|
|
59
|
-
super(`Validation failed for query "${queryName}" ${phase}: ${message}`);
|
|
60
|
-
this.name = "QueryValidationError";
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Error thrown when signal input validation fails
|
|
66
|
-
*/
|
|
67
|
-
export class SignalValidationError extends TypedClientError {
|
|
68
|
-
constructor(
|
|
69
|
-
public readonly signalName: string,
|
|
70
|
-
public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
|
|
71
|
-
) {
|
|
72
|
-
const message = issues.map((issue) => issue.message).join("; ");
|
|
73
|
-
super(`Validation failed for signal "${signalName}" input: ${message}`);
|
|
74
|
-
this.name = "SignalValidationError";
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Error thrown when update input or output validation fails
|
|
80
|
-
*/
|
|
81
|
-
export class UpdateValidationError extends TypedClientError {
|
|
82
|
-
constructor(
|
|
83
|
-
public readonly updateName: string,
|
|
84
|
-
public readonly phase: "input" | "output",
|
|
85
|
-
public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
|
|
86
|
-
) {
|
|
87
|
-
const message = issues.map((issue) => issue.message).join("; ");
|
|
88
|
-
super(`Validation failed for update "${updateName}" ${phase}: ${message}`);
|
|
89
|
-
this.name = "UpdateValidationError";
|
|
90
|
-
}
|
|
91
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { TypedClient, type TypedWorkflowHandle, type TypedWorkflowStartOptions } from "./client.js";
|
|
2
|
-
export {
|
|
3
|
-
TypedClientError,
|
|
4
|
-
WorkflowNotFoundError,
|
|
5
|
-
WorkflowValidationError,
|
|
6
|
-
QueryValidationError,
|
|
7
|
-
SignalValidationError,
|
|
8
|
-
UpdateValidationError,
|
|
9
|
-
} from "./errors.js";
|
package/tsconfig.json
DELETED
package/vitest.config.ts
DELETED