@temporal-contract/client 0.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +516 -159
- package/dist/index.d.cts +286 -47
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +286 -47
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +511 -158
- package/dist/index.mjs.map +1 -1
- package/package.json +30 -27
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { TypedSearchAttributes, WorkflowNotFoundError as WorkflowNotFoundError$1, defineSearchAttributeKey } from "@temporalio/common";
|
|
2
|
+
import { ResultAsync, err, ok } from "neverthrow";
|
|
3
|
+
import { WorkflowExecutionAlreadyStartedError, WorkflowFailedError as WorkflowFailedError$1 } from "@temporalio/client";
|
|
3
4
|
//#region src/errors.ts
|
|
4
5
|
/**
|
|
5
6
|
* Base class for all typed client errors with boxed pattern
|
|
@@ -32,11 +33,117 @@ var WorkflowNotFoundError = class extends TypedClientError {
|
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
35
|
/**
|
|
36
|
+
* Discriminated variant of {@link RuntimeClientError} surfaced when starting
|
|
37
|
+
* a workflow collides with an existing execution — Temporal's
|
|
38
|
+
* `WorkflowExecutionAlreadyStartedError`. The most common cause is a
|
|
39
|
+
* workflowId reuse policy that rejects duplicates while a previous run is
|
|
40
|
+
* still in retention.
|
|
41
|
+
*
|
|
42
|
+
* Distinguishing this from `RuntimeClientError` lets idempotent callers
|
|
43
|
+
* branch on it explicitly (e.g. fetch the existing handle and continue)
|
|
44
|
+
* without inspecting `error.cause` against a Temporal SDK class.
|
|
45
|
+
*/
|
|
46
|
+
var WorkflowAlreadyStartedError = class extends TypedClientError {
|
|
47
|
+
constructor(workflowType, workflowId, cause) {
|
|
48
|
+
super(`Workflow "${workflowType}" with ID "${workflowId}" is already started or in retention.`);
|
|
49
|
+
this.workflowType = workflowType;
|
|
50
|
+
this.workflowId = workflowId;
|
|
51
|
+
this.cause = cause;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Discriminated variant of {@link RuntimeClientError} surfaced when an
|
|
56
|
+
* operation targets a workflow execution that doesn't exist in the
|
|
57
|
+
* namespace — Temporal's `WorkflowNotFoundError` (distinct from this
|
|
58
|
+
* package's contract-level {@link WorkflowNotFoundError}).
|
|
59
|
+
*
|
|
60
|
+
* Returned from:
|
|
61
|
+
* - handle methods: `signal`, `query`, `executeUpdate`, `result`,
|
|
62
|
+
* `terminate`, `cancel`, `describe`, `fetchHistory`
|
|
63
|
+
* - `executeWorkflow` (when the underlying execute call hits a missing
|
|
64
|
+
* execution mid-flight)
|
|
65
|
+
*/
|
|
66
|
+
var WorkflowExecutionNotFoundError = class extends TypedClientError {
|
|
67
|
+
constructor(workflowId, runId, cause) {
|
|
68
|
+
super(`Workflow execution "${workflowId}"${runId ? ` (run "${runId}")` : ""} not found in namespace.`);
|
|
69
|
+
this.workflowId = workflowId;
|
|
70
|
+
this.runId = runId;
|
|
71
|
+
this.cause = cause;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Discriminated variant of {@link RuntimeClientError} surfaced when waiting
|
|
76
|
+
* on a workflow's result and the workflow completes with a failure —
|
|
77
|
+
* Temporal's `WorkflowFailedError`.
|
|
78
|
+
*
|
|
79
|
+
* `cause` is the *unwrapped* underlying failure (typically an
|
|
80
|
+
* `ApplicationFailure`, `CancelledFailure`, `TerminatedFailure`, or
|
|
81
|
+
* `TimeoutFailure`) lifted from Temporal's wrapper, so callers can branch
|
|
82
|
+
* on the failure category in one step (`err.cause instanceof
|
|
83
|
+
* ApplicationFailure`) instead of unwrapping twice via the SDK wrapper.
|
|
84
|
+
*
|
|
85
|
+
* Returned from `executeWorkflow` and `handle.result()`.
|
|
86
|
+
*/
|
|
87
|
+
var WorkflowFailedError = class extends TypedClientError {
|
|
88
|
+
constructor(workflowId, cause) {
|
|
89
|
+
const causeMessage = cause instanceof Error ? cause.message : String(cause ?? "unknown failure");
|
|
90
|
+
super(`Workflow "${workflowId}" completed with failure: ${causeMessage}`);
|
|
91
|
+
this.workflowId = workflowId;
|
|
92
|
+
this.cause = cause;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Pattern for string keys safe to render with dot notation. A "safe" key is a
|
|
97
|
+
* JavaScript identifier (letters/digits/underscore/$, not starting with a
|
|
98
|
+
* digit). Anything else — keys containing dots, spaces, leading digits, the
|
|
99
|
+
* empty string, the literal string `"0"` etc. — gets bracket-quoted so the
|
|
100
|
+
* path is unambiguous.
|
|
101
|
+
*
|
|
102
|
+
* This helper is intentionally duplicated with the worker package so each
|
|
103
|
+
* entry point is self-contained; keep the two copies in sync.
|
|
104
|
+
*/
|
|
105
|
+
const SAFE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
106
|
+
/**
|
|
107
|
+
* Render a Standard Schema {@link StandardSchemaV1.Issue} into a human-readable
|
|
108
|
+
* string that includes the failing field's path.
|
|
109
|
+
*
|
|
110
|
+
* Example output:
|
|
111
|
+
* - `at items[0].quantity: Expected number, received undefined`
|
|
112
|
+
* - `at customerId: Expected string, received undefined`
|
|
113
|
+
* - `at user["first name"]: Expected string, received undefined`
|
|
114
|
+
* - `Validation error` *(no path)*
|
|
115
|
+
*
|
|
116
|
+
* Path segments come either as bare `PropertyKey` values or as
|
|
117
|
+
* `{ key: PropertyKey }` objects (per the spec); both are normalized.
|
|
118
|
+
* - Numeric keys → `[N]`
|
|
119
|
+
* - String keys that are valid JS identifiers → bare (first) or `.key`
|
|
120
|
+
* - String keys that aren't valid identifiers → `["..."]` with JSON-style
|
|
121
|
+
* escaping (handles dots, spaces, leading digits, the empty string, the
|
|
122
|
+
* literal string `"0"`, embedded quotes, etc.)
|
|
123
|
+
* - Symbol / other `PropertyKey` → `[Symbol(name)]`
|
|
124
|
+
*/
|
|
125
|
+
function formatIssue(issue) {
|
|
126
|
+
if (issue.path === void 0 || issue.path.length === 0) return issue.message;
|
|
127
|
+
let path = "";
|
|
128
|
+
for (let i = 0; i < issue.path.length; i++) {
|
|
129
|
+
const segment = issue.path[i];
|
|
130
|
+
const key = segment !== null && typeof segment === "object" && "key" in segment ? segment.key : segment;
|
|
131
|
+
if (typeof key === "number") path += `[${key}]`;
|
|
132
|
+
else if (typeof key === "string" && SAFE_IDENTIFIER.test(key)) path += i === 0 ? key : `.${key}`;
|
|
133
|
+
else if (typeof key === "string") path += `[${JSON.stringify(key)}]`;
|
|
134
|
+
else path += `[${String(key)}]`;
|
|
135
|
+
}
|
|
136
|
+
return `at ${path}: ${issue.message}`;
|
|
137
|
+
}
|
|
138
|
+
function summarizeIssues(issues) {
|
|
139
|
+
return issues.map(formatIssue).join("; ");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
35
142
|
* Thrown when workflow input or output validation fails
|
|
36
143
|
*/
|
|
37
144
|
var WorkflowValidationError = class extends TypedClientError {
|
|
38
145
|
constructor(workflowName, direction, issues) {
|
|
39
|
-
super(`Validation failed for workflow "${workflowName}" ${direction}: ${
|
|
146
|
+
super(`Validation failed for workflow "${workflowName}" ${direction}: ${summarizeIssues(issues)}`);
|
|
40
147
|
this.workflowName = workflowName;
|
|
41
148
|
this.direction = direction;
|
|
42
149
|
this.issues = issues;
|
|
@@ -47,7 +154,7 @@ var WorkflowValidationError = class extends TypedClientError {
|
|
|
47
154
|
*/
|
|
48
155
|
var QueryValidationError = class extends TypedClientError {
|
|
49
156
|
constructor(queryName, direction, issues) {
|
|
50
|
-
super(`Validation failed for query "${queryName}" ${direction}: ${
|
|
157
|
+
super(`Validation failed for query "${queryName}" ${direction}: ${summarizeIssues(issues)}`);
|
|
51
158
|
this.queryName = queryName;
|
|
52
159
|
this.direction = direction;
|
|
53
160
|
this.issues = issues;
|
|
@@ -58,7 +165,7 @@ var QueryValidationError = class extends TypedClientError {
|
|
|
58
165
|
*/
|
|
59
166
|
var SignalValidationError = class extends TypedClientError {
|
|
60
167
|
constructor(signalName, issues) {
|
|
61
|
-
super(`Validation failed for signal "${signalName}": ${
|
|
168
|
+
super(`Validation failed for signal "${signalName}": ${summarizeIssues(issues)}`);
|
|
62
169
|
this.signalName = signalName;
|
|
63
170
|
this.issues = issues;
|
|
64
171
|
}
|
|
@@ -68,28 +175,221 @@ var SignalValidationError = class extends TypedClientError {
|
|
|
68
175
|
*/
|
|
69
176
|
var UpdateValidationError = class extends TypedClientError {
|
|
70
177
|
constructor(updateName, direction, issues) {
|
|
71
|
-
super(`Validation failed for update "${updateName}" ${direction}: ${
|
|
178
|
+
super(`Validation failed for update "${updateName}" ${direction}: ${summarizeIssues(issues)}`);
|
|
72
179
|
this.updateName = updateName;
|
|
73
180
|
this.direction = direction;
|
|
74
181
|
this.issues = issues;
|
|
75
182
|
}
|
|
76
183
|
};
|
|
77
|
-
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/internal.ts
|
|
186
|
+
/**
|
|
187
|
+
* Internal helpers shared across the client package's modules.
|
|
188
|
+
*
|
|
189
|
+
* Not part of the public API — this module is not listed in the package's
|
|
190
|
+
* `exports` map, so consumers can't import from `@temporal-contract/client/internal`.
|
|
191
|
+
* In-package modules and tests import it directly via relative path.
|
|
192
|
+
*/
|
|
193
|
+
/**
|
|
194
|
+
* Wrap an async result-producing function in a `ResultAsync`, catching any
|
|
195
|
+
* unhandled rejection as a `RuntimeClientError("unexpected", error)`.
|
|
196
|
+
*
|
|
197
|
+
* The work function is expected to handle its own domain errors and return
|
|
198
|
+
* an `err(...)` for them; the catch here is a safety net for thrown
|
|
199
|
+
* exceptions the work didn't anticipate.
|
|
200
|
+
*
|
|
201
|
+
* Used by `client.ts` (workflow operations) and `schedule.ts` (schedule
|
|
202
|
+
* operations) so the unexpected-rejection shape is identical across the
|
|
203
|
+
* typed client surface.
|
|
204
|
+
*/
|
|
205
|
+
function makeResultAsync(work) {
|
|
206
|
+
return new ResultAsync(work().catch((e) => err(new RuntimeClientError("unexpected", e))));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Map a thrown error from `client.workflow.start` / `signalWithStart` into
|
|
210
|
+
* the discriminated union surfaced by the typed client. Specifically
|
|
211
|
+
* recognizes Temporal's `WorkflowExecutionAlreadyStartedError`; everything
|
|
212
|
+
* else falls through to {@link RuntimeClientError}.
|
|
213
|
+
*/
|
|
214
|
+
function classifyStartError(operation, error) {
|
|
215
|
+
if (error instanceof WorkflowExecutionAlreadyStartedError) return new WorkflowAlreadyStartedError(error.workflowType, error.workflowId, error);
|
|
216
|
+
return new RuntimeClientError(operation, error);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Map a thrown error from a workflow handle method (signal, query,
|
|
220
|
+
* executeUpdate, terminate, cancel, describe, fetchHistory) into the
|
|
221
|
+
* discriminated union surfaced by the typed client. Recognizes Temporal's
|
|
222
|
+
* `WorkflowNotFoundError`; everything else falls through to
|
|
223
|
+
* {@link RuntimeClientError}.
|
|
224
|
+
*
|
|
225
|
+
* `fallbackWorkflowId` is used when Temporal's error carries an empty
|
|
226
|
+
* `workflowId` (it normalizes missing IDs to the empty string), so the
|
|
227
|
+
* surfaced error always identifies the targeted execution.
|
|
228
|
+
*/
|
|
229
|
+
function classifyHandleError(operation, error, fallbackWorkflowId) {
|
|
230
|
+
if (error instanceof WorkflowNotFoundError$1) return new WorkflowExecutionNotFoundError(error.workflowId || fallbackWorkflowId, error.runId, error);
|
|
231
|
+
return new RuntimeClientError(operation, error);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Map a thrown error from `handle.result()` / `client.workflow.execute()`
|
|
235
|
+
* (the latter when waiting on the result phase). Recognizes Temporal's
|
|
236
|
+
* `WorkflowFailedError` and `WorkflowNotFoundError`; everything else falls
|
|
237
|
+
* through to {@link RuntimeClientError}.
|
|
238
|
+
*
|
|
239
|
+
* Temporal's `WorkflowFailedError` is itself a wrapper — the actionable
|
|
240
|
+
* failure (ApplicationFailure, CancelledFailure, TerminatedFailure, etc.)
|
|
241
|
+
* lives on its `cause` field. We forward that inner cause directly so
|
|
242
|
+
* consumers can match `err.cause` against the underlying failure class
|
|
243
|
+
* without an extra unwrap step. (If Temporal's cause is `undefined`, our
|
|
244
|
+
* `cause` is too — same shape as before.)
|
|
245
|
+
*/
|
|
246
|
+
function classifyResultError(operation, error, workflowId) {
|
|
247
|
+
if (error instanceof WorkflowFailedError$1) return new WorkflowFailedError(workflowId, error.cause);
|
|
248
|
+
if (error instanceof WorkflowNotFoundError$1) return new WorkflowExecutionNotFoundError(error.workflowId || workflowId, error.runId, error);
|
|
249
|
+
return new RuntimeClientError(operation, error);
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/schedule.ts
|
|
253
|
+
/**
|
|
254
|
+
* Typed wrapper around Temporal's `ScheduleClient`. Exposed as
|
|
255
|
+
* `typedClient.schedule` — keeps the typed-client surface organized the
|
|
256
|
+
* same way Temporal's own `Client.schedule` does.
|
|
257
|
+
*/
|
|
258
|
+
var TypedScheduleClient = class {
|
|
259
|
+
constructor(contract, scheduleClient) {
|
|
260
|
+
this.contract = contract;
|
|
261
|
+
this.scheduleClient = scheduleClient;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Create a new schedule that, on each fire, starts the named contract
|
|
265
|
+
* workflow with validated args.
|
|
266
|
+
*
|
|
267
|
+
* Validates `args` against the workflow's input schema before dispatching
|
|
268
|
+
* the create request to Temporal. The workflow's `taskQueue` and
|
|
269
|
+
* `workflowType` are pulled from the contract automatically; the typed
|
|
270
|
+
* options shape omits them so call sites don't have to repeat themselves.
|
|
271
|
+
*/
|
|
272
|
+
create(workflowName, options) {
|
|
273
|
+
const work = async () => {
|
|
274
|
+
const definition = this.contract.workflows[workflowName];
|
|
275
|
+
if (!definition) return err(new WorkflowNotFoundError(String(workflowName), Object.keys(this.contract.workflows)));
|
|
276
|
+
const inputResult = await definition.input["~standard"].validate(options.args);
|
|
277
|
+
if (inputResult.issues) return err(new WorkflowValidationError(String(workflowName), "input", inputResult.issues));
|
|
278
|
+
try {
|
|
279
|
+
const overrides = options.action ?? {};
|
|
280
|
+
const action = {
|
|
281
|
+
type: "startWorkflow",
|
|
282
|
+
workflowType: workflowName,
|
|
283
|
+
taskQueue: this.contract.taskQueue,
|
|
284
|
+
args: [inputResult.value],
|
|
285
|
+
...overrides.workflowId !== void 0 ? { workflowId: overrides.workflowId } : {},
|
|
286
|
+
...overrides.workflowExecutionTimeout !== void 0 ? { workflowExecutionTimeout: overrides.workflowExecutionTimeout } : {},
|
|
287
|
+
...overrides.workflowRunTimeout !== void 0 ? { workflowRunTimeout: overrides.workflowRunTimeout } : {},
|
|
288
|
+
...overrides.workflowTaskTimeout !== void 0 ? { workflowTaskTimeout: overrides.workflowTaskTimeout } : {},
|
|
289
|
+
...overrides.retry !== void 0 ? { retry: overrides.retry } : {},
|
|
290
|
+
...overrides.memo !== void 0 ? { memo: overrides.memo } : {},
|
|
291
|
+
...overrides.staticDetails !== void 0 ? { staticDetails: overrides.staticDetails } : {},
|
|
292
|
+
...overrides.staticSummary !== void 0 ? { staticSummary: overrides.staticSummary } : {}
|
|
293
|
+
};
|
|
294
|
+
return ok(wrapScheduleHandle(await this.scheduleClient.create({
|
|
295
|
+
scheduleId: options.scheduleId,
|
|
296
|
+
spec: options.spec,
|
|
297
|
+
action,
|
|
298
|
+
...options.policies !== void 0 ? { policies: options.policies } : {},
|
|
299
|
+
...options.state !== void 0 ? { state: options.state } : {},
|
|
300
|
+
...options.memo !== void 0 ? { memo: options.memo } : {}
|
|
301
|
+
})));
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return err(new RuntimeClientError("schedule.create", error));
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
return makeResultAsync(work);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Get a typed handle to an existing schedule. Does not validate that the
|
|
310
|
+
* schedule exists — handle methods (`describe`, `pause`, etc.) will
|
|
311
|
+
* surface a `RuntimeClientError` if the underlying ID is unknown.
|
|
312
|
+
*/
|
|
313
|
+
getHandle(scheduleId) {
|
|
314
|
+
return wrapScheduleHandle(this.scheduleClient.getHandle(scheduleId));
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
function wrapScheduleHandle(handle) {
|
|
318
|
+
return {
|
|
319
|
+
scheduleId: handle.scheduleId,
|
|
320
|
+
pause: (note) => ResultAsync.fromPromise(handle.pause(note), (error) => new RuntimeClientError("schedule.pause", error)).map(() => void 0),
|
|
321
|
+
unpause: (note) => ResultAsync.fromPromise(handle.unpause(note), (error) => new RuntimeClientError("schedule.unpause", error)).map(() => void 0),
|
|
322
|
+
trigger: (overlap) => ResultAsync.fromPromise(handle.trigger(overlap), (error) => new RuntimeClientError("schedule.trigger", error)).map(() => void 0),
|
|
323
|
+
delete: () => ResultAsync.fromPromise(handle.delete(), (error) => new RuntimeClientError("schedule.delete", error)).map(() => void 0),
|
|
324
|
+
describe: () => ResultAsync.fromPromise(handle.describe(), (error) => new RuntimeClientError("schedule.describe", error))
|
|
325
|
+
};
|
|
326
|
+
}
|
|
78
327
|
//#endregion
|
|
79
328
|
//#region src/client.ts
|
|
80
329
|
/**
|
|
81
|
-
*
|
|
330
|
+
* Translate the contract's typed `searchAttributes` map (declared
|
|
331
|
+
* name → value) into a Temporal `TypedSearchAttributes` instance, so the
|
|
332
|
+
* Temporal client honours indexing when starting the workflow.
|
|
333
|
+
*
|
|
334
|
+
* Workflows without a `searchAttributes` block (or callers passing no
|
|
335
|
+
* values) skip the conversion entirely and return `undefined`, matching
|
|
336
|
+
* the Temporal SDK's "absent ≠ empty" semantics.
|
|
337
|
+
*/
|
|
338
|
+
function toTypedSearchAttributes(workflowDef, values) {
|
|
339
|
+
if (!values || !workflowDef.searchAttributes) return void 0;
|
|
340
|
+
const pairs = [];
|
|
341
|
+
for (const [name, value] of Object.entries(values)) {
|
|
342
|
+
if (value === void 0) continue;
|
|
343
|
+
const def = workflowDef.searchAttributes[name];
|
|
344
|
+
if (!def) continue;
|
|
345
|
+
const key = defineSearchAttributeKey(name, def.kind);
|
|
346
|
+
pairs.push({
|
|
347
|
+
key,
|
|
348
|
+
value
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
return pairs.length > 0 ? new TypedSearchAttributes(pairs) : void 0;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Typed Temporal client with neverthrow Result/ResultAsync pattern based on a contract
|
|
82
355
|
*
|
|
83
356
|
* Provides type-safe methods to start and execute workflows
|
|
84
357
|
* defined in the contract, with explicit error handling using Result pattern.
|
|
85
358
|
*/
|
|
86
359
|
var TypedClient = class TypedClient {
|
|
360
|
+
/**
|
|
361
|
+
* Typed wrapper around Temporal's `client.schedule.create(...)` and
|
|
362
|
+
* related lifecycle methods. Fires the underlying `startWorkflow` action
|
|
363
|
+
* with args validated against the contract's input schema.
|
|
364
|
+
*
|
|
365
|
+
* **Requires `@temporalio/client` 1.16+.** The Schedule API was added in
|
|
366
|
+
* 1.16; on older versions this property is unset and any access throws.
|
|
367
|
+
* The package's peer dep is pinned to `^1.16.0` so the standard install
|
|
368
|
+
* paths surface a peer-dependency warning rather than a runtime crash.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* const result = await client.schedule.create("processOrder", {
|
|
373
|
+
* scheduleId: "daily-sweep",
|
|
374
|
+
* spec: { cronExpressions: ["0 2 * * *"] },
|
|
375
|
+
* args: { orderId: "sweep" },
|
|
376
|
+
* });
|
|
377
|
+
*
|
|
378
|
+
* result.match(
|
|
379
|
+
* async (handle) => { await handle.pause("maintenance"); },
|
|
380
|
+
* (error) => console.error("schedule create failed", error),
|
|
381
|
+
* );
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
schedule;
|
|
87
385
|
constructor(contract, client) {
|
|
88
386
|
this.contract = contract;
|
|
89
387
|
this.client = client;
|
|
388
|
+
if (!client.schedule) throw new Error("TypedClient requires @temporalio/client >= 1.16 (the Schedule API was added in 1.16). Found a Client instance without a `schedule` property — please upgrade.");
|
|
389
|
+
this.schedule = new TypedScheduleClient(contract, client.schedule);
|
|
90
390
|
}
|
|
91
391
|
/**
|
|
92
|
-
* Create a typed Temporal client with
|
|
392
|
+
* Create a typed Temporal client with neverthrow pattern from a contract
|
|
93
393
|
*
|
|
94
394
|
* @example
|
|
95
395
|
* ```ts
|
|
@@ -102,17 +402,17 @@ var TypedClient = class TypedClient {
|
|
|
102
402
|
* args: { ... },
|
|
103
403
|
* });
|
|
104
404
|
*
|
|
105
|
-
* result.match(
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
405
|
+
* result.match(
|
|
406
|
+
* (output) => console.log('Success:', output),
|
|
407
|
+
* (error) => console.error('Failed:', error),
|
|
408
|
+
* );
|
|
109
409
|
* ```
|
|
110
410
|
*/
|
|
111
411
|
static create(contract, client) {
|
|
112
412
|
return new TypedClient(contract, client);
|
|
113
413
|
}
|
|
114
414
|
/**
|
|
115
|
-
* Start a workflow and return a typed handle with
|
|
415
|
+
* Start a workflow and return a typed handle with ResultAsync pattern
|
|
116
416
|
*
|
|
117
417
|
* @example
|
|
118
418
|
* ```ts
|
|
@@ -123,39 +423,94 @@ var TypedClient = class TypedClient {
|
|
|
123
423
|
* retry: { maximumAttempts: 3 },
|
|
124
424
|
* });
|
|
125
425
|
*
|
|
126
|
-
* handleResult.match(
|
|
127
|
-
*
|
|
426
|
+
* handleResult.match(
|
|
427
|
+
* async (handle) => {
|
|
128
428
|
* const result = await handle.result();
|
|
129
429
|
* // ... handle result
|
|
130
430
|
* },
|
|
131
|
-
*
|
|
431
|
+
* (error) => console.error('Failed to start:', error),
|
|
432
|
+
* );
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
startWorkflow(workflowName, { args, searchAttributes, ...temporalOptions }) {
|
|
436
|
+
const work = async () => {
|
|
437
|
+
const definition = this.contract.workflows[workflowName];
|
|
438
|
+
if (!definition) return err(createWorkflowNotFoundError(workflowName, this.contract));
|
|
439
|
+
const inputResult = await definition.input["~standard"].validate(args);
|
|
440
|
+
if (inputResult.issues) return err(createWorkflowValidationError(workflowName, "input", inputResult.issues));
|
|
441
|
+
const typedSearchAttributes = toTypedSearchAttributes(definition, searchAttributes);
|
|
442
|
+
try {
|
|
443
|
+
const handle = await this.client.workflow.start(workflowName, {
|
|
444
|
+
...temporalOptions,
|
|
445
|
+
taskQueue: this.contract.taskQueue,
|
|
446
|
+
args: [inputResult.value],
|
|
447
|
+
...typedSearchAttributes ? { typedSearchAttributes } : {}
|
|
448
|
+
});
|
|
449
|
+
return ok(this.createTypedHandle(handle, definition));
|
|
450
|
+
} catch (error) {
|
|
451
|
+
return err(classifyStartError("startWorkflow", error));
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
return makeResultAsync(work);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Send a signal to a workflow, starting it first if it doesn't already exist.
|
|
458
|
+
*
|
|
459
|
+
* Validates both halves of the call against the contract:
|
|
460
|
+
* - `args` against the workflow's input schema
|
|
461
|
+
* - `signalArgs` against the named signal's input schema
|
|
462
|
+
*
|
|
463
|
+
* Returns a `TypedWorkflowHandleWithSignaledRunId` — the same shape as
|
|
464
|
+
* `startWorkflow`'s handle, plus a `signaledRunId` field for correlating
|
|
465
|
+
* the signal with the (possibly pre-existing) workflow execution chain.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* const result = await client.signalWithStart('processOrder', {
|
|
470
|
+
* workflowId: 'order-123',
|
|
471
|
+
* args: { orderId: 'ORD-123', customerId: 'CUST-1' },
|
|
472
|
+
* signalName: 'cancel',
|
|
473
|
+
* signalArgs: { reason: 'duplicate' },
|
|
132
474
|
* });
|
|
475
|
+
*
|
|
476
|
+
* result.match(
|
|
477
|
+
* (handle) => console.log('signaled run', handle.signaledRunId),
|
|
478
|
+
* (error) => console.error('signalWithStart failed', error),
|
|
479
|
+
* );
|
|
133
480
|
* ```
|
|
134
481
|
*/
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
482
|
+
signalWithStart(workflowName, { args, signalName, signalArgs, searchAttributes, ...temporalOptions }) {
|
|
483
|
+
const work = async () => {
|
|
484
|
+
const definition = this.contract.workflows[workflowName];
|
|
485
|
+
if (!definition) return err(createWorkflowNotFoundError(workflowName, this.contract));
|
|
486
|
+
const inputResult = await definition.input["~standard"].validate(args);
|
|
487
|
+
if (inputResult.issues) return err(createWorkflowValidationError(workflowName, "input", inputResult.issues));
|
|
488
|
+
const signalDef = definition.signals?.[signalName];
|
|
489
|
+
if (!signalDef) return err(new SignalValidationError(signalName, [{ message: `Signal "${signalName}" is not declared on workflow "${String(workflowName)}".` }]));
|
|
490
|
+
const signalInputResult = await signalDef.input["~standard"].validate(signalArgs);
|
|
491
|
+
if (signalInputResult.issues) return err(new SignalValidationError(signalName, signalInputResult.issues));
|
|
492
|
+
const typedSearchAttributes = toTypedSearchAttributes(definition, searchAttributes);
|
|
493
|
+
try {
|
|
494
|
+
const handle = await this.client.workflow.signalWithStart(workflowName, {
|
|
495
|
+
...temporalOptions,
|
|
496
|
+
taskQueue: this.contract.taskQueue,
|
|
497
|
+
args: [inputResult.value],
|
|
498
|
+
signal: signalName,
|
|
499
|
+
signalArgs: [signalInputResult.value],
|
|
500
|
+
...typedSearchAttributes ? { typedSearchAttributes } : {}
|
|
501
|
+
});
|
|
502
|
+
return ok({
|
|
503
|
+
...this.createTypedHandle(handle, definition),
|
|
504
|
+
signaledRunId: handle.signaledRunId
|
|
505
|
+
});
|
|
506
|
+
} catch (error) {
|
|
507
|
+
return err(classifyStartError("signalWithStart", error));
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
return makeResultAsync(work);
|
|
156
511
|
}
|
|
157
512
|
/**
|
|
158
|
-
* Execute a workflow (start and wait for result) with
|
|
513
|
+
* Execute a workflow (start and wait for result) with ResultAsync pattern
|
|
159
514
|
*
|
|
160
515
|
* @example
|
|
161
516
|
* ```ts
|
|
@@ -166,146 +521,115 @@ var TypedClient = class TypedClient {
|
|
|
166
521
|
* retry: { maximumAttempts: 3 },
|
|
167
522
|
* });
|
|
168
523
|
*
|
|
169
|
-
* result.match(
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
524
|
+
* result.match(
|
|
525
|
+
* (output) => console.log('Order processed:', output.status),
|
|
526
|
+
* (error) => console.error('Processing failed:', error),
|
|
527
|
+
* );
|
|
173
528
|
* ```
|
|
174
529
|
*/
|
|
175
|
-
executeWorkflow(workflowName, { args, ...temporalOptions }) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
530
|
+
executeWorkflow(workflowName, { args, searchAttributes, ...temporalOptions }) {
|
|
531
|
+
const work = async () => {
|
|
532
|
+
const definition = this.contract.workflows[workflowName];
|
|
533
|
+
if (!definition) return err(createWorkflowNotFoundError(workflowName, this.contract));
|
|
534
|
+
const inputResult = await definition.input["~standard"].validate(args);
|
|
535
|
+
if (inputResult.issues) return err(createWorkflowValidationError(workflowName, "input", inputResult.issues));
|
|
536
|
+
const typedSearchAttributes = toTypedSearchAttributes(definition, searchAttributes);
|
|
537
|
+
try {
|
|
538
|
+
const result = await this.client.workflow.execute(workflowName, {
|
|
539
|
+
...temporalOptions,
|
|
540
|
+
taskQueue: this.contract.taskQueue,
|
|
541
|
+
args: [inputResult.value],
|
|
542
|
+
...typedSearchAttributes ? { typedSearchAttributes } : {}
|
|
543
|
+
});
|
|
544
|
+
const outputResult = await definition.output["~standard"].validate(result);
|
|
545
|
+
if (outputResult.issues) return err(createWorkflowValidationError(workflowName, "output", outputResult.issues));
|
|
546
|
+
return ok(outputResult.value);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (error instanceof WorkflowExecutionAlreadyStartedError) return err(new WorkflowAlreadyStartedError(error.workflowType, error.workflowId, error));
|
|
549
|
+
if (error instanceof WorkflowFailedError$1) return err(new WorkflowFailedError(temporalOptions.workflowId, error.cause));
|
|
550
|
+
if (error instanceof WorkflowNotFoundError$1) return err(new WorkflowExecutionNotFoundError(error.workflowId || temporalOptions.workflowId, error.runId, error));
|
|
551
|
+
return err(createRuntimeClientError("executeWorkflow", error));
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
return makeResultAsync(work);
|
|
197
555
|
}
|
|
198
556
|
/**
|
|
199
|
-
* Get a handle to an existing workflow with
|
|
557
|
+
* Get a handle to an existing workflow with ResultAsync pattern
|
|
200
558
|
*
|
|
201
559
|
* @example
|
|
202
560
|
* ```ts
|
|
203
561
|
* const handleResult = await client.getHandle('processOrder', 'order-123');
|
|
204
|
-
* handleResult.match(
|
|
205
|
-
*
|
|
562
|
+
* handleResult.match(
|
|
563
|
+
* async (handle) => {
|
|
206
564
|
* const result = await handle.result();
|
|
207
565
|
* // ... handle result
|
|
208
566
|
* },
|
|
209
|
-
*
|
|
210
|
-
*
|
|
567
|
+
* (error) => console.error('Failed to get handle:', error),
|
|
568
|
+
* );
|
|
211
569
|
* ```
|
|
212
570
|
*/
|
|
213
571
|
getHandle(workflowName, workflowId) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
})().then(resolve).catch((e) => resolve(Result.Error(createRuntimeClientError("unexpected", e))));
|
|
226
|
-
});
|
|
572
|
+
const work = async () => {
|
|
573
|
+
const definition = this.contract.workflows[workflowName];
|
|
574
|
+
if (!definition) return err(createWorkflowNotFoundError(workflowName, this.contract));
|
|
575
|
+
try {
|
|
576
|
+
const handle = this.client.workflow.getHandle(workflowId);
|
|
577
|
+
return ok(this.createTypedHandle(handle, definition));
|
|
578
|
+
} catch (error) {
|
|
579
|
+
return err(createRuntimeClientError("getHandle", error));
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
return makeResultAsync(work);
|
|
227
583
|
}
|
|
228
584
|
createTypedHandle(workflowHandle, definition) {
|
|
229
|
-
const queries = {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
} catch (error) {
|
|
256
|
-
return Result.Error(createRuntimeClientError("signal", error));
|
|
257
|
-
}
|
|
258
|
-
})().then(resolve).catch((e) => resolve(Result.Error(createRuntimeClientError("unexpected", e))));
|
|
259
|
-
});
|
|
260
|
-
};
|
|
261
|
-
const updates = {};
|
|
262
|
-
for (const [updateName, updateDef] of Object.entries(definition.updates ?? {})) updates[updateName] = (args) => {
|
|
263
|
-
return Future.make((resolve) => {
|
|
264
|
-
(async () => {
|
|
265
|
-
const inputResult = await updateDef.input["~standard"].validate(args);
|
|
266
|
-
if (inputResult.issues) return Result.Error(new UpdateValidationError(updateName, "input", inputResult.issues));
|
|
267
|
-
try {
|
|
268
|
-
const result = await workflowHandle.executeUpdate(updateName, { args: [inputResult.value] });
|
|
269
|
-
const outputResult = await updateDef.output["~standard"].validate(result);
|
|
270
|
-
if (outputResult.issues) return Result.Error(new UpdateValidationError(updateName, "output", outputResult.issues));
|
|
271
|
-
return Result.Ok(outputResult.value);
|
|
272
|
-
} catch (error) {
|
|
273
|
-
return Result.Error(createRuntimeClientError("update", error));
|
|
274
|
-
}
|
|
275
|
-
})().then(resolve).catch((e) => resolve(Result.Error(createRuntimeClientError("unexpected", e))));
|
|
276
|
-
});
|
|
277
|
-
};
|
|
585
|
+
const queries = buildValidatedProxy({
|
|
586
|
+
defs: definition.queries,
|
|
587
|
+
operation: "query",
|
|
588
|
+
workflowId: workflowHandle.workflowId,
|
|
589
|
+
makeValidationError: (name, direction, issues) => new QueryValidationError(name, direction, issues),
|
|
590
|
+
invoke: (name, validated) => workflowHandle.query(name, validated),
|
|
591
|
+
validateOutput: (def) => def.output
|
|
592
|
+
});
|
|
593
|
+
const signals = buildValidatedProxy({
|
|
594
|
+
defs: definition.signals,
|
|
595
|
+
operation: "signal",
|
|
596
|
+
workflowId: workflowHandle.workflowId,
|
|
597
|
+
makeValidationError: (name, _direction, issues) => new SignalValidationError(name, issues),
|
|
598
|
+
invoke: async (name, validated) => {
|
|
599
|
+
await workflowHandle.signal(name, validated);
|
|
600
|
+
},
|
|
601
|
+
validateOutput: () => null
|
|
602
|
+
});
|
|
603
|
+
const updates = buildValidatedProxy({
|
|
604
|
+
defs: definition.updates,
|
|
605
|
+
operation: "update",
|
|
606
|
+
workflowId: workflowHandle.workflowId,
|
|
607
|
+
makeValidationError: (name, direction, issues) => new UpdateValidationError(name, direction, issues),
|
|
608
|
+
invoke: (name, validated) => workflowHandle.executeUpdate(name, { args: [validated] }),
|
|
609
|
+
validateOutput: (def) => def.output
|
|
610
|
+
});
|
|
278
611
|
return {
|
|
279
612
|
workflowId: workflowHandle.workflowId,
|
|
280
613
|
queries,
|
|
281
614
|
signals,
|
|
282
615
|
updates,
|
|
283
616
|
result: () => {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
});
|
|
296
|
-
},
|
|
297
|
-
terminate: (reason) => {
|
|
298
|
-
return Future.fromPromise(workflowHandle.terminate(reason)).mapError((error) => createRuntimeClientError("terminate", error)).mapOk(() => void 0);
|
|
299
|
-
},
|
|
300
|
-
cancel: () => {
|
|
301
|
-
return Future.fromPromise(workflowHandle.cancel()).mapError((error) => createRuntimeClientError("cancel", error)).mapOk(() => void 0);
|
|
302
|
-
},
|
|
303
|
-
describe: () => {
|
|
304
|
-
return Future.fromPromise(workflowHandle.describe()).mapError((error) => createRuntimeClientError("describe", error));
|
|
617
|
+
const work = async () => {
|
|
618
|
+
try {
|
|
619
|
+
const result = await workflowHandle.result();
|
|
620
|
+
const outputResult = await definition.output["~standard"].validate(result);
|
|
621
|
+
if (outputResult.issues) return err(new WorkflowValidationError(workflowHandle.workflowId, "output", outputResult.issues));
|
|
622
|
+
return ok(outputResult.value);
|
|
623
|
+
} catch (error) {
|
|
624
|
+
return err(classifyResultError("result", error, workflowHandle.workflowId));
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
return makeResultAsync(work);
|
|
305
628
|
},
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
629
|
+
terminate: (reason) => ResultAsync.fromPromise(workflowHandle.terminate(reason), (error) => classifyHandleError("terminate", error, workflowHandle.workflowId)).map(() => void 0),
|
|
630
|
+
cancel: () => ResultAsync.fromPromise(workflowHandle.cancel(), (error) => classifyHandleError("cancel", error, workflowHandle.workflowId)).map(() => void 0),
|
|
631
|
+
describe: () => ResultAsync.fromPromise(workflowHandle.describe(), (error) => classifyHandleError("describe", error, workflowHandle.workflowId)),
|
|
632
|
+
fetchHistory: () => ResultAsync.fromPromise(workflowHandle.fetchHistory(), (error) => classifyHandleError("fetchHistory", error, workflowHandle.workflowId))
|
|
309
633
|
};
|
|
310
634
|
}
|
|
311
635
|
};
|
|
@@ -318,7 +642,36 @@ function createWorkflowNotFoundError(workflowName, contract) {
|
|
|
318
642
|
function createWorkflowValidationError(workflowName, direction, issues) {
|
|
319
643
|
return new WorkflowValidationError(String(workflowName), direction, issues);
|
|
320
644
|
}
|
|
321
|
-
|
|
645
|
+
/**
|
|
646
|
+
* Build a `{ name: (args) => ResultAsync<...> }` proxy for a contract's
|
|
647
|
+
* queries/signals/updates. The three call sites differ only in how they
|
|
648
|
+
* invoke Temporal and whether they validate output, so the shared
|
|
649
|
+
* input-validate → invoke → output-validate → wrap-Result pipeline lives
|
|
650
|
+
* here once.
|
|
651
|
+
*/
|
|
652
|
+
function buildValidatedProxy({ defs, operation, workflowId, makeValidationError, invoke, validateOutput }) {
|
|
653
|
+
const proxy = {};
|
|
654
|
+
if (!defs) return proxy;
|
|
655
|
+
for (const [name, def] of Object.entries(defs)) proxy[name] = (args) => {
|
|
656
|
+
const work = async () => {
|
|
657
|
+
const inputResult = await def.input["~standard"].validate(args);
|
|
658
|
+
if (inputResult.issues) return err(makeValidationError(name, "input", inputResult.issues));
|
|
659
|
+
try {
|
|
660
|
+
const result = await invoke(name, inputResult.value);
|
|
661
|
+
const outputSchema = validateOutput(def);
|
|
662
|
+
if (!outputSchema) return ok(result);
|
|
663
|
+
const outputResult = await outputSchema["~standard"].validate(result);
|
|
664
|
+
if (outputResult.issues) return err(makeValidationError(name, "output", outputResult.issues));
|
|
665
|
+
return ok(outputResult.value);
|
|
666
|
+
} catch (error) {
|
|
667
|
+
return err(classifyHandleError(operation, error, workflowId));
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
return makeResultAsync(work);
|
|
671
|
+
};
|
|
672
|
+
return proxy;
|
|
673
|
+
}
|
|
322
674
|
//#endregion
|
|
323
|
-
export { QueryValidationError, SignalValidationError, TypedClient, UpdateValidationError, WorkflowNotFoundError, WorkflowValidationError };
|
|
675
|
+
export { QueryValidationError, RuntimeClientError, SignalValidationError, TypedClient, TypedScheduleClient, UpdateValidationError, WorkflowAlreadyStartedError, WorkflowExecutionNotFoundError, WorkflowFailedError, WorkflowNotFoundError, WorkflowValidationError };
|
|
676
|
+
|
|
324
677
|
//# sourceMappingURL=index.mjs.map
|