@temporal-contract/worker 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,502 @@
1
+ import { Future, Result } from "@temporal-contract/boxed";
2
+ import { defineQuery, defineSignal, defineUpdate, executeChild, proxyActivities, setHandler, startChild, workflowInfo } from "@temporalio/workflow";
3
+
4
+ //#region src/errors.ts
5
+ /**
6
+ * Base error class for worker errors
7
+ */
8
+ var WorkerError = class extends Error {
9
+ constructor(message, cause) {
10
+ super(message, { cause });
11
+ this.name = "WorkerError";
12
+ if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ };
15
+ /**
16
+ * Activity error class that should be used to wrap all technical exceptions
17
+ * Forces proper error handling and enables retry policies
18
+ */
19
+ var ActivityError = class ActivityError extends Error {
20
+ code;
21
+ cause;
22
+ constructor(code, message, cause) {
23
+ super(message, { cause });
24
+ this.code = code;
25
+ this.cause = cause;
26
+ this.name = "ActivityError";
27
+ if (Error.captureStackTrace) Error.captureStackTrace(this, ActivityError);
28
+ }
29
+ };
30
+ /**
31
+ * Error thrown when an activity definition is not found in the contract
32
+ */
33
+ var ActivityDefinitionNotFoundError = class extends WorkerError {
34
+ constructor(activityName, availableDefinitions = []) {
35
+ const available = availableDefinitions.length > 0 ? availableDefinitions.join(", ") : "none";
36
+ super(`Activity definition not found for: "${activityName}". Available activities: ${available}`);
37
+ this.activityName = activityName;
38
+ this.availableDefinitions = availableDefinitions;
39
+ this.name = "ActivityDefinitionNotFoundError";
40
+ }
41
+ };
42
+ /**
43
+ * Error thrown when activity input validation fails
44
+ */
45
+ var ActivityInputValidationError = class extends WorkerError {
46
+ constructor(activityName, issues) {
47
+ const message = issues.map((issue) => issue.message).join("; ");
48
+ super(`Activity "${activityName}" input validation failed: ${message}`);
49
+ this.activityName = activityName;
50
+ this.issues = issues;
51
+ this.name = "ActivityInputValidationError";
52
+ }
53
+ };
54
+ /**
55
+ * Error thrown when activity output validation fails
56
+ */
57
+ var ActivityOutputValidationError = class extends WorkerError {
58
+ constructor(activityName, issues) {
59
+ const message = issues.map((issue) => issue.message).join("; ");
60
+ super(`Activity "${activityName}" output validation failed: ${message}`);
61
+ this.activityName = activityName;
62
+ this.issues = issues;
63
+ this.name = "ActivityOutputValidationError";
64
+ }
65
+ };
66
+ /**
67
+ * Error thrown when workflow input validation fails
68
+ */
69
+ var WorkflowInputValidationError = class extends WorkerError {
70
+ constructor(workflowName, issues) {
71
+ const message = issues.map((issue) => issue.message).join("; ");
72
+ super(`Workflow "${workflowName}" input validation failed: ${message}`);
73
+ this.workflowName = workflowName;
74
+ this.issues = issues;
75
+ this.name = "WorkflowInputValidationError";
76
+ }
77
+ };
78
+ /**
79
+ * Error thrown when workflow output validation fails
80
+ */
81
+ var WorkflowOutputValidationError = class extends WorkerError {
82
+ constructor(workflowName, issues) {
83
+ const message = issues.map((issue) => issue.message).join("; ");
84
+ super(`Workflow "${workflowName}" output validation failed: ${message}`);
85
+ this.workflowName = workflowName;
86
+ this.issues = issues;
87
+ this.name = "WorkflowOutputValidationError";
88
+ }
89
+ };
90
+ /**
91
+ * Error thrown when signal input validation fails
92
+ */
93
+ var SignalInputValidationError = class extends WorkerError {
94
+ constructor(signalName, issues) {
95
+ const message = issues.map((issue) => issue.message).join("; ");
96
+ super(`Signal "${signalName}" input validation failed: ${message}`);
97
+ this.signalName = signalName;
98
+ this.issues = issues;
99
+ this.name = "SignalInputValidationError";
100
+ }
101
+ };
102
+ /**
103
+ * Error thrown when query input validation fails
104
+ */
105
+ var QueryInputValidationError = class extends WorkerError {
106
+ constructor(queryName, issues) {
107
+ const message = issues.map((issue) => issue.message).join("; ");
108
+ super(`Query "${queryName}" input validation failed: ${message}`);
109
+ this.queryName = queryName;
110
+ this.issues = issues;
111
+ this.name = "QueryInputValidationError";
112
+ }
113
+ };
114
+ /**
115
+ * Error thrown when query output validation fails
116
+ */
117
+ var QueryOutputValidationError = class extends WorkerError {
118
+ constructor(queryName, issues) {
119
+ const message = issues.map((issue) => issue.message).join("; ");
120
+ super(`Query "${queryName}" output validation failed: ${message}`);
121
+ this.queryName = queryName;
122
+ this.issues = issues;
123
+ this.name = "QueryOutputValidationError";
124
+ }
125
+ };
126
+ /**
127
+ * Error thrown when update input validation fails
128
+ */
129
+ var UpdateInputValidationError = class extends WorkerError {
130
+ constructor(updateName, issues) {
131
+ const message = issues.map((issue) => issue.message).join("; ");
132
+ super(`Update "${updateName}" input validation failed: ${message}`);
133
+ this.updateName = updateName;
134
+ this.issues = issues;
135
+ this.name = "UpdateInputValidationError";
136
+ }
137
+ };
138
+ /**
139
+ * Error thrown when update output validation fails
140
+ */
141
+ var UpdateOutputValidationError = class extends WorkerError {
142
+ constructor(updateName, issues) {
143
+ const message = issues.map((issue) => issue.message).join("; ");
144
+ super(`Update "${updateName}" output validation failed: ${message}`);
145
+ this.updateName = updateName;
146
+ this.issues = issues;
147
+ this.name = "UpdateOutputValidationError";
148
+ }
149
+ };
150
+ /**
151
+ * Error thrown when a child workflow is not found in the contract
152
+ */
153
+ var ChildWorkflowNotFoundError = class extends WorkerError {
154
+ constructor(workflowName, availableWorkflows = []) {
155
+ const available = availableWorkflows.length > 0 ? availableWorkflows.join(", ") : "none";
156
+ super(`Child workflow not found: "${workflowName}". Available workflows: ${available}`);
157
+ this.workflowName = workflowName;
158
+ this.availableWorkflows = availableWorkflows;
159
+ this.name = "ChildWorkflowNotFoundError";
160
+ }
161
+ };
162
+ /**
163
+ * Generic error for child workflow operations
164
+ */
165
+ var ChildWorkflowError = class extends WorkerError {
166
+ constructor(message, cause) {
167
+ super(message, cause);
168
+ this.name = "ChildWorkflowError";
169
+ }
170
+ };
171
+
172
+ //#endregion
173
+ //#region src/handler.ts
174
+ /**
175
+ * Create a validated activities proxy that parses inputs and outputs
176
+ *
177
+ * This wrapper ensures data integrity across the network boundary between
178
+ * workflow and activity execution.
179
+ */
180
+ function createValidatedActivities(rawActivities, workflowActivitiesDefinition, contractActivitiesDefinition) {
181
+ const validatedActivities = {};
182
+ const allActivitiesDefinition = {
183
+ ...contractActivitiesDefinition,
184
+ ...workflowActivitiesDefinition
185
+ };
186
+ for (const [activityName, activityDef] of Object.entries(allActivitiesDefinition)) {
187
+ const rawActivity = rawActivities[activityName];
188
+ if (!rawActivity) throw new Error(`Activity implementation not found for: "${activityName}". Available activities: ${Object.keys(rawActivities).length > 0 ? Object.keys(rawActivities).join(", ") : "none"}`);
189
+ const wrappedActivity = async (input) => {
190
+ const inputResult = await activityDef.input["~standard"].validate(input);
191
+ if (inputResult.issues) throw new ActivityInputValidationError(activityName, inputResult.issues);
192
+ const result = await rawActivity(inputResult.value);
193
+ const outputResult = await activityDef.output["~standard"].validate(result);
194
+ if (outputResult.issues) throw new ActivityOutputValidationError(activityName, outputResult.issues);
195
+ return outputResult.value;
196
+ };
197
+ validatedActivities[activityName] = wrappedActivity;
198
+ }
199
+ return validatedActivities;
200
+ }
201
+ /**
202
+ * Create a typed activities handler with automatic validation and Result pattern
203
+ *
204
+ * This wraps all activity implementations with:
205
+ * - Validation at network boundaries
206
+ * - Result<T, ActivityError> pattern for explicit error handling
207
+ * - Automatic conversion from Result to Promise (throwing on Error)
208
+ *
209
+ * TypeScript ensures ALL activities (global + workflow-specific) are implemented.
210
+ *
211
+ * Use this to create the activities object for the Temporal Worker.
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * import { declareActivitiesHandler, ActivityError } from '@temporal-contract/worker/activity';
216
+ * import { Result, Future } from '@temporal-contract/boxed';
217
+ * import myContract from './contract';
218
+ *
219
+ * export const activitiesHandler = declareActivitiesHandler({
220
+ * contract: myContract,
221
+ * activities: {
222
+ * // Activity returns Result instead of throwing
223
+ * // All technical exceptions must be wrapped in ActivityError for retry policies
224
+ * sendEmail: (args) => {
225
+ * return Future.make(async resolve => {
226
+ * try {
227
+ * await emailService.send(args);
228
+ * resolve(Result.Ok({ sent: true }));
229
+ * } catch (error) {
230
+ * // Wrap technical errors in ActivityError to enable retries
231
+ * resolve(Result.Error(
232
+ * new ActivityError(
233
+ * 'EMAIL_SEND_FAILED',
234
+ * 'Failed to send email',
235
+ * error // Original error as cause for debugging
236
+ * )
237
+ * ));
238
+ * }
239
+ * });
240
+ * },
241
+ * },
242
+ * });
243
+ *
244
+ * // Use with Temporal Worker
245
+ * import { Worker } from '@temporalio/worker';
246
+ *
247
+ * const worker = await Worker.create({
248
+ * workflowsPath: require.resolve('./workflows'),
249
+ * activities: activitiesHandler.activities,
250
+ * taskQueue: activitiesHandler.contract.taskQueue,
251
+ * });
252
+ * ```
253
+ */
254
+ function declareActivitiesHandler(options) {
255
+ const { contract, activities } = options;
256
+ const wrappedActivities = {};
257
+ const allDefinitions = [];
258
+ if (contract.activities) allDefinitions.push(...Object.keys(contract.activities));
259
+ for (const workflow of Object.values(contract.workflows)) if (workflow.activities) allDefinitions.push(...Object.keys(workflow.activities));
260
+ for (const [activityName, activityImpl] of Object.entries(activities)) {
261
+ let activityDef;
262
+ if (contract.activities?.[activityName]) activityDef = contract.activities[activityName];
263
+ else for (const workflow of Object.values(contract.workflows)) if (workflow.activities?.[activityName]) {
264
+ activityDef = workflow.activities[activityName];
265
+ break;
266
+ }
267
+ if (!activityDef) throw new ActivityDefinitionNotFoundError(activityName, allDefinitions);
268
+ wrappedActivities[activityName] = async (...args) => {
269
+ const input = args.length === 1 ? args[0] : args;
270
+ const inputResult = await activityDef.input["~standard"].validate(input);
271
+ if (inputResult.issues) throw new ActivityInputValidationError(activityName, inputResult.issues);
272
+ const result = await activityImpl(inputResult.value);
273
+ if (result.isOk()) {
274
+ const outputResult = await activityDef.output["~standard"].validate(result.value);
275
+ if (outputResult.issues) throw new ActivityOutputValidationError(activityName, outputResult.issues);
276
+ return outputResult.value;
277
+ } else throw result.error;
278
+ };
279
+ }
280
+ return {
281
+ contract,
282
+ activities: wrappedActivities
283
+ };
284
+ }
285
+ /**
286
+ * Create a typed workflow implementation with automatic validation
287
+ *
288
+ * This wraps a workflow implementation with:
289
+ * - Input/output validation
290
+ * - Typed workflow context with activities
291
+ * - Workflow info access
292
+ *
293
+ * Workflows must be defined in separate files and imported by the Temporal Worker
294
+ * via workflowsPath.
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * // workflows/processOrder.ts
299
+ * import { declareWorkflow } from '@temporal-contract/worker';
300
+ * import myContract from '../contract';
301
+ *
302
+ * export const processOrder = declareWorkflow({
303
+ * workflowName: 'processOrder',
304
+ * contract: myContract,
305
+ * implementation: async (context, orderId, customerId) => {
306
+ * // context.activities: typed activities (workflow + global)
307
+ * // context.info: WorkflowInfo
308
+ *
309
+ * const inventory = await context.activities.validateInventory(orderId);
310
+ *
311
+ * if (!inventory.available) {
312
+ * throw new Error('Out of stock');
313
+ * }
314
+ *
315
+ * const payment = await context.activities.chargePayment(customerId, 100);
316
+ *
317
+ * // Global activity
318
+ * await context.activities.sendEmail(
319
+ * customerId,
320
+ * 'Order processed',
321
+ * 'Your order has been processed'
322
+ * );
323
+ *
324
+ * return {
325
+ * orderId,
326
+ * status: payment.success ? 'success' : 'failed',
327
+ * transactionId: payment.transactionId,
328
+ * };
329
+ * },
330
+ * activityOptions: {
331
+ * startToCloseTimeout: '1 minute',
332
+ * },
333
+ * });
334
+ * ```
335
+ *
336
+ * Then in your worker setup:
337
+ * ```ts
338
+ * // worker.ts
339
+ * import { Worker } from '@temporalio/worker';
340
+ * import { activitiesHandler } from './activities';
341
+ *
342
+ * const worker = await Worker.create({
343
+ * workflowsPath: require.resolve('./workflows'), // Imports processOrder
344
+ * activities: activitiesHandler.activities,
345
+ * taskQueue: activitiesHandler.contract.taskQueue,
346
+ * });
347
+ * ```
348
+ */
349
+ function declareWorkflow(options) {
350
+ const { workflowName, contract, implementation, activityOptions } = options;
351
+ const definition = contract.workflows[workflowName];
352
+ return async (args) => {
353
+ const singleArg = Array.isArray(args) ? args[0] : args;
354
+ const inputResult = await definition.input["~standard"].validate(singleArg);
355
+ if (inputResult.issues) throw new WorkflowInputValidationError(String(workflowName), inputResult.issues);
356
+ const validatedInput = inputResult.value;
357
+ let contextActivities = {};
358
+ if (definition.activities || contract.activities) contextActivities = createValidatedActivities(proxyActivities({
359
+ startToCloseTimeout: activityOptions?.startToCloseTimeout ?? 6e4,
360
+ ...activityOptions
361
+ }), definition.activities, contract.activities);
362
+ async function validateChildWorkflowOutput(childDefinition, result$1, childWorkflowName) {
363
+ const outputResult$1 = await childDefinition.output["~standard"].validate(result$1);
364
+ if (outputResult$1.issues) return Result.Error(new ChildWorkflowError(`Child workflow "${childWorkflowName}" output validation failed: ${outputResult$1.issues.map((i) => i.message).join("; ")}`));
365
+ return Result.Ok(outputResult$1.value);
366
+ }
367
+ async function getAndValidateChildWorkflow(childContract, childWorkflowName, args$1) {
368
+ const childDefinition = childContract.workflows[childWorkflowName];
369
+ if (!childDefinition) return Result.Error(new ChildWorkflowNotFoundError(String(childWorkflowName), Object.keys(childContract.workflows)));
370
+ const inputResult$1 = await childDefinition.input["~standard"].validate(args$1);
371
+ if (inputResult$1.issues) return Result.Error(new ChildWorkflowError(`Child workflow "${String(childWorkflowName)}" input validation failed: ${inputResult$1.issues.map((i) => i.message).join("; ")}`));
372
+ const validatedInput$1 = inputResult$1.value;
373
+ return Result.Ok({
374
+ definition: childDefinition,
375
+ validatedInput: validatedInput$1,
376
+ taskQueue: childContract.taskQueue
377
+ });
378
+ }
379
+ function createTypedChildHandle(handle, childDefinition, childWorkflowName) {
380
+ return {
381
+ workflowId: handle.workflowId,
382
+ result: () => {
383
+ return Future.make((resolve) => {
384
+ (async () => {
385
+ try {
386
+ resolve(await validateChildWorkflowOutput(childDefinition, await handle.result(), childWorkflowName));
387
+ } catch (error) {
388
+ resolve(Result.Error(new ChildWorkflowError(`Child workflow execution failed: ${error instanceof Error ? error.message : String(error)}`, error)));
389
+ }
390
+ })();
391
+ });
392
+ }
393
+ };
394
+ }
395
+ function createStartChildWorkflow(childContract, childWorkflowName, options$1) {
396
+ return Future.make((resolve) => {
397
+ (async () => {
398
+ const validationResult = await getAndValidateChildWorkflow(childContract, childWorkflowName, options$1.args);
399
+ if (validationResult.isError()) {
400
+ resolve(Result.Error(validationResult.error));
401
+ return;
402
+ }
403
+ const { definition: childDefinition, validatedInput: validatedInput$1, taskQueue } = validationResult.value;
404
+ try {
405
+ const { args: _args, ...temporalOptions } = options$1;
406
+ const typedHandle = createTypedChildHandle(await startChild(childWorkflowName, {
407
+ ...temporalOptions,
408
+ taskQueue,
409
+ args: [validatedInput$1]
410
+ }), childDefinition, String(childWorkflowName));
411
+ resolve(Result.Ok(typedHandle));
412
+ } catch (error) {
413
+ resolve(Result.Error(new ChildWorkflowError(`Failed to start child workflow: ${error instanceof Error ? error.message : String(error)}`, error)));
414
+ }
415
+ })();
416
+ });
417
+ }
418
+ function createExecuteChildWorkflow(childContract, childWorkflowName, options$1) {
419
+ return Future.make((resolve) => {
420
+ (async () => {
421
+ const validationResult = await getAndValidateChildWorkflow(childContract, childWorkflowName, options$1.args);
422
+ if (validationResult.isError()) {
423
+ resolve(Result.Error(validationResult.error));
424
+ return;
425
+ }
426
+ const { definition: childDefinition, validatedInput: validatedInput$1, taskQueue } = validationResult.value;
427
+ try {
428
+ const { args: _args, ...temporalOptions } = options$1;
429
+ const outputValidationResult = await validateChildWorkflowOutput(childDefinition, await executeChild(childWorkflowName, {
430
+ ...temporalOptions,
431
+ taskQueue,
432
+ args: [validatedInput$1]
433
+ }), String(childWorkflowName));
434
+ if (outputValidationResult.isError()) {
435
+ resolve(Result.Error(outputValidationResult.error));
436
+ return;
437
+ }
438
+ resolve(Result.Ok(outputValidationResult.value));
439
+ } catch (error) {
440
+ resolve(Result.Error(new ChildWorkflowError(`Failed to execute child workflow: ${error instanceof Error ? error.message : String(error)}`, error)));
441
+ }
442
+ })();
443
+ });
444
+ }
445
+ function createDefineSignal(signalName, handler) {
446
+ if (!definition.signals) throw new Error(`Signal "${String(signalName)}" cannot be defined: workflow "${String(workflowName)}" has no signals in its contract`);
447
+ const signalDef = definition.signals[signalName];
448
+ if (!signalDef) throw new Error(`Signal "${String(signalName)}" not found in workflow "${String(workflowName)}" contract`);
449
+ setHandler(defineSignal(signalName), async (...args$1) => {
450
+ const input = args$1.length === 1 ? args$1[0] : args$1;
451
+ const inputResult$1 = await signalDef.input["~standard"].validate(input);
452
+ if (inputResult$1.issues) throw new SignalInputValidationError(signalName, inputResult$1.issues);
453
+ await handler(inputResult$1.value);
454
+ });
455
+ }
456
+ function createDefineQuery(queryName, handler) {
457
+ if (!definition.queries) throw new Error(`Query "${String(queryName)}" cannot be defined: workflow "${String(workflowName)}" has no queries in its contract`);
458
+ const queryDef = definition.queries[queryName];
459
+ if (!queryDef) throw new Error(`Query "${String(queryName)}" not found in workflow "${String(workflowName)}" contract`);
460
+ setHandler(defineQuery(queryName), (...args$1) => {
461
+ const input = args$1.length === 1 ? args$1[0] : args$1;
462
+ const inputResult$1 = queryDef.input["~standard"].validate(input);
463
+ if (inputResult$1 instanceof Promise) throw new Error(`Query "${String(queryName)}" validation must be synchronous. Use a schema library that supports synchronous validation for queries.`);
464
+ if (inputResult$1.issues) throw new QueryInputValidationError(queryName, inputResult$1.issues);
465
+ const result$1 = handler(inputResult$1.value);
466
+ const outputResult$1 = queryDef.output["~standard"].validate(result$1);
467
+ if (outputResult$1 instanceof Promise) throw new Error(`Query "${String(queryName)}" output validation must be synchronous. Use a schema library that supports synchronous validation for queries.`);
468
+ if (outputResult$1.issues) throw new QueryOutputValidationError(queryName, outputResult$1.issues);
469
+ return outputResult$1.value;
470
+ });
471
+ }
472
+ function createDefineUpdate(updateName, handler) {
473
+ if (!definition.updates) throw new Error(`Update "${String(updateName)}" cannot be defined: workflow "${String(workflowName)}" has no updates in its contract`);
474
+ const updateDef = definition.updates[updateName];
475
+ if (!updateDef) throw new Error(`Update "${String(updateName)}" not found in workflow "${String(workflowName)}" contract`);
476
+ setHandler(defineUpdate(updateName), async (...args$1) => {
477
+ const input = args$1.length === 1 ? args$1[0] : args$1;
478
+ const inputResult$1 = await updateDef.input["~standard"].validate(input);
479
+ if (inputResult$1.issues) throw new UpdateInputValidationError(updateName, inputResult$1.issues);
480
+ const result$1 = await handler(inputResult$1.value);
481
+ const outputResult$1 = await updateDef.output["~standard"].validate(result$1);
482
+ if (outputResult$1.issues) throw new UpdateOutputValidationError(updateName, outputResult$1.issues);
483
+ return outputResult$1.value;
484
+ });
485
+ }
486
+ const result = await implementation({
487
+ activities: contextActivities,
488
+ info: workflowInfo(),
489
+ startChildWorkflow: createStartChildWorkflow,
490
+ executeChildWorkflow: createExecuteChildWorkflow,
491
+ defineSignal: createDefineSignal,
492
+ defineQuery: createDefineQuery,
493
+ defineUpdate: createDefineUpdate
494
+ }, validatedInput);
495
+ const outputResult = await definition.output["~standard"].validate(result);
496
+ if (outputResult.issues) throw new WorkflowOutputValidationError(String(workflowName), outputResult.issues);
497
+ return outputResult.value;
498
+ };
499
+ }
500
+
501
+ //#endregion
502
+ export { ActivityInputValidationError as a, ChildWorkflowNotFoundError as c, SignalInputValidationError as d, UpdateInputValidationError as f, WorkflowOutputValidationError as g, WorkflowInputValidationError as h, ActivityError as i, QueryInputValidationError as l, WorkerError as m, declareWorkflow as n, ActivityOutputValidationError as o, UpdateOutputValidationError as p, ActivityDefinitionNotFoundError as r, ChildWorkflowError as s, declareActivitiesHandler as t, QueryOutputValidationError as u };