@temporal-contract/client 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +9 -0
- package/README.md +11 -70
- package/dist/index.cjs +50 -80
- package/dist/index.d.cts +11 -10
- package/dist/index.d.mts +11 -10
- package/dist/index.mjs +50 -80
- package/package.json +6 -6
- package/src/client.ts +61 -85
- package/src/errors.ts +22 -11
- package/vitest.config.ts +12 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
> @temporal-contract/client@0.0.
|
|
2
|
+
> @temporal-contract/client@0.0.2 build /home/runner/work/temporal-contract/temporal-contract/packages/client
|
|
3
3
|
> tsdown src/index.ts --format cjs,esm --dts --clean
|
|
4
4
|
|
|
5
5
|
[34mℹ[39m tsdown [2mv0.17.2[22m powered by rolldown [2mv1.0.0-beta.53[22m
|
|
6
6
|
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
7
7
|
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
8
8
|
[34mℹ[39m Build start
|
|
9
|
-
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[1mindex.cjs[22m [2m8.
|
|
10
|
-
[34mℹ[39m [33m[CJS][39m 1 files, total: 8.
|
|
11
|
-
[34mℹ[39m [
|
|
12
|
-
[34mℹ[39m [
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[34mℹ[39m [
|
|
16
|
-
[34mℹ[39m [
|
|
17
|
-
[32m✔[39m Build complete in [
|
|
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
ADDED
package/README.md
CHANGED
|
@@ -2,96 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
> Type-safe client for consuming Temporal workflows
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/@temporal-contract/client)
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
pnpm add @temporal-contract/client @temporal-contract/contract @temporalio/client zod
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
## Quick
|
|
13
|
+
## Quick Example
|
|
12
14
|
|
|
13
15
|
```typescript
|
|
14
|
-
import { Connection } from '@temporalio/client';
|
|
15
16
|
import { TypedClient } from '@temporal-contract/client';
|
|
16
|
-
import {
|
|
17
|
+
import { Connection } from '@temporalio/client';
|
|
17
18
|
|
|
18
|
-
// Connect to Temporal
|
|
19
19
|
const connection = await Connection.connect({ address: 'localhost:7233' });
|
|
20
|
-
|
|
21
|
-
// Create typed client
|
|
22
|
-
const client = TypedClient.create(myContract, {
|
|
23
|
-
connection,
|
|
24
|
-
namespace: 'default',
|
|
25
|
-
});
|
|
20
|
+
const client = TypedClient.create(myContract, { connection });
|
|
26
21
|
|
|
27
22
|
// Execute workflow (fully typed!)
|
|
28
23
|
const result = await client.executeWorkflow('processOrder', {
|
|
29
24
|
workflowId: 'order-123',
|
|
30
|
-
args: { orderId: 'ORD-123'
|
|
25
|
+
args: { orderId: 'ORD-123' }
|
|
31
26
|
});
|
|
32
|
-
|
|
33
|
-
console.log(result.status); // 'success' | 'failed' — typed!
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## API
|
|
37
|
-
|
|
38
|
-
### `TypedClient.create(contract, options)`
|
|
39
|
-
|
|
40
|
-
Creates a type-safe Temporal client.
|
|
41
|
-
|
|
42
|
-
**Returns:** Type-safe client with these methods:
|
|
43
|
-
|
|
44
|
-
#### `executeWorkflow(name, options)`
|
|
45
|
-
|
|
46
|
-
Execute and wait for result:
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
const result = await client.executeWorkflow('processOrder', {
|
|
50
|
-
workflowId: 'order-123',
|
|
51
|
-
args: { orderId: 'ORD-123', customerId: 'CUST-456' },
|
|
52
|
-
});
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
#### `startWorkflow(name, options)` / `getHandle(name, workflowId)`
|
|
56
|
-
|
|
57
|
-
Get a typed workflow handle for signals/queries/updates:
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
const handle = await client.startWorkflow('processOrder', {
|
|
61
|
-
workflowId: 'order-456',
|
|
62
|
-
args: { orderId: 'ORD-456' },
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// Type-safe queries, signals, updates
|
|
66
|
-
await handle.queries.getStatus();
|
|
67
|
-
await handle.signals.cancel({ reason: 'Customer request' });
|
|
68
|
-
await handle.updates.changeAmount({ newAmount: 150 });
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Error Handling
|
|
72
|
-
|
|
73
|
-
Custom error classes with contextual information:
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { WorkflowValidationError, WorkflowNotFoundError } from '@temporal-contract/client';
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
await client.executeWorkflow('processOrder', { args: invalidData });
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (error instanceof WorkflowValidationError) {
|
|
82
|
-
console.error('Validation:', error.zodError.errors);
|
|
83
|
-
} else if (error instanceof WorkflowNotFoundError) {
|
|
84
|
-
console.error('Available:', error.availableWorkflows);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
27
|
```
|
|
88
28
|
|
|
89
|
-
|
|
29
|
+
## Documentation
|
|
90
30
|
|
|
91
|
-
|
|
31
|
+
📖 **[Read the full documentation →](https://btravers.github.io/temporal-contract)**
|
|
92
32
|
|
|
93
|
-
- [
|
|
94
|
-
- [
|
|
33
|
+
- [API Reference](https://btravers.github.io/temporal-contract/api/client)
|
|
34
|
+
- [Getting Started](https://btravers.github.io/temporal-contract/guide/getting-started)
|
|
35
|
+
- [Examples](https://btravers.github.io/temporal-contract/examples/)
|
|
95
36
|
|
|
96
37
|
## License
|
|
97
38
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
let __temporalio_client = require("@temporalio/client");
|
|
2
|
-
let zod = require("zod");
|
|
3
2
|
|
|
4
3
|
//#region src/errors.ts
|
|
5
4
|
/**
|
|
@@ -16,9 +15,11 @@ var TypedClientError = class extends Error {
|
|
|
16
15
|
* Error thrown when a workflow is not found in the contract
|
|
17
16
|
*/
|
|
18
17
|
var WorkflowNotFoundError = class extends TypedClientError {
|
|
19
|
-
constructor(workflowName) {
|
|
20
|
-
|
|
18
|
+
constructor(workflowName, availableWorkflows = []) {
|
|
19
|
+
const message = availableWorkflows.length > 0 ? `Workflow "${workflowName}" not found in contract. Available workflows: ${availableWorkflows.join(", ")}` : `Workflow "${workflowName}" not found in contract`;
|
|
20
|
+
super(message);
|
|
21
21
|
this.workflowName = workflowName;
|
|
22
|
+
this.availableWorkflows = availableWorkflows;
|
|
22
23
|
this.name = "WorkflowNotFoundError";
|
|
23
24
|
}
|
|
24
25
|
};
|
|
@@ -26,11 +27,12 @@ var WorkflowNotFoundError = class extends TypedClientError {
|
|
|
26
27
|
* Error thrown when workflow input or output validation fails
|
|
27
28
|
*/
|
|
28
29
|
var WorkflowValidationError = class extends TypedClientError {
|
|
29
|
-
constructor(workflowName, phase,
|
|
30
|
-
|
|
30
|
+
constructor(workflowName, phase, issues) {
|
|
31
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
32
|
+
super(`Validation failed for workflow "${workflowName}" ${phase}: ${message}`);
|
|
31
33
|
this.workflowName = workflowName;
|
|
32
34
|
this.phase = phase;
|
|
33
|
-
this.
|
|
35
|
+
this.issues = issues;
|
|
34
36
|
this.name = "WorkflowValidationError";
|
|
35
37
|
}
|
|
36
38
|
};
|
|
@@ -38,11 +40,12 @@ var WorkflowValidationError = class extends TypedClientError {
|
|
|
38
40
|
* Error thrown when query input or output validation fails
|
|
39
41
|
*/
|
|
40
42
|
var QueryValidationError = class extends TypedClientError {
|
|
41
|
-
constructor(queryName, phase,
|
|
42
|
-
|
|
43
|
+
constructor(queryName, phase, issues) {
|
|
44
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
45
|
+
super(`Validation failed for query "${queryName}" ${phase}: ${message}`);
|
|
43
46
|
this.queryName = queryName;
|
|
44
47
|
this.phase = phase;
|
|
45
|
-
this.
|
|
48
|
+
this.issues = issues;
|
|
46
49
|
this.name = "QueryValidationError";
|
|
47
50
|
}
|
|
48
51
|
};
|
|
@@ -50,10 +53,11 @@ var QueryValidationError = class extends TypedClientError {
|
|
|
50
53
|
* Error thrown when signal input validation fails
|
|
51
54
|
*/
|
|
52
55
|
var SignalValidationError = class extends TypedClientError {
|
|
53
|
-
constructor(signalName,
|
|
54
|
-
|
|
56
|
+
constructor(signalName, issues) {
|
|
57
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
58
|
+
super(`Validation failed for signal "${signalName}" input: ${message}`);
|
|
55
59
|
this.signalName = signalName;
|
|
56
|
-
this.
|
|
60
|
+
this.issues = issues;
|
|
57
61
|
this.name = "SignalValidationError";
|
|
58
62
|
}
|
|
59
63
|
};
|
|
@@ -61,11 +65,12 @@ var SignalValidationError = class extends TypedClientError {
|
|
|
61
65
|
* Error thrown when update input or output validation fails
|
|
62
66
|
*/
|
|
63
67
|
var UpdateValidationError = class extends TypedClientError {
|
|
64
|
-
constructor(updateName, phase,
|
|
65
|
-
|
|
68
|
+
constructor(updateName, phase, issues) {
|
|
69
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
70
|
+
super(`Validation failed for update "${updateName}" ${phase}: ${message}`);
|
|
66
71
|
this.updateName = updateName;
|
|
67
72
|
this.phase = phase;
|
|
68
|
-
this.
|
|
73
|
+
this.issues = issues;
|
|
69
74
|
this.name = "UpdateValidationError";
|
|
70
75
|
}
|
|
71
76
|
};
|
|
@@ -120,14 +125,10 @@ var TypedClient = class TypedClient {
|
|
|
120
125
|
*/
|
|
121
126
|
async startWorkflow(workflowName, { args, ...temporalOptions }) {
|
|
122
127
|
const definition = this.contract.workflows[workflowName];
|
|
123
|
-
if (!definition) throw new WorkflowNotFoundError(String(workflowName));
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
} catch (error) {
|
|
128
|
-
if (error instanceof zod.ZodError) throw new WorkflowValidationError(String(workflowName), "input", error);
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
128
|
+
if (!definition) throw new WorkflowNotFoundError(String(workflowName), Object.keys(this.contract.workflows));
|
|
129
|
+
const inputResult = await definition.input["~standard"].validate(args);
|
|
130
|
+
if (inputResult.issues) throw new WorkflowValidationError(String(workflowName), "input", inputResult.issues);
|
|
131
|
+
const validatedInput = inputResult.value;
|
|
131
132
|
const handle = await this.client.workflow.start(workflowName, {
|
|
132
133
|
...temporalOptions,
|
|
133
134
|
taskQueue: this.contract.taskQueue,
|
|
@@ -152,25 +153,18 @@ var TypedClient = class TypedClient {
|
|
|
152
153
|
*/
|
|
153
154
|
async executeWorkflow(workflowName, { args, ...temporalOptions }) {
|
|
154
155
|
const definition = this.contract.workflows[workflowName];
|
|
155
|
-
if (!definition) throw new WorkflowNotFoundError(String(workflowName));
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} catch (error) {
|
|
160
|
-
if (error instanceof zod.ZodError) throw new WorkflowValidationError(String(workflowName), "input", error);
|
|
161
|
-
throw error;
|
|
162
|
-
}
|
|
156
|
+
if (!definition) throw new WorkflowNotFoundError(String(workflowName), Object.keys(this.contract.workflows));
|
|
157
|
+
const inputResult = await definition.input["~standard"].validate(args);
|
|
158
|
+
if (inputResult.issues) throw new WorkflowValidationError(String(workflowName), "input", inputResult.issues);
|
|
159
|
+
const validatedInput = inputResult.value;
|
|
163
160
|
const result = await this.client.workflow.execute(workflowName, {
|
|
164
161
|
...temporalOptions,
|
|
165
162
|
taskQueue: this.contract.taskQueue,
|
|
166
163
|
args: [validatedInput]
|
|
167
164
|
});
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (error instanceof zod.ZodError) throw new WorkflowValidationError(String(workflowName), "output", error);
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
165
|
+
const outputResult = await definition.output["~standard"].validate(result);
|
|
166
|
+
if (outputResult.issues) throw new WorkflowValidationError(String(workflowName), "output", outputResult.issues);
|
|
167
|
+
return outputResult.value;
|
|
174
168
|
}
|
|
175
169
|
/**
|
|
176
170
|
* Get a handle to an existing workflow
|
|
@@ -183,55 +177,34 @@ var TypedClient = class TypedClient {
|
|
|
183
177
|
*/
|
|
184
178
|
async getHandle(workflowName, workflowId) {
|
|
185
179
|
const definition = this.contract.workflows[workflowName];
|
|
186
|
-
if (!definition) throw new WorkflowNotFoundError(String(workflowName));
|
|
180
|
+
if (!definition) throw new WorkflowNotFoundError(String(workflowName), Object.keys(this.contract.workflows));
|
|
187
181
|
const handle = this.client.workflow.getHandle(workflowId);
|
|
188
182
|
return this.createTypedHandle(handle, definition);
|
|
189
183
|
}
|
|
190
184
|
createTypedHandle(handle, definition) {
|
|
191
185
|
const queries = {};
|
|
192
186
|
for (const [queryName, queryDef] of Object.entries(definition.queries ?? {})) queries[queryName] = async (args) => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
const result = await handle.query(queryName, validatedInput);
|
|
201
|
-
try {
|
|
202
|
-
return queryDef.output.parse(result);
|
|
203
|
-
} catch (error) {
|
|
204
|
-
if (error instanceof zod.ZodError) throw new QueryValidationError(queryName, "output", error);
|
|
205
|
-
throw error;
|
|
206
|
-
}
|
|
187
|
+
const inputResult = await queryDef.input["~standard"].validate(args);
|
|
188
|
+
if (inputResult.issues) throw new QueryValidationError(queryName, "input", inputResult.issues);
|
|
189
|
+
const result = await handle.query(queryName, inputResult.value);
|
|
190
|
+
const outputResult = await queryDef.output["~standard"].validate(result);
|
|
191
|
+
if (outputResult.issues) throw new QueryValidationError(queryName, "output", outputResult.issues);
|
|
192
|
+
return outputResult.value;
|
|
207
193
|
};
|
|
208
194
|
const signals = {};
|
|
209
195
|
for (const [signalName, signalDef] of Object.entries(definition.signals ?? {})) signals[signalName] = async (args) => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
} catch (error) {
|
|
214
|
-
if (error instanceof zod.ZodError) throw new SignalValidationError(signalName, error);
|
|
215
|
-
throw error;
|
|
216
|
-
}
|
|
217
|
-
await handle.signal(signalName, validatedInput);
|
|
196
|
+
const inputResult = await signalDef.input["~standard"].validate(args);
|
|
197
|
+
if (inputResult.issues) throw new SignalValidationError(signalName, inputResult.issues);
|
|
198
|
+
await handle.signal(signalName, inputResult.value);
|
|
218
199
|
};
|
|
219
200
|
const updates = {};
|
|
220
201
|
for (const [updateName, updateDef] of Object.entries(definition.updates ?? {})) updates[updateName] = async (args) => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
const result = await handle.executeUpdate(updateName, { args: [validatedInput] });
|
|
229
|
-
try {
|
|
230
|
-
return updateDef.output.parse(result);
|
|
231
|
-
} catch (error) {
|
|
232
|
-
if (error instanceof zod.ZodError) throw new UpdateValidationError(updateName, "output", error);
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
202
|
+
const inputResult = await updateDef.input["~standard"].validate(args);
|
|
203
|
+
if (inputResult.issues) throw new UpdateValidationError(updateName, "input", inputResult.issues);
|
|
204
|
+
const result = await handle.executeUpdate(updateName, { args: [inputResult.value] });
|
|
205
|
+
const outputResult = await updateDef.output["~standard"].validate(result);
|
|
206
|
+
if (outputResult.issues) throw new UpdateValidationError(updateName, "output", outputResult.issues);
|
|
207
|
+
return outputResult.value;
|
|
235
208
|
};
|
|
236
209
|
return {
|
|
237
210
|
workflowId: handle.workflowId,
|
|
@@ -240,12 +213,9 @@ var TypedClient = class TypedClient {
|
|
|
240
213
|
updates,
|
|
241
214
|
result: async () => {
|
|
242
215
|
const result = await handle.result();
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (error instanceof zod.ZodError) throw new WorkflowValidationError(handle.workflowId, "output", error);
|
|
247
|
-
throw error;
|
|
248
|
-
}
|
|
216
|
+
const outputResult = await definition.output["~standard"].validate(result);
|
|
217
|
+
if (outputResult.issues) throw new WorkflowValidationError(handle.workflowId, "output", outputResult.issues);
|
|
218
|
+
return outputResult.value;
|
|
249
219
|
},
|
|
250
220
|
terminate: async (reason) => {
|
|
251
221
|
await handle.terminate(reason);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ClientOptions, WorkflowHandle, WorkflowOptions, WorkflowStartOptions } from "@temporalio/client";
|
|
2
2
|
import { ClientInferInput, ClientInferOutput, ClientInferWorkflowQueries, ClientInferWorkflowSignals, ClientInferWorkflowUpdates, ContractDefinition, WorkflowDefinition } from "@temporal-contract/contract";
|
|
3
|
-
import {
|
|
3
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
4
4
|
|
|
5
5
|
//#region src/client.d.ts
|
|
6
6
|
|
|
@@ -149,7 +149,8 @@ declare class TypedClientError extends Error {
|
|
|
149
149
|
*/
|
|
150
150
|
declare class WorkflowNotFoundError extends TypedClientError {
|
|
151
151
|
readonly workflowName: string;
|
|
152
|
-
|
|
152
|
+
readonly availableWorkflows: readonly string[];
|
|
153
|
+
constructor(workflowName: string, availableWorkflows?: readonly string[]);
|
|
153
154
|
}
|
|
154
155
|
/**
|
|
155
156
|
* Error thrown when workflow input or output validation fails
|
|
@@ -157,8 +158,8 @@ declare class WorkflowNotFoundError extends TypedClientError {
|
|
|
157
158
|
declare class WorkflowValidationError extends TypedClientError {
|
|
158
159
|
readonly workflowName: string;
|
|
159
160
|
readonly phase: "input" | "output";
|
|
160
|
-
readonly
|
|
161
|
-
constructor(workflowName: string, phase: "input" | "output",
|
|
161
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
162
|
+
constructor(workflowName: string, phase: "input" | "output", issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
162
163
|
}
|
|
163
164
|
/**
|
|
164
165
|
* Error thrown when query input or output validation fails
|
|
@@ -166,16 +167,16 @@ declare class WorkflowValidationError extends TypedClientError {
|
|
|
166
167
|
declare class QueryValidationError extends TypedClientError {
|
|
167
168
|
readonly queryName: string;
|
|
168
169
|
readonly phase: "input" | "output";
|
|
169
|
-
readonly
|
|
170
|
-
constructor(queryName: string, phase: "input" | "output",
|
|
170
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
171
|
+
constructor(queryName: string, phase: "input" | "output", issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
171
172
|
}
|
|
172
173
|
/**
|
|
173
174
|
* Error thrown when signal input validation fails
|
|
174
175
|
*/
|
|
175
176
|
declare class SignalValidationError extends TypedClientError {
|
|
176
177
|
readonly signalName: string;
|
|
177
|
-
readonly
|
|
178
|
-
constructor(signalName: string,
|
|
178
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
179
|
+
constructor(signalName: string, issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
179
180
|
}
|
|
180
181
|
/**
|
|
181
182
|
* Error thrown when update input or output validation fails
|
|
@@ -183,8 +184,8 @@ declare class SignalValidationError extends TypedClientError {
|
|
|
183
184
|
declare class UpdateValidationError extends TypedClientError {
|
|
184
185
|
readonly updateName: string;
|
|
185
186
|
readonly phase: "input" | "output";
|
|
186
|
-
readonly
|
|
187
|
-
constructor(updateName: string, phase: "input" | "output",
|
|
187
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
188
|
+
constructor(updateName: string, phase: "input" | "output", issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
188
189
|
}
|
|
189
190
|
//#endregion
|
|
190
191
|
export { QueryValidationError, SignalValidationError, TypedClient, TypedClientError, type TypedWorkflowHandle, type TypedWorkflowStartOptions, UpdateValidationError, WorkflowNotFoundError, WorkflowValidationError };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ClientOptions, WorkflowHandle, WorkflowOptions, WorkflowStartOptions } from "@temporalio/client";
|
|
2
|
-
import { z } from "zod";
|
|
3
2
|
import { ClientInferInput, ClientInferOutput, ClientInferWorkflowQueries, ClientInferWorkflowSignals, ClientInferWorkflowUpdates, ContractDefinition, WorkflowDefinition } from "@temporal-contract/contract";
|
|
3
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
4
4
|
|
|
5
5
|
//#region src/client.d.ts
|
|
6
6
|
|
|
@@ -149,7 +149,8 @@ declare class TypedClientError extends Error {
|
|
|
149
149
|
*/
|
|
150
150
|
declare class WorkflowNotFoundError extends TypedClientError {
|
|
151
151
|
readonly workflowName: string;
|
|
152
|
-
|
|
152
|
+
readonly availableWorkflows: readonly string[];
|
|
153
|
+
constructor(workflowName: string, availableWorkflows?: readonly string[]);
|
|
153
154
|
}
|
|
154
155
|
/**
|
|
155
156
|
* Error thrown when workflow input or output validation fails
|
|
@@ -157,8 +158,8 @@ declare class WorkflowNotFoundError extends TypedClientError {
|
|
|
157
158
|
declare class WorkflowValidationError extends TypedClientError {
|
|
158
159
|
readonly workflowName: string;
|
|
159
160
|
readonly phase: "input" | "output";
|
|
160
|
-
readonly
|
|
161
|
-
constructor(workflowName: string, phase: "input" | "output",
|
|
161
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
162
|
+
constructor(workflowName: string, phase: "input" | "output", issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
162
163
|
}
|
|
163
164
|
/**
|
|
164
165
|
* Error thrown when query input or output validation fails
|
|
@@ -166,16 +167,16 @@ declare class WorkflowValidationError extends TypedClientError {
|
|
|
166
167
|
declare class QueryValidationError extends TypedClientError {
|
|
167
168
|
readonly queryName: string;
|
|
168
169
|
readonly phase: "input" | "output";
|
|
169
|
-
readonly
|
|
170
|
-
constructor(queryName: string, phase: "input" | "output",
|
|
170
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
171
|
+
constructor(queryName: string, phase: "input" | "output", issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
171
172
|
}
|
|
172
173
|
/**
|
|
173
174
|
* Error thrown when signal input validation fails
|
|
174
175
|
*/
|
|
175
176
|
declare class SignalValidationError extends TypedClientError {
|
|
176
177
|
readonly signalName: string;
|
|
177
|
-
readonly
|
|
178
|
-
constructor(signalName: string,
|
|
178
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
179
|
+
constructor(signalName: string, issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
179
180
|
}
|
|
180
181
|
/**
|
|
181
182
|
* Error thrown when update input or output validation fails
|
|
@@ -183,8 +184,8 @@ declare class SignalValidationError extends TypedClientError {
|
|
|
183
184
|
declare class UpdateValidationError extends TypedClientError {
|
|
184
185
|
readonly updateName: string;
|
|
185
186
|
readonly phase: "input" | "output";
|
|
186
|
-
readonly
|
|
187
|
-
constructor(updateName: string, phase: "input" | "output",
|
|
187
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
188
|
+
constructor(updateName: string, phase: "input" | "output", issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
188
189
|
}
|
|
189
190
|
//#endregion
|
|
190
191
|
export { QueryValidationError, SignalValidationError, TypedClient, TypedClientError, type TypedWorkflowHandle, type TypedWorkflowStartOptions, UpdateValidationError, WorkflowNotFoundError, WorkflowValidationError };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Client } from "@temporalio/client";
|
|
2
|
-
import { ZodError } from "zod";
|
|
3
2
|
|
|
4
3
|
//#region src/errors.ts
|
|
5
4
|
/**
|
|
@@ -16,9 +15,11 @@ var TypedClientError = class extends Error {
|
|
|
16
15
|
* Error thrown when a workflow is not found in the contract
|
|
17
16
|
*/
|
|
18
17
|
var WorkflowNotFoundError = class extends TypedClientError {
|
|
19
|
-
constructor(workflowName) {
|
|
20
|
-
|
|
18
|
+
constructor(workflowName, availableWorkflows = []) {
|
|
19
|
+
const message = availableWorkflows.length > 0 ? `Workflow "${workflowName}" not found in contract. Available workflows: ${availableWorkflows.join(", ")}` : `Workflow "${workflowName}" not found in contract`;
|
|
20
|
+
super(message);
|
|
21
21
|
this.workflowName = workflowName;
|
|
22
|
+
this.availableWorkflows = availableWorkflows;
|
|
22
23
|
this.name = "WorkflowNotFoundError";
|
|
23
24
|
}
|
|
24
25
|
};
|
|
@@ -26,11 +27,12 @@ var WorkflowNotFoundError = class extends TypedClientError {
|
|
|
26
27
|
* Error thrown when workflow input or output validation fails
|
|
27
28
|
*/
|
|
28
29
|
var WorkflowValidationError = class extends TypedClientError {
|
|
29
|
-
constructor(workflowName, phase,
|
|
30
|
-
|
|
30
|
+
constructor(workflowName, phase, issues) {
|
|
31
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
32
|
+
super(`Validation failed for workflow "${workflowName}" ${phase}: ${message}`);
|
|
31
33
|
this.workflowName = workflowName;
|
|
32
34
|
this.phase = phase;
|
|
33
|
-
this.
|
|
35
|
+
this.issues = issues;
|
|
34
36
|
this.name = "WorkflowValidationError";
|
|
35
37
|
}
|
|
36
38
|
};
|
|
@@ -38,11 +40,12 @@ var WorkflowValidationError = class extends TypedClientError {
|
|
|
38
40
|
* Error thrown when query input or output validation fails
|
|
39
41
|
*/
|
|
40
42
|
var QueryValidationError = class extends TypedClientError {
|
|
41
|
-
constructor(queryName, phase,
|
|
42
|
-
|
|
43
|
+
constructor(queryName, phase, issues) {
|
|
44
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
45
|
+
super(`Validation failed for query "${queryName}" ${phase}: ${message}`);
|
|
43
46
|
this.queryName = queryName;
|
|
44
47
|
this.phase = phase;
|
|
45
|
-
this.
|
|
48
|
+
this.issues = issues;
|
|
46
49
|
this.name = "QueryValidationError";
|
|
47
50
|
}
|
|
48
51
|
};
|
|
@@ -50,10 +53,11 @@ var QueryValidationError = class extends TypedClientError {
|
|
|
50
53
|
* Error thrown when signal input validation fails
|
|
51
54
|
*/
|
|
52
55
|
var SignalValidationError = class extends TypedClientError {
|
|
53
|
-
constructor(signalName,
|
|
54
|
-
|
|
56
|
+
constructor(signalName, issues) {
|
|
57
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
58
|
+
super(`Validation failed for signal "${signalName}" input: ${message}`);
|
|
55
59
|
this.signalName = signalName;
|
|
56
|
-
this.
|
|
60
|
+
this.issues = issues;
|
|
57
61
|
this.name = "SignalValidationError";
|
|
58
62
|
}
|
|
59
63
|
};
|
|
@@ -61,11 +65,12 @@ var SignalValidationError = class extends TypedClientError {
|
|
|
61
65
|
* Error thrown when update input or output validation fails
|
|
62
66
|
*/
|
|
63
67
|
var UpdateValidationError = class extends TypedClientError {
|
|
64
|
-
constructor(updateName, phase,
|
|
65
|
-
|
|
68
|
+
constructor(updateName, phase, issues) {
|
|
69
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
70
|
+
super(`Validation failed for update "${updateName}" ${phase}: ${message}`);
|
|
66
71
|
this.updateName = updateName;
|
|
67
72
|
this.phase = phase;
|
|
68
|
-
this.
|
|
73
|
+
this.issues = issues;
|
|
69
74
|
this.name = "UpdateValidationError";
|
|
70
75
|
}
|
|
71
76
|
};
|
|
@@ -120,14 +125,10 @@ var TypedClient = class TypedClient {
|
|
|
120
125
|
*/
|
|
121
126
|
async startWorkflow(workflowName, { args, ...temporalOptions }) {
|
|
122
127
|
const definition = this.contract.workflows[workflowName];
|
|
123
|
-
if (!definition) throw new WorkflowNotFoundError(String(workflowName));
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
} catch (error) {
|
|
128
|
-
if (error instanceof ZodError) throw new WorkflowValidationError(String(workflowName), "input", error);
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
128
|
+
if (!definition) throw new WorkflowNotFoundError(String(workflowName), Object.keys(this.contract.workflows));
|
|
129
|
+
const inputResult = await definition.input["~standard"].validate(args);
|
|
130
|
+
if (inputResult.issues) throw new WorkflowValidationError(String(workflowName), "input", inputResult.issues);
|
|
131
|
+
const validatedInput = inputResult.value;
|
|
131
132
|
const handle = await this.client.workflow.start(workflowName, {
|
|
132
133
|
...temporalOptions,
|
|
133
134
|
taskQueue: this.contract.taskQueue,
|
|
@@ -152,25 +153,18 @@ var TypedClient = class TypedClient {
|
|
|
152
153
|
*/
|
|
153
154
|
async executeWorkflow(workflowName, { args, ...temporalOptions }) {
|
|
154
155
|
const definition = this.contract.workflows[workflowName];
|
|
155
|
-
if (!definition) throw new WorkflowNotFoundError(String(workflowName));
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} catch (error) {
|
|
160
|
-
if (error instanceof ZodError) throw new WorkflowValidationError(String(workflowName), "input", error);
|
|
161
|
-
throw error;
|
|
162
|
-
}
|
|
156
|
+
if (!definition) throw new WorkflowNotFoundError(String(workflowName), Object.keys(this.contract.workflows));
|
|
157
|
+
const inputResult = await definition.input["~standard"].validate(args);
|
|
158
|
+
if (inputResult.issues) throw new WorkflowValidationError(String(workflowName), "input", inputResult.issues);
|
|
159
|
+
const validatedInput = inputResult.value;
|
|
163
160
|
const result = await this.client.workflow.execute(workflowName, {
|
|
164
161
|
...temporalOptions,
|
|
165
162
|
taskQueue: this.contract.taskQueue,
|
|
166
163
|
args: [validatedInput]
|
|
167
164
|
});
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (error instanceof ZodError) throw new WorkflowValidationError(String(workflowName), "output", error);
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
165
|
+
const outputResult = await definition.output["~standard"].validate(result);
|
|
166
|
+
if (outputResult.issues) throw new WorkflowValidationError(String(workflowName), "output", outputResult.issues);
|
|
167
|
+
return outputResult.value;
|
|
174
168
|
}
|
|
175
169
|
/**
|
|
176
170
|
* Get a handle to an existing workflow
|
|
@@ -183,55 +177,34 @@ var TypedClient = class TypedClient {
|
|
|
183
177
|
*/
|
|
184
178
|
async getHandle(workflowName, workflowId) {
|
|
185
179
|
const definition = this.contract.workflows[workflowName];
|
|
186
|
-
if (!definition) throw new WorkflowNotFoundError(String(workflowName));
|
|
180
|
+
if (!definition) throw new WorkflowNotFoundError(String(workflowName), Object.keys(this.contract.workflows));
|
|
187
181
|
const handle = this.client.workflow.getHandle(workflowId);
|
|
188
182
|
return this.createTypedHandle(handle, definition);
|
|
189
183
|
}
|
|
190
184
|
createTypedHandle(handle, definition) {
|
|
191
185
|
const queries = {};
|
|
192
186
|
for (const [queryName, queryDef] of Object.entries(definition.queries ?? {})) queries[queryName] = async (args) => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
const result = await handle.query(queryName, validatedInput);
|
|
201
|
-
try {
|
|
202
|
-
return queryDef.output.parse(result);
|
|
203
|
-
} catch (error) {
|
|
204
|
-
if (error instanceof ZodError) throw new QueryValidationError(queryName, "output", error);
|
|
205
|
-
throw error;
|
|
206
|
-
}
|
|
187
|
+
const inputResult = await queryDef.input["~standard"].validate(args);
|
|
188
|
+
if (inputResult.issues) throw new QueryValidationError(queryName, "input", inputResult.issues);
|
|
189
|
+
const result = await handle.query(queryName, inputResult.value);
|
|
190
|
+
const outputResult = await queryDef.output["~standard"].validate(result);
|
|
191
|
+
if (outputResult.issues) throw new QueryValidationError(queryName, "output", outputResult.issues);
|
|
192
|
+
return outputResult.value;
|
|
207
193
|
};
|
|
208
194
|
const signals = {};
|
|
209
195
|
for (const [signalName, signalDef] of Object.entries(definition.signals ?? {})) signals[signalName] = async (args) => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
} catch (error) {
|
|
214
|
-
if (error instanceof ZodError) throw new SignalValidationError(signalName, error);
|
|
215
|
-
throw error;
|
|
216
|
-
}
|
|
217
|
-
await handle.signal(signalName, validatedInput);
|
|
196
|
+
const inputResult = await signalDef.input["~standard"].validate(args);
|
|
197
|
+
if (inputResult.issues) throw new SignalValidationError(signalName, inputResult.issues);
|
|
198
|
+
await handle.signal(signalName, inputResult.value);
|
|
218
199
|
};
|
|
219
200
|
const updates = {};
|
|
220
201
|
for (const [updateName, updateDef] of Object.entries(definition.updates ?? {})) updates[updateName] = async (args) => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
const result = await handle.executeUpdate(updateName, { args: [validatedInput] });
|
|
229
|
-
try {
|
|
230
|
-
return updateDef.output.parse(result);
|
|
231
|
-
} catch (error) {
|
|
232
|
-
if (error instanceof ZodError) throw new UpdateValidationError(updateName, "output", error);
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
202
|
+
const inputResult = await updateDef.input["~standard"].validate(args);
|
|
203
|
+
if (inputResult.issues) throw new UpdateValidationError(updateName, "input", inputResult.issues);
|
|
204
|
+
const result = await handle.executeUpdate(updateName, { args: [inputResult.value] });
|
|
205
|
+
const outputResult = await updateDef.output["~standard"].validate(result);
|
|
206
|
+
if (outputResult.issues) throw new UpdateValidationError(updateName, "output", outputResult.issues);
|
|
207
|
+
return outputResult.value;
|
|
235
208
|
};
|
|
236
209
|
return {
|
|
237
210
|
workflowId: handle.workflowId,
|
|
@@ -240,12 +213,9 @@ var TypedClient = class TypedClient {
|
|
|
240
213
|
updates,
|
|
241
214
|
result: async () => {
|
|
242
215
|
const result = await handle.result();
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (error instanceof ZodError) throw new WorkflowValidationError(handle.workflowId, "output", error);
|
|
247
|
-
throw error;
|
|
248
|
-
}
|
|
216
|
+
const outputResult = await definition.output["~standard"].validate(result);
|
|
217
|
+
if (outputResult.issues) throw new WorkflowValidationError(handle.workflowId, "output", outputResult.issues);
|
|
218
|
+
return outputResult.value;
|
|
249
219
|
},
|
|
250
220
|
terminate: async (reason) => {
|
|
251
221
|
await handle.terminate(reason);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@temporal-contract/client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Client utilities for consuming temporal-contract workflows",
|
|
6
6
|
"homepage": "https://github.com/btravers/temporal-contract#readme",
|
|
@@ -37,21 +37,21 @@
|
|
|
37
37
|
"./package.json": "./package.json"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@
|
|
40
|
+
"@standard-schema/spec": "1.0.0",
|
|
41
|
+
"@temporal-contract/contract": "0.0.2"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@temporalio/client": "1.13.2",
|
|
44
45
|
"@types/node": "24.10.2",
|
|
46
|
+
"@vitest/coverage-v8": "4.0.15",
|
|
45
47
|
"tsdown": "0.17.2",
|
|
46
|
-
"type-fest": "5.3.1",
|
|
47
48
|
"typescript": "5.9.3",
|
|
48
49
|
"vitest": "4.0.15",
|
|
49
50
|
"zod": "4.1.13",
|
|
50
|
-
"@temporal-contract/tsconfig": "0.0.
|
|
51
|
+
"@temporal-contract/tsconfig": "0.0.2"
|
|
51
52
|
},
|
|
52
53
|
"peerDependencies": {
|
|
53
|
-
"@temporalio/client": ">=1.13.0 <2.0.0"
|
|
54
|
-
"zod": "^4.0.0"
|
|
54
|
+
"@temporalio/client": ">=1.13.0 <2.0.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
|
package/src/client.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Client, WorkflowHandle } from "@temporalio/client";
|
|
2
2
|
import type { ClientOptions, WorkflowStartOptions, WorkflowOptions } from "@temporalio/client";
|
|
3
|
-
import { ZodError } from "zod";
|
|
4
3
|
import type {
|
|
5
4
|
ClientInferInput,
|
|
6
5
|
ClientInferOutput,
|
|
@@ -155,21 +154,20 @@ export class TypedClient<TContract extends ContractDefinition> {
|
|
|
155
154
|
const definition = this.contract.workflows[workflowName as string];
|
|
156
155
|
|
|
157
156
|
if (!definition) {
|
|
158
|
-
throw new WorkflowNotFoundError(
|
|
157
|
+
throw new WorkflowNotFoundError(
|
|
158
|
+
String(workflowName),
|
|
159
|
+
Object.keys(this.contract.workflows) as string[],
|
|
160
|
+
);
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
// Validate input with
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
TContract["workflows"][TWorkflowName]
|
|
166
|
-
>;
|
|
167
|
-
} catch (error) {
|
|
168
|
-
if (error instanceof ZodError) {
|
|
169
|
-
throw new WorkflowValidationError(String(workflowName), "input", error);
|
|
170
|
-
}
|
|
171
|
-
throw error;
|
|
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);
|
|
172
167
|
}
|
|
168
|
+
const validatedInput = inputResult.value as ClientInferInput<
|
|
169
|
+
TContract["workflows"][TWorkflowName]
|
|
170
|
+
>;
|
|
173
171
|
|
|
174
172
|
// Start workflow (Temporal expects args as array, so wrap single parameter)
|
|
175
173
|
const handle = await this.client.workflow.start(workflowName as string, {
|
|
@@ -210,21 +208,20 @@ export class TypedClient<TContract extends ContractDefinition> {
|
|
|
210
208
|
const definition = this.contract.workflows[workflowName as string];
|
|
211
209
|
|
|
212
210
|
if (!definition) {
|
|
213
|
-
throw new WorkflowNotFoundError(
|
|
211
|
+
throw new WorkflowNotFoundError(
|
|
212
|
+
String(workflowName),
|
|
213
|
+
Object.keys(this.contract.workflows) as string[],
|
|
214
|
+
);
|
|
214
215
|
}
|
|
215
216
|
|
|
216
|
-
// Validate input with
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
TContract["workflows"][TWorkflowName]
|
|
221
|
-
>;
|
|
222
|
-
} catch (error) {
|
|
223
|
-
if (error instanceof ZodError) {
|
|
224
|
-
throw new WorkflowValidationError(String(workflowName), "input", error);
|
|
225
|
-
}
|
|
226
|
-
throw error;
|
|
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);
|
|
227
221
|
}
|
|
222
|
+
const validatedInput = inputResult.value as ClientInferInput<
|
|
223
|
+
TContract["workflows"][TWorkflowName]
|
|
224
|
+
>;
|
|
228
225
|
|
|
229
226
|
// Execute workflow (Temporal expects args as array, so wrap single parameter)
|
|
230
227
|
const result = await this.client.workflow.execute(workflowName as string, {
|
|
@@ -233,17 +230,13 @@ export class TypedClient<TContract extends ContractDefinition> {
|
|
|
233
230
|
args: [validatedInput],
|
|
234
231
|
});
|
|
235
232
|
|
|
236
|
-
// Validate output with
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
>;
|
|
241
|
-
} catch (error) {
|
|
242
|
-
if (error instanceof ZodError) {
|
|
243
|
-
throw new WorkflowValidationError(String(workflowName), "output", error);
|
|
244
|
-
}
|
|
245
|
-
throw error;
|
|
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);
|
|
246
237
|
}
|
|
238
|
+
|
|
239
|
+
return outputResult.value as ClientInferOutput<TContract["workflows"][TWorkflowName]>;
|
|
247
240
|
}
|
|
248
241
|
|
|
249
242
|
/**
|
|
@@ -262,7 +255,10 @@ export class TypedClient<TContract extends ContractDefinition> {
|
|
|
262
255
|
const definition = this.contract.workflows[workflowName as string];
|
|
263
256
|
|
|
264
257
|
if (!definition) {
|
|
265
|
-
throw new WorkflowNotFoundError(
|
|
258
|
+
throw new WorkflowNotFoundError(
|
|
259
|
+
String(workflowName),
|
|
260
|
+
Object.keys(this.contract.workflows) as string[],
|
|
261
|
+
);
|
|
266
262
|
}
|
|
267
263
|
|
|
268
264
|
const handle = this.client.workflow.getHandle(workflowId);
|
|
@@ -283,26 +279,19 @@ export class TypedClient<TContract extends ContractDefinition> {
|
|
|
283
279
|
(queries as Record<string, unknown>)[queryName] = async (
|
|
284
280
|
args: ClientInferInput<typeof queryDef>,
|
|
285
281
|
) => {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
} catch (error) {
|
|
290
|
-
if (error instanceof ZodError) {
|
|
291
|
-
throw new QueryValidationError(queryName, "input", error);
|
|
292
|
-
}
|
|
293
|
-
throw error;
|
|
282
|
+
const inputResult = await queryDef.input["~standard"].validate(args);
|
|
283
|
+
if (inputResult.issues) {
|
|
284
|
+
throw new QueryValidationError(queryName, "input", inputResult.issues);
|
|
294
285
|
}
|
|
295
286
|
|
|
296
|
-
const result = await handle.query(queryName as string,
|
|
287
|
+
const result = await handle.query(queryName as string, inputResult.value);
|
|
297
288
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (error instanceof ZodError) {
|
|
302
|
-
throw new QueryValidationError(queryName, "output", error);
|
|
303
|
-
}
|
|
304
|
-
throw error;
|
|
289
|
+
const outputResult = await queryDef.output["~standard"].validate(result);
|
|
290
|
+
if (outputResult.issues) {
|
|
291
|
+
throw new QueryValidationError(queryName, "output", outputResult.issues);
|
|
305
292
|
}
|
|
293
|
+
|
|
294
|
+
return outputResult.value;
|
|
306
295
|
};
|
|
307
296
|
}
|
|
308
297
|
|
|
@@ -314,16 +303,11 @@ export class TypedClient<TContract extends ContractDefinition> {
|
|
|
314
303
|
(signals as Record<string, unknown>)[signalName] = async (
|
|
315
304
|
args: ClientInferInput<typeof signalDef>,
|
|
316
305
|
) => {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
} catch (error) {
|
|
321
|
-
if (error instanceof ZodError) {
|
|
322
|
-
throw new SignalValidationError(signalName, error);
|
|
323
|
-
}
|
|
324
|
-
throw error;
|
|
306
|
+
const inputResult = await signalDef.input["~standard"].validate(args);
|
|
307
|
+
if (inputResult.issues) {
|
|
308
|
+
throw new SignalValidationError(signalName, inputResult.issues);
|
|
325
309
|
}
|
|
326
|
-
await handle.signal(signalName as string,
|
|
310
|
+
await handle.signal(signalName as string, inputResult.value);
|
|
327
311
|
};
|
|
328
312
|
}
|
|
329
313
|
|
|
@@ -335,26 +319,21 @@ export class TypedClient<TContract extends ContractDefinition> {
|
|
|
335
319
|
(updates as Record<string, unknown>)[updateName] = async (
|
|
336
320
|
args: ClientInferInput<typeof updateDef>,
|
|
337
321
|
) => {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
} catch (error) {
|
|
342
|
-
if (error instanceof ZodError) {
|
|
343
|
-
throw new UpdateValidationError(updateName, "input", error);
|
|
344
|
-
}
|
|
345
|
-
throw error;
|
|
322
|
+
const inputResult = await updateDef.input["~standard"].validate(args);
|
|
323
|
+
if (inputResult.issues) {
|
|
324
|
+
throw new UpdateValidationError(updateName, "input", inputResult.issues);
|
|
346
325
|
}
|
|
347
326
|
|
|
348
|
-
const result = await handle.executeUpdate(updateName as string, {
|
|
327
|
+
const result = await handle.executeUpdate(updateName as string, {
|
|
328
|
+
args: [inputResult.value],
|
|
329
|
+
});
|
|
349
330
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (error instanceof ZodError) {
|
|
354
|
-
throw new UpdateValidationError(updateName, "output", error);
|
|
355
|
-
}
|
|
356
|
-
throw error;
|
|
331
|
+
const outputResult = await updateDef.output["~standard"].validate(result);
|
|
332
|
+
if (outputResult.issues) {
|
|
333
|
+
throw new UpdateValidationError(updateName, "output", outputResult.issues);
|
|
357
334
|
}
|
|
335
|
+
|
|
336
|
+
return outputResult.value;
|
|
358
337
|
};
|
|
359
338
|
}
|
|
360
339
|
|
|
@@ -365,15 +344,12 @@ export class TypedClient<TContract extends ContractDefinition> {
|
|
|
365
344
|
updates,
|
|
366
345
|
result: async () => {
|
|
367
346
|
const result = await handle.result();
|
|
368
|
-
// Validate output with
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (error instanceof ZodError) {
|
|
373
|
-
throw new WorkflowValidationError(handle.workflowId, "output", error);
|
|
374
|
-
}
|
|
375
|
-
throw error;
|
|
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);
|
|
376
351
|
}
|
|
352
|
+
return outputResult.value as ClientInferOutput<TWorkflow>;
|
|
377
353
|
},
|
|
378
354
|
terminate: async (reason?: string) => {
|
|
379
355
|
await handle.terminate(reason);
|
package/src/errors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Base error class for typed client errors
|
|
@@ -18,8 +18,15 @@ export class TypedClientError extends Error {
|
|
|
18
18
|
* Error thrown when a workflow is not found in the contract
|
|
19
19
|
*/
|
|
20
20
|
export class WorkflowNotFoundError extends TypedClientError {
|
|
21
|
-
constructor(
|
|
22
|
-
|
|
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);
|
|
23
30
|
this.name = "WorkflowNotFoundError";
|
|
24
31
|
}
|
|
25
32
|
}
|
|
@@ -31,9 +38,10 @@ export class WorkflowValidationError extends TypedClientError {
|
|
|
31
38
|
constructor(
|
|
32
39
|
public readonly workflowName: string,
|
|
33
40
|
public readonly phase: "input" | "output",
|
|
34
|
-
public readonly
|
|
41
|
+
public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
|
|
35
42
|
) {
|
|
36
|
-
|
|
43
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
44
|
+
super(`Validation failed for workflow "${workflowName}" ${phase}: ${message}`);
|
|
37
45
|
this.name = "WorkflowValidationError";
|
|
38
46
|
}
|
|
39
47
|
}
|
|
@@ -45,9 +53,10 @@ export class QueryValidationError extends TypedClientError {
|
|
|
45
53
|
constructor(
|
|
46
54
|
public readonly queryName: string,
|
|
47
55
|
public readonly phase: "input" | "output",
|
|
48
|
-
public readonly
|
|
56
|
+
public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
|
|
49
57
|
) {
|
|
50
|
-
|
|
58
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
59
|
+
super(`Validation failed for query "${queryName}" ${phase}: ${message}`);
|
|
51
60
|
this.name = "QueryValidationError";
|
|
52
61
|
}
|
|
53
62
|
}
|
|
@@ -58,9 +67,10 @@ export class QueryValidationError extends TypedClientError {
|
|
|
58
67
|
export class SignalValidationError extends TypedClientError {
|
|
59
68
|
constructor(
|
|
60
69
|
public readonly signalName: string,
|
|
61
|
-
public readonly
|
|
70
|
+
public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
|
|
62
71
|
) {
|
|
63
|
-
|
|
72
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
73
|
+
super(`Validation failed for signal "${signalName}" input: ${message}`);
|
|
64
74
|
this.name = "SignalValidationError";
|
|
65
75
|
}
|
|
66
76
|
}
|
|
@@ -72,9 +82,10 @@ export class UpdateValidationError extends TypedClientError {
|
|
|
72
82
|
constructor(
|
|
73
83
|
public readonly updateName: string,
|
|
74
84
|
public readonly phase: "input" | "output",
|
|
75
|
-
public readonly
|
|
85
|
+
public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
|
|
76
86
|
) {
|
|
77
|
-
|
|
87
|
+
const message = issues.map((issue) => issue.message).join("; ");
|
|
88
|
+
super(`Validation failed for update "${updateName}" ${phase}: ${message}`);
|
|
78
89
|
this.name = "UpdateValidationError";
|
|
79
90
|
}
|
|
80
91
|
}
|
package/vitest.config.ts
ADDED