@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.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import { Future, Result } from "@swan-io/boxed";
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}: ${JSON.stringify(issues)}`);
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}: ${JSON.stringify(issues)}`);
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}": ${JSON.stringify(issues)}`);
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}: ${JSON.stringify(issues)}`);
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
- * Typed Temporal client with Result/Future pattern based on a contract
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 boxed pattern from a contract
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
- * Ok: (output) => console.log('Success:', output),
107
- * Error: (error) => console.error('Failed:', error),
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 Future pattern
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
- * Ok: async (handle) => {
426
+ * handleResult.match(
427
+ * async (handle) => {
128
428
  * const result = await handle.result();
129
429
  * // ... handle result
130
430
  * },
131
- * Error: (error) => console.error('Failed to start:', error),
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
- startWorkflow(workflowName, { args, ...temporalOptions }) {
136
- return Future.make((resolve) => {
137
- (async () => {
138
- const definition = this.contract.workflows[workflowName];
139
- if (!definition) return Result.Error(createWorkflowNotFoundError(workflowName, this.contract));
140
- const inputResult = await definition.input["~standard"].validate(args);
141
- if (inputResult.issues) return Result.Error(createWorkflowValidationError(workflowName, "input", inputResult.issues));
142
- const validatedInput = inputResult.value;
143
- try {
144
- const handle = await this.client.workflow.start(workflowName, {
145
- ...temporalOptions,
146
- taskQueue: this.contract.taskQueue,
147
- args: [validatedInput]
148
- });
149
- const typedHandle = this.createTypedHandle(handle, definition);
150
- return Result.Ok(typedHandle);
151
- } catch (error) {
152
- return Result.Error(createRuntimeClientError("startWorkflow", error));
153
- }
154
- })().then(resolve).catch((e) => resolve(Result.Error(createRuntimeClientError("unexpected", e))));
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 Future/Result pattern
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
- * Ok: (output) => console.log('Order processed:', output.status),
171
- * Error: (error) => console.error('Processing failed:', error),
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
- return Future.make((resolve) => {
177
- (async () => {
178
- const definition = this.contract.workflows[workflowName];
179
- if (!definition) return Result.Error(createWorkflowNotFoundError(workflowName, this.contract));
180
- const inputResult = await definition.input["~standard"].validate(args);
181
- if (inputResult.issues) return Result.Error(createWorkflowValidationError(workflowName, "input", inputResult.issues));
182
- const validatedInput = inputResult.value;
183
- try {
184
- const result = await this.client.workflow.execute(workflowName, {
185
- ...temporalOptions,
186
- taskQueue: this.contract.taskQueue,
187
- args: [validatedInput]
188
- });
189
- const outputResult = await definition.output["~standard"].validate(result);
190
- if (outputResult.issues) return Result.Error(createWorkflowValidationError(workflowName, "output", outputResult.issues));
191
- return Result.Ok(outputResult.value);
192
- } catch (error) {
193
- return Result.Error(createRuntimeClientError("executeWorkflow", error));
194
- }
195
- })().then(resolve).catch((e) => resolve(Result.Error(createRuntimeClientError("unexpected", e))));
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 Future/Result pattern
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
- * Ok: async (handle) => {
562
+ * handleResult.match(
563
+ * async (handle) => {
206
564
  * const result = await handle.result();
207
565
  * // ... handle result
208
566
  * },
209
- * Error: (error) => console.error('Failed to get handle:', error),
210
- * });
567
+ * (error) => console.error('Failed to get handle:', error),
568
+ * );
211
569
  * ```
212
570
  */
213
571
  getHandle(workflowName, workflowId) {
214
- return Future.make((resolve) => {
215
- (async () => {
216
- const definition = this.contract.workflows[workflowName];
217
- if (!definition) return Result.Error(createWorkflowNotFoundError(workflowName, this.contract));
218
- try {
219
- const handle = this.client.workflow.getHandle(workflowId);
220
- const typedHandle = this.createTypedHandle(handle, definition);
221
- return Result.Ok(typedHandle);
222
- } catch (error) {
223
- return Result.Error(createRuntimeClientError("getHandle", error));
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
- for (const [queryName, queryDef] of Object.entries(definition.queries ?? {})) queries[queryName] = (args) => {
231
- return Future.make((resolve) => {
232
- (async () => {
233
- const inputResult = await queryDef.input["~standard"].validate(args);
234
- if (inputResult.issues) return Result.Error(new QueryValidationError(queryName, "input", inputResult.issues));
235
- try {
236
- const result = await workflowHandle.query(queryName, inputResult.value);
237
- const outputResult = await queryDef.output["~standard"].validate(result);
238
- if (outputResult.issues) return Result.Error(new QueryValidationError(queryName, "output", outputResult.issues));
239
- return Result.Ok(outputResult.value);
240
- } catch (error) {
241
- return Result.Error(createRuntimeClientError("query", error));
242
- }
243
- })().then(resolve).catch((e) => resolve(Result.Error(createRuntimeClientError("unexpected", e))));
244
- });
245
- };
246
- const signals = {};
247
- for (const [signalName, signalDef] of Object.entries(definition.signals ?? {})) signals[signalName] = (args) => {
248
- return Future.make((resolve) => {
249
- (async () => {
250
- const inputResult = await signalDef.input["~standard"].validate(args);
251
- if (inputResult.issues) return Result.Error(new SignalValidationError(signalName, inputResult.issues));
252
- try {
253
- await workflowHandle.signal(signalName, inputResult.value);
254
- return Result.Ok(void 0);
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
- return Future.make((resolve) => {
285
- (async () => {
286
- try {
287
- const result = await workflowHandle.result();
288
- const outputResult = await definition.output["~standard"].validate(result);
289
- if (outputResult.issues) return Result.Error(new WorkflowValidationError(workflowHandle.workflowId, "output", outputResult.issues));
290
- return Result.Ok(outputResult.value);
291
- } catch (error) {
292
- return Result.Error(createRuntimeClientError("result", error));
293
- }
294
- })().then(resolve).catch((e) => resolve(Result.Error(createRuntimeClientError("unexpected", e))));
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
- fetchHistory: () => {
307
- return Future.fromPromise(workflowHandle.fetchHistory()).mapError((error) => createRuntimeClientError("fetchHistory", error));
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