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