@temporal-contract/client 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.
@@ -0,0 +1,190 @@
1
+ import { ClientOptions, WorkflowHandle, WorkflowOptions, WorkflowStartOptions } from "@temporalio/client";
2
+ import { z } from "zod";
3
+ import { ClientInferInput, ClientInferOutput, ClientInferWorkflowQueries, ClientInferWorkflowSignals, ClientInferWorkflowUpdates, ContractDefinition, WorkflowDefinition } from "@temporal-contract/contract";
4
+
5
+ //#region src/client.d.ts
6
+
7
+ /**
8
+ * Extended options for starting workflows with Temporal-specific features
9
+ * Combines required workflowId with optional Temporal workflow options
10
+ */
11
+ type TypedWorkflowStartOptions = Pick<WorkflowStartOptions, "workflowId" | "workflowIdReusePolicy" | "workflowExecutionTimeout" | "workflowRunTimeout" | "workflowTaskTimeout" | "retry" | "memo" | "searchAttributes" | "cronSchedule"> & Pick<WorkflowOptions, "workflowId">;
12
+ /**
13
+ * Typed workflow handle with validated results, queries, signals and updates
14
+ */
15
+ interface TypedWorkflowHandle<TWorkflow extends WorkflowDefinition> {
16
+ workflowId: string;
17
+ /**
18
+ * Type-safe queries based on workflow definition
19
+ */
20
+ queries: ClientInferWorkflowQueries<TWorkflow>;
21
+ /**
22
+ * Type-safe signals based on workflow definition
23
+ */
24
+ signals: ClientInferWorkflowSignals<TWorkflow>;
25
+ /**
26
+ * Type-safe updates based on workflow definition
27
+ */
28
+ updates: ClientInferWorkflowUpdates<TWorkflow>;
29
+ result: () => Promise<ClientInferOutput<TWorkflow>>;
30
+ terminate: (reason?: string) => Promise<void>;
31
+ cancel: () => Promise<void>;
32
+ /**
33
+ * Get workflow execution description including status and metadata
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const handle = await client.getHandle('processOrder', 'order-123');
38
+ * const description = await handle.describe();
39
+ * console.log(description.workflowExecutionInfo.status); // RUNNING, COMPLETED, etc.
40
+ * ```
41
+ */
42
+ describe: () => ReturnType<WorkflowHandle["describe"]>;
43
+ /**
44
+ * Fetch the workflow execution history
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const handle = await client.getHandle('processOrder', 'order-123');
49
+ * const history = handle.fetchHistory();
50
+ * for await (const event of history) {
51
+ * console.log(event);
52
+ * }
53
+ * ```
54
+ */
55
+ fetchHistory: () => ReturnType<WorkflowHandle["fetchHistory"]>;
56
+ }
57
+ /**
58
+ * Typed Temporal client based on a contract
59
+ *
60
+ * Provides type-safe methods to start and execute workflows
61
+ * defined in the contract.
62
+ */
63
+ declare class TypedClient<TContract extends ContractDefinition> {
64
+ private readonly contract;
65
+ private readonly client;
66
+ private constructor();
67
+ /**
68
+ * Create a typed Temporal client from a contract
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * const connection = await Connection.connect();
73
+ * const client = TypedClient.create(myContract, {
74
+ * connection,
75
+ * namespace: 'default',
76
+ * });
77
+ *
78
+ * const result = await client.executeWorkflow('processOrder', {
79
+ * workflowId: 'order-123',
80
+ * args: [...],
81
+ * });
82
+ * ```
83
+ */
84
+ static create<TContract extends ContractDefinition>(contract: TContract, options: ClientOptions): TypedClient<TContract>;
85
+ /**
86
+ * Start a workflow and return a typed handle
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const handle = await client.startWorkflow('processOrder', {
91
+ * workflowId: 'order-123',
92
+ * args: ['ORD-123', 'CUST-456', [{ productId: 'PROD-1', quantity: 2 }]],
93
+ * workflowExecutionTimeout: '1 day',
94
+ * retry: { maximumAttempts: 3 },
95
+ * });
96
+ *
97
+ * const result = await handle.result();
98
+ * ```
99
+ */
100
+ startWorkflow<TWorkflowName extends keyof TContract["workflows"]>(workflowName: TWorkflowName, {
101
+ args,
102
+ ...temporalOptions
103
+ }: TypedWorkflowStartOptions & {
104
+ args: ClientInferInput<TContract["workflows"][TWorkflowName]>;
105
+ }): Promise<TypedWorkflowHandle<TContract["workflows"][TWorkflowName]>>;
106
+ /**
107
+ * Execute a workflow (start and wait for result)
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * const result = await client.executeWorkflow('processOrder', {
112
+ * workflowId: 'order-123',
113
+ * args: ['ORD-123', 'CUST-456', [{ productId: 'PROD-1', quantity: 2 }]],
114
+ * workflowExecutionTimeout: '1 day',
115
+ * retry: { maximumAttempts: 3 },
116
+ * });
117
+ *
118
+ * console.log(result.status); // fully typed!
119
+ * ```
120
+ */
121
+ executeWorkflow<TWorkflowName extends keyof TContract["workflows"]>(workflowName: TWorkflowName, {
122
+ args,
123
+ ...temporalOptions
124
+ }: TypedWorkflowStartOptions & {
125
+ args: ClientInferInput<TContract["workflows"][TWorkflowName]>;
126
+ }): Promise<ClientInferOutput<TContract["workflows"][TWorkflowName]>>;
127
+ /**
128
+ * Get a handle to an existing workflow
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * const handle = await client.getHandle('processOrder', 'order-123');
133
+ * const result = await handle.result();
134
+ * ```
135
+ */
136
+ getHandle<TWorkflowName extends keyof TContract["workflows"]>(workflowName: TWorkflowName, workflowId: string): Promise<TypedWorkflowHandle<TContract["workflows"][TWorkflowName]>>;
137
+ private createTypedHandle;
138
+ }
139
+ //#endregion
140
+ //#region src/errors.d.ts
141
+ /**
142
+ * Base error class for typed client errors
143
+ */
144
+ declare class TypedClientError extends Error {
145
+ constructor(message: string);
146
+ }
147
+ /**
148
+ * Error thrown when a workflow is not found in the contract
149
+ */
150
+ declare class WorkflowNotFoundError extends TypedClientError {
151
+ readonly workflowName: string;
152
+ constructor(workflowName: string);
153
+ }
154
+ /**
155
+ * Error thrown when workflow input or output validation fails
156
+ */
157
+ declare class WorkflowValidationError extends TypedClientError {
158
+ readonly workflowName: string;
159
+ readonly phase: "input" | "output";
160
+ readonly zodError: z.ZodError;
161
+ constructor(workflowName: string, phase: "input" | "output", zodError: z.ZodError);
162
+ }
163
+ /**
164
+ * Error thrown when query input or output validation fails
165
+ */
166
+ declare class QueryValidationError extends TypedClientError {
167
+ readonly queryName: string;
168
+ readonly phase: "input" | "output";
169
+ readonly zodError: z.ZodError;
170
+ constructor(queryName: string, phase: "input" | "output", zodError: z.ZodError);
171
+ }
172
+ /**
173
+ * Error thrown when signal input validation fails
174
+ */
175
+ declare class SignalValidationError extends TypedClientError {
176
+ readonly signalName: string;
177
+ readonly zodError: z.ZodError;
178
+ constructor(signalName: string, zodError: z.ZodError);
179
+ }
180
+ /**
181
+ * Error thrown when update input or output validation fails
182
+ */
183
+ declare class UpdateValidationError extends TypedClientError {
184
+ readonly updateName: string;
185
+ readonly phase: "input" | "output";
186
+ readonly zodError: z.ZodError;
187
+ constructor(updateName: string, phase: "input" | "output", zodError: z.ZodError);
188
+ }
189
+ //#endregion
190
+ export { QueryValidationError, SignalValidationError, TypedClient, TypedClientError, type TypedWorkflowHandle, type TypedWorkflowStartOptions, UpdateValidationError, WorkflowNotFoundError, WorkflowValidationError };
package/dist/index.mjs ADDED
@@ -0,0 +1,263 @@
1
+ import { Client } from "@temporalio/client";
2
+ import { ZodError } from "zod";
3
+
4
+ //#region src/errors.ts
5
+ /**
6
+ * Base error class for typed client errors
7
+ */
8
+ var TypedClientError = class extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "TypedClientError";
12
+ if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ };
15
+ /**
16
+ * Error thrown when a workflow is not found in the contract
17
+ */
18
+ var WorkflowNotFoundError = class extends TypedClientError {
19
+ constructor(workflowName) {
20
+ super(`Workflow "${workflowName}" not found in contract`);
21
+ this.workflowName = workflowName;
22
+ this.name = "WorkflowNotFoundError";
23
+ }
24
+ };
25
+ /**
26
+ * Error thrown when workflow input or output validation fails
27
+ */
28
+ var WorkflowValidationError = class extends TypedClientError {
29
+ constructor(workflowName, phase, zodError) {
30
+ super(`Validation failed for workflow "${workflowName}" ${phase}: ${zodError.message}`);
31
+ this.workflowName = workflowName;
32
+ this.phase = phase;
33
+ this.zodError = zodError;
34
+ this.name = "WorkflowValidationError";
35
+ }
36
+ };
37
+ /**
38
+ * Error thrown when query input or output validation fails
39
+ */
40
+ var QueryValidationError = class extends TypedClientError {
41
+ constructor(queryName, phase, zodError) {
42
+ super(`Validation failed for query "${queryName}" ${phase}: ${zodError.message}`);
43
+ this.queryName = queryName;
44
+ this.phase = phase;
45
+ this.zodError = zodError;
46
+ this.name = "QueryValidationError";
47
+ }
48
+ };
49
+ /**
50
+ * Error thrown when signal input validation fails
51
+ */
52
+ var SignalValidationError = class extends TypedClientError {
53
+ constructor(signalName, zodError) {
54
+ super(`Validation failed for signal "${signalName}" input: ${zodError.message}`);
55
+ this.signalName = signalName;
56
+ this.zodError = zodError;
57
+ this.name = "SignalValidationError";
58
+ }
59
+ };
60
+ /**
61
+ * Error thrown when update input or output validation fails
62
+ */
63
+ var UpdateValidationError = class extends TypedClientError {
64
+ constructor(updateName, phase, zodError) {
65
+ super(`Validation failed for update "${updateName}" ${phase}: ${zodError.message}`);
66
+ this.updateName = updateName;
67
+ this.phase = phase;
68
+ this.zodError = zodError;
69
+ this.name = "UpdateValidationError";
70
+ }
71
+ };
72
+
73
+ //#endregion
74
+ //#region src/client.ts
75
+ /**
76
+ * Typed Temporal client based on a contract
77
+ *
78
+ * Provides type-safe methods to start and execute workflows
79
+ * defined in the contract.
80
+ */
81
+ var TypedClient = class TypedClient {
82
+ constructor(contract, client) {
83
+ this.contract = contract;
84
+ this.client = client;
85
+ }
86
+ /**
87
+ * Create a typed Temporal client from a contract
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * const connection = await Connection.connect();
92
+ * const client = TypedClient.create(myContract, {
93
+ * connection,
94
+ * namespace: 'default',
95
+ * });
96
+ *
97
+ * const result = await client.executeWorkflow('processOrder', {
98
+ * workflowId: 'order-123',
99
+ * args: [...],
100
+ * });
101
+ * ```
102
+ */
103
+ static create(contract, options) {
104
+ return new TypedClient(contract, new Client(options));
105
+ }
106
+ /**
107
+ * Start a workflow and return a typed handle
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * const handle = await client.startWorkflow('processOrder', {
112
+ * workflowId: 'order-123',
113
+ * args: ['ORD-123', 'CUST-456', [{ productId: 'PROD-1', quantity: 2 }]],
114
+ * workflowExecutionTimeout: '1 day',
115
+ * retry: { maximumAttempts: 3 },
116
+ * });
117
+ *
118
+ * const result = await handle.result();
119
+ * ```
120
+ */
121
+ async startWorkflow(workflowName, { args, ...temporalOptions }) {
122
+ const definition = this.contract.workflows[workflowName];
123
+ if (!definition) throw new WorkflowNotFoundError(String(workflowName));
124
+ let validatedInput;
125
+ try {
126
+ validatedInput = definition.input.parse(args);
127
+ } catch (error) {
128
+ if (error instanceof ZodError) throw new WorkflowValidationError(String(workflowName), "input", error);
129
+ throw error;
130
+ }
131
+ const handle = await this.client.workflow.start(workflowName, {
132
+ ...temporalOptions,
133
+ taskQueue: this.contract.taskQueue,
134
+ args: [validatedInput]
135
+ });
136
+ return this.createTypedHandle(handle, definition);
137
+ }
138
+ /**
139
+ * Execute a workflow (start and wait for result)
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * const result = await client.executeWorkflow('processOrder', {
144
+ * workflowId: 'order-123',
145
+ * args: ['ORD-123', 'CUST-456', [{ productId: 'PROD-1', quantity: 2 }]],
146
+ * workflowExecutionTimeout: '1 day',
147
+ * retry: { maximumAttempts: 3 },
148
+ * });
149
+ *
150
+ * console.log(result.status); // fully typed!
151
+ * ```
152
+ */
153
+ async executeWorkflow(workflowName, { args, ...temporalOptions }) {
154
+ const definition = this.contract.workflows[workflowName];
155
+ if (!definition) throw new WorkflowNotFoundError(String(workflowName));
156
+ let validatedInput;
157
+ try {
158
+ validatedInput = definition.input.parse(args);
159
+ } catch (error) {
160
+ if (error instanceof ZodError) throw new WorkflowValidationError(String(workflowName), "input", error);
161
+ throw error;
162
+ }
163
+ const result = await this.client.workflow.execute(workflowName, {
164
+ ...temporalOptions,
165
+ taskQueue: this.contract.taskQueue,
166
+ args: [validatedInput]
167
+ });
168
+ try {
169
+ return definition.output.parse(result);
170
+ } catch (error) {
171
+ if (error instanceof ZodError) throw new WorkflowValidationError(String(workflowName), "output", error);
172
+ throw error;
173
+ }
174
+ }
175
+ /**
176
+ * Get a handle to an existing workflow
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * const handle = await client.getHandle('processOrder', 'order-123');
181
+ * const result = await handle.result();
182
+ * ```
183
+ */
184
+ async getHandle(workflowName, workflowId) {
185
+ const definition = this.contract.workflows[workflowName];
186
+ if (!definition) throw new WorkflowNotFoundError(String(workflowName));
187
+ const handle = this.client.workflow.getHandle(workflowId);
188
+ return this.createTypedHandle(handle, definition);
189
+ }
190
+ createTypedHandle(handle, definition) {
191
+ const queries = {};
192
+ for (const [queryName, queryDef] of Object.entries(definition.queries ?? {})) queries[queryName] = async (args) => {
193
+ let validatedInput;
194
+ try {
195
+ validatedInput = queryDef.input.parse(args);
196
+ } catch (error) {
197
+ if (error instanceof ZodError) throw new QueryValidationError(queryName, "input", error);
198
+ throw error;
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
+ }
207
+ };
208
+ const signals = {};
209
+ for (const [signalName, signalDef] of Object.entries(definition.signals ?? {})) signals[signalName] = async (args) => {
210
+ let validatedInput;
211
+ try {
212
+ validatedInput = signalDef.input.parse(args);
213
+ } catch (error) {
214
+ if (error instanceof ZodError) throw new SignalValidationError(signalName, error);
215
+ throw error;
216
+ }
217
+ await handle.signal(signalName, validatedInput);
218
+ };
219
+ const updates = {};
220
+ for (const [updateName, updateDef] of Object.entries(definition.updates ?? {})) updates[updateName] = async (args) => {
221
+ let validatedInput;
222
+ try {
223
+ validatedInput = updateDef.input.parse(args);
224
+ } catch (error) {
225
+ if (error instanceof ZodError) throw new UpdateValidationError(updateName, "input", error);
226
+ throw error;
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
+ }
235
+ };
236
+ return {
237
+ workflowId: handle.workflowId,
238
+ queries,
239
+ signals,
240
+ updates,
241
+ result: async () => {
242
+ const result = await handle.result();
243
+ try {
244
+ return definition.output.parse(result);
245
+ } catch (error) {
246
+ if (error instanceof ZodError) throw new WorkflowValidationError(handle.workflowId, "output", error);
247
+ throw error;
248
+ }
249
+ },
250
+ terminate: async (reason) => {
251
+ await handle.terminate(reason);
252
+ },
253
+ cancel: async () => {
254
+ await handle.cancel();
255
+ },
256
+ describe: () => handle.describe(),
257
+ fetchHistory: () => handle.fetchHistory()
258
+ };
259
+ }
260
+ };
261
+
262
+ //#endregion
263
+ export { QueryValidationError, SignalValidationError, TypedClient, TypedClientError, UpdateValidationError, WorkflowNotFoundError, WorkflowValidationError };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@temporal-contract/client",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "Client utilities for consuming temporal-contract workflows",
6
+ "homepage": "https://github.com/btravers/temporal-contract#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/btravers/temporal-contract/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/btravers/temporal-contract.git",
13
+ "directory": "packages/client"
14
+ },
15
+ "author": "Benoit TRAVERS <benoit.travers.frgmail.com>",
16
+ "license": "MIT",
17
+ "keywords": [
18
+ "temporal",
19
+ "typescript",
20
+ "contract",
21
+ "client"
22
+ ],
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.mjs",
25
+ "types": "./dist/index.d.mts",
26
+ "exports": {
27
+ ".": {
28
+ "import": {
29
+ "types": "./dist/index.d.mts",
30
+ "default": "./dist/index.mjs"
31
+ },
32
+ "require": {
33
+ "types": "./dist/index.d.cts",
34
+ "default": "./dist/index.cjs"
35
+ }
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "dependencies": {
40
+ "@temporal-contract/contract": "0.0.1"
41
+ },
42
+ "devDependencies": {
43
+ "@temporalio/client": "1.13.2",
44
+ "@types/node": "24.10.2",
45
+ "tsdown": "0.17.2",
46
+ "type-fest": "5.3.1",
47
+ "typescript": "5.9.3",
48
+ "vitest": "4.0.15",
49
+ "zod": "4.1.13",
50
+ "@temporal-contract/tsconfig": "0.0.1"
51
+ },
52
+ "peerDependencies": {
53
+ "@temporalio/client": ">=1.13.0 <2.0.0",
54
+ "zod": "^4.0.0"
55
+ },
56
+ "scripts": {
57
+ "dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
58
+ "build": "tsdown src/index.ts --format cjs,esm --dts --clean",
59
+ "typecheck": "tsc --noEmit",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest"
62
+ }
63
+ }