@temporal-contract/worker 0.0.1 → 0.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temporal-contract/worker",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "description": "Worker utilities for implementing temporal-contract workflows and activities",
6
6
  "homepage": "https://github.com/btravers/temporal-contract#readme",
@@ -47,20 +47,21 @@
47
47
  "./package.json": "./package.json"
48
48
  },
49
49
  "dependencies": {
50
- "@temporal-contract/contract": "0.0.1"
50
+ "@standard-schema/spec": "1.0.0",
51
+ "@temporal-contract/contract": "0.0.2"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@temporalio/workflow": "1.13.2",
54
55
  "@types/node": "24.10.2",
56
+ "@vitest/coverage-v8": "4.0.15",
55
57
  "tsdown": "0.17.2",
56
58
  "typescript": "5.9.3",
57
59
  "vitest": "4.0.15",
58
60
  "zod": "4.1.13",
59
- "@temporal-contract/tsconfig": "0.0.1"
61
+ "@temporal-contract/tsconfig": "0.0.2"
60
62
  },
61
63
  "peerDependencies": {
62
- "@temporalio/workflow": ">=1.13.0 <2.0.0",
63
- "zod": "^4.0.0"
64
+ "@temporalio/workflow": ">=1.13.0 <2.0.0"
64
65
  },
65
66
  "scripts": {
66
67
  "dev": "tsdown src/activity.ts src/workflow.ts --format cjs,esm --dts --watch",
package/src/errors.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { z } from "zod";
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
2
 
3
3
  /**
4
4
  * Base error class for worker errors
@@ -52,9 +52,10 @@ export class ActivityDefinitionNotFoundError extends WorkerError {
52
52
  export class ActivityInputValidationError extends WorkerError {
53
53
  constructor(
54
54
  public readonly activityName: string,
55
- public readonly zodError: z.ZodError,
55
+ public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
56
56
  ) {
57
- super(`Activity "${activityName}" input validation failed: ${zodError.message}`);
57
+ const message = issues.map((issue) => issue.message).join("; ");
58
+ super(`Activity "${activityName}" input validation failed: ${message}`);
58
59
  this.name = "ActivityInputValidationError";
59
60
  }
60
61
  }
@@ -65,9 +66,10 @@ export class ActivityInputValidationError extends WorkerError {
65
66
  export class ActivityOutputValidationError extends WorkerError {
66
67
  constructor(
67
68
  public readonly activityName: string,
68
- public readonly zodError: z.ZodError,
69
+ public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
69
70
  ) {
70
- super(`Activity "${activityName}" output validation failed: ${zodError.message}`);
71
+ const message = issues.map((issue) => issue.message).join("; ");
72
+ super(`Activity "${activityName}" output validation failed: ${message}`);
71
73
  this.name = "ActivityOutputValidationError";
72
74
  }
73
75
  }
@@ -78,9 +80,10 @@ export class ActivityOutputValidationError extends WorkerError {
78
80
  export class WorkflowInputValidationError extends WorkerError {
79
81
  constructor(
80
82
  public readonly workflowName: string,
81
- public readonly zodError: z.ZodError,
83
+ public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
82
84
  ) {
83
- super(`Workflow "${workflowName}" input validation failed: ${zodError.message}`);
85
+ const message = issues.map((issue) => issue.message).join("; ");
86
+ super(`Workflow "${workflowName}" input validation failed: ${message}`);
84
87
  this.name = "WorkflowInputValidationError";
85
88
  }
86
89
  }
@@ -91,9 +94,10 @@ export class WorkflowInputValidationError extends WorkerError {
91
94
  export class WorkflowOutputValidationError extends WorkerError {
92
95
  constructor(
93
96
  public readonly workflowName: string,
94
- public readonly zodError: z.ZodError,
97
+ public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
95
98
  ) {
96
- super(`Workflow "${workflowName}" output validation failed: ${zodError.message}`);
99
+ const message = issues.map((issue) => issue.message).join("; ");
100
+ super(`Workflow "${workflowName}" output validation failed: ${message}`);
97
101
  this.name = "WorkflowOutputValidationError";
98
102
  }
99
103
  }
@@ -104,9 +108,10 @@ export class WorkflowOutputValidationError extends WorkerError {
104
108
  export class SignalInputValidationError extends WorkerError {
105
109
  constructor(
106
110
  public readonly signalName: string,
107
- public readonly zodError: z.ZodError,
111
+ public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
108
112
  ) {
109
- super(`Signal "${signalName}" input validation failed: ${zodError.message}`);
113
+ const message = issues.map((issue) => issue.message).join("; ");
114
+ super(`Signal "${signalName}" input validation failed: ${message}`);
110
115
  this.name = "SignalInputValidationError";
111
116
  }
112
117
  }
@@ -117,9 +122,10 @@ export class SignalInputValidationError extends WorkerError {
117
122
  export class QueryInputValidationError extends WorkerError {
118
123
  constructor(
119
124
  public readonly queryName: string,
120
- public readonly zodError: z.ZodError,
125
+ public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
121
126
  ) {
122
- super(`Query "${queryName}" input validation failed: ${zodError.message}`);
127
+ const message = issues.map((issue) => issue.message).join("; ");
128
+ super(`Query "${queryName}" input validation failed: ${message}`);
123
129
  this.name = "QueryInputValidationError";
124
130
  }
125
131
  }
@@ -130,9 +136,10 @@ export class QueryInputValidationError extends WorkerError {
130
136
  export class QueryOutputValidationError extends WorkerError {
131
137
  constructor(
132
138
  public readonly queryName: string,
133
- public readonly zodError: z.ZodError,
139
+ public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
134
140
  ) {
135
- super(`Query "${queryName}" output validation failed: ${zodError.message}`);
141
+ const message = issues.map((issue) => issue.message).join("; ");
142
+ super(`Query "${queryName}" output validation failed: ${message}`);
136
143
  this.name = "QueryOutputValidationError";
137
144
  }
138
145
  }
@@ -143,9 +150,10 @@ export class QueryOutputValidationError extends WorkerError {
143
150
  export class UpdateInputValidationError extends WorkerError {
144
151
  constructor(
145
152
  public readonly updateName: string,
146
- public readonly zodError: z.ZodError,
153
+ public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
147
154
  ) {
148
- super(`Update "${updateName}" input validation failed: ${zodError.message}`);
155
+ const message = issues.map((issue) => issue.message).join("; ");
156
+ super(`Update "${updateName}" input validation failed: ${message}`);
149
157
  this.name = "UpdateInputValidationError";
150
158
  }
151
159
  }
@@ -156,9 +164,10 @@ export class UpdateInputValidationError extends WorkerError {
156
164
  export class UpdateOutputValidationError extends WorkerError {
157
165
  constructor(
158
166
  public readonly updateName: string,
159
- public readonly zodError: z.ZodError,
167
+ public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
160
168
  ) {
161
- super(`Update "${updateName}" output validation failed: ${zodError.message}`);
169
+ const message = issues.map((issue) => issue.message).join("; ");
170
+ super(`Update "${updateName}" output validation failed: ${message}`);
162
171
  this.name = "UpdateOutputValidationError";
163
172
  }
164
173
  }
@@ -1,15 +1,7 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import { z } from "zod";
3
3
  import { declareActivitiesHandler, declareWorkflow } from "./handler.js";
4
- import type { ActivityImplementations } from "./handler.js";
5
4
  import type { ContractDefinition, WorkflowDefinition } from "@temporal-contract/contract";
6
- import {
7
- ActivityDefinitionNotFoundError,
8
- ActivityInputValidationError,
9
- ActivityOutputValidationError,
10
- WorkflowInputValidationError,
11
- WorkflowOutputValidationError,
12
- } from "./errors.js";
13
5
 
14
6
  // Mock Temporal workflow functions
15
7
  vi.mock("@temporalio/workflow", () => ({
@@ -363,23 +355,16 @@ describe("Worker Package", () => {
363
355
  expect(() => {
364
356
  declareActivitiesHandler({
365
357
  contract,
366
- activities: testActivities as unknown as ActivityImplementations<typeof contract>,
358
+ // @ts-expect-error Testing unknown activity
359
+ activities: testActivities,
367
360
  });
368
- }).toThrow(ActivityDefinitionNotFoundError);
369
-
370
- try {
371
- declareActivitiesHandler({
372
- contract,
373
- activities: testActivities as unknown as ActivityImplementations<typeof contract>,
374
- });
375
- } catch (error) {
376
- if (error instanceof ActivityDefinitionNotFoundError) {
377
- expect(error.activityName).toBe("unknownActivity");
378
- expect(error.availableDefinitions).toEqual(["sendEmail", "processPayment"]);
379
- expect(error.message).toContain("unknownActivity");
380
- expect(error.message).toContain("sendEmail");
381
- }
382
- }
361
+ }).toThrowError(
362
+ expect.objectContaining({
363
+ name: "ActivityDefinitionNotFoundError",
364
+ activityName: "unknownActivity",
365
+ availableDefinitions: ["sendEmail", "processPayment"],
366
+ }),
367
+ );
383
368
  });
384
369
 
385
370
  it("should throw ActivityInputValidationError with Zod details", async () => {
@@ -403,19 +388,16 @@ describe("Worker Package", () => {
403
388
  },
404
389
  });
405
390
 
406
- try {
407
- await handler.activities["processPayment"]!({
391
+ await expect(
392
+ handler.activities["processPayment"]!({
408
393
  amount: -100,
409
394
  currency: "USD",
410
- });
411
- } catch (error) {
412
- if (error instanceof ActivityInputValidationError) {
413
- expect(error.activityName).toBe("processPayment");
414
- expect(error.zodError).toBeDefined();
415
- expect(error.message).toContain("processPayment");
416
- expect(error.message).toContain("input validation failed");
417
- }
418
- }
395
+ }),
396
+ ).rejects.toMatchObject({
397
+ name: "ActivityInputValidationError",
398
+ activityName: "processPayment",
399
+ message: expect.stringContaining("processPayment"),
400
+ });
419
401
  });
420
402
 
421
403
  it("should throw ActivityOutputValidationError with Zod details", async () => {
@@ -439,16 +421,11 @@ describe("Worker Package", () => {
439
421
  },
440
422
  });
441
423
 
442
- try {
443
- await handler.activities["fetchData"]!({ id: "123" });
444
- } catch (error) {
445
- if (error instanceof ActivityOutputValidationError) {
446
- expect(error.activityName).toBe("fetchData");
447
- expect(error.zodError).toBeDefined();
448
- expect(error.message).toContain("fetchData");
449
- expect(error.message).toContain("output validation failed");
450
- }
451
- }
424
+ await expect(handler.activities["fetchData"]!({ id: "123" })).rejects.toMatchObject({
425
+ name: "ActivityOutputValidationError",
426
+ activityName: "fetchData",
427
+ message: expect.stringContaining("fetchData"),
428
+ });
452
429
  });
453
430
 
454
431
  it("should throw WorkflowInputValidationError", async () => {
@@ -472,18 +449,16 @@ describe("Worker Package", () => {
472
449
  },
473
450
  });
474
451
 
475
- try {
476
- await workflow([{ orderId: "not-a-uuid", amount: 100 }] as unknown as {
452
+ await expect(
453
+ workflow([{ orderId: "not-a-uuid", amount: 100 }] as unknown as {
477
454
  orderId: string;
478
455
  amount: number;
479
- });
480
- } catch (error) {
481
- if (error instanceof WorkflowInputValidationError) {
482
- expect(error.workflowName).toBe("processOrder");
483
- expect(error.zodError).toBeDefined();
484
- expect(error.message).toContain("input validation failed");
485
- }
486
- }
456
+ }),
457
+ ).rejects.toMatchObject({
458
+ name: "WorkflowInputValidationError",
459
+ workflowName: "processOrder",
460
+ message: expect.stringContaining("input validation failed"),
461
+ });
487
462
  });
488
463
 
489
464
  it("should throw WorkflowOutputValidationError", async () => {
@@ -511,15 +486,14 @@ describe("Worker Package", () => {
511
486
  },
512
487
  });
513
488
 
514
- try {
515
- await workflow([{ orderId: "123" }] as unknown as { orderId: string });
516
- } catch (error) {
517
- if (error instanceof WorkflowOutputValidationError) {
518
- expect(error.workflowName).toBe("processOrder");
519
- expect(error.zodError).toBeDefined();
520
- expect(error.message).toContain("output validation failed");
521
- }
522
- }
489
+ await expect(
490
+ // @ts-expect-error Testing invalid output
491
+ workflow([{ orderId: "123" }]),
492
+ ).rejects.toMatchObject({
493
+ name: "WorkflowOutputValidationError",
494
+ workflowName: "processOrder",
495
+ message: expect.stringContaining("output validation failed"),
496
+ });
523
497
  });
524
498
  });
525
499
  });
package/src/handler.ts CHANGED
@@ -8,7 +8,6 @@ import {
8
8
  setHandler,
9
9
  workflowInfo,
10
10
  } from "@temporalio/workflow";
11
- import { ZodError } from "zod";
12
11
  import type {
13
12
  ActivityDefinition,
14
13
  ContractDefinition,
@@ -236,32 +235,29 @@ function createValidatedActivities<
236
235
  throw new ActivityImplementationNotFoundError(activityName, Object.keys(rawActivities));
237
236
  }
238
237
 
239
- // @ts-expect-error fixme later
240
- validatedActivities[activityName] = async (input: unknown) => {
238
+ // Create the wrapped activity with validation
239
+ // Type assertion to unknown is safe as we're building the object step by step
240
+ const wrappedActivity = async (input: unknown) => {
241
241
  // Validate input before sending over network
242
- let validatedInput: unknown;
243
- try {
244
- validatedInput = activityDef.input.parse(input);
245
- } catch (error) {
246
- if (error instanceof ZodError) {
247
- throw new ActivityInputValidationError(activityName, error);
248
- }
249
- throw error;
242
+ const inputResult = await activityDef.input["~standard"].validate(input);
243
+ if (inputResult.issues) {
244
+ throw new ActivityInputValidationError(activityName, inputResult.issues);
250
245
  }
251
246
 
252
247
  // Call the actual activity (pass the single parameter directly)
253
- const result = await rawActivity(validatedInput);
248
+ const result = await rawActivity(inputResult.value);
254
249
 
255
250
  // Validate output after receiving from network
256
- try {
257
- return activityDef.output.parse(result);
258
- } catch (error) {
259
- if (error instanceof ZodError) {
260
- throw new ActivityOutputValidationError(activityName, error);
261
- }
262
- throw error;
251
+ const outputResult = await activityDef.output["~standard"].validate(result);
252
+ if (outputResult.issues) {
253
+ throw new ActivityOutputValidationError(activityName, outputResult.issues);
263
254
  }
255
+
256
+ return outputResult.value;
264
257
  };
258
+
259
+ // Assign to validatedActivities with proper type handling
260
+ (validatedActivities as Record<string, unknown>)[activityName] = wrappedActivity;
265
261
  }
266
262
 
267
263
  return validatedActivities;
@@ -348,28 +344,21 @@ export function declareActivitiesHandler<T extends ContractDefinition>(
348
344
 
349
345
  wrappedActivities[activityName] = async (input: unknown) => {
350
346
  // Validate input
351
- let validatedInput: unknown;
352
- try {
353
- validatedInput = activityDef.input.parse(input);
354
- } catch (error) {
355
- if (error instanceof ZodError) {
356
- throw new ActivityInputValidationError(activityName, error);
357
- }
358
- throw error;
347
+ const inputResult = await activityDef.input["~standard"].validate(input);
348
+ if (inputResult.issues) {
349
+ throw new ActivityInputValidationError(activityName, inputResult.issues);
359
350
  }
360
351
 
361
352
  // Execute activity
362
- const result = await activityImpl(validatedInput);
353
+ const result = await activityImpl(inputResult.value);
363
354
 
364
355
  // Validate output
365
- try {
366
- return activityDef.output.parse(result);
367
- } catch (error) {
368
- if (error instanceof ZodError) {
369
- throw new ActivityOutputValidationError(activityName, error);
370
- }
371
- throw error;
356
+ const outputResult = await activityDef.output["~standard"].validate(result);
357
+ if (outputResult.issues) {
358
+ throw new ActivityOutputValidationError(activityName, outputResult.issues);
372
359
  }
360
+
361
+ return outputResult.value;
373
362
  };
374
363
  }
375
364
 
@@ -464,17 +453,13 @@ export function declareWorkflow<
464
453
  const singleArg = Array.isArray(args) ? args[0] : args;
465
454
 
466
455
  // Validate workflow input
467
- let validatedInput: WorkerInferInput<TContract["workflows"][TWorkflowName]>;
468
- try {
469
- validatedInput = definition.input.parse(singleArg) as WorkerInferInput<
470
- TContract["workflows"][TWorkflowName]
471
- >;
472
- } catch (error) {
473
- if (error instanceof ZodError) {
474
- throw new WorkflowInputValidationError(String(workflowName), error);
475
- }
476
- throw error;
456
+ const inputResult = await definition.input["~standard"].validate(singleArg);
457
+ if (inputResult.issues) {
458
+ throw new WorkflowInputValidationError(String(workflowName), inputResult.issues);
477
459
  }
460
+ const validatedInput = inputResult.value as WorkerInferInput<
461
+ TContract["workflows"][TWorkflowName]
462
+ >;
478
463
 
479
464
  // Register signal handlers
480
465
  if (definition.signals && signals) {
@@ -488,16 +473,11 @@ export function declareWorkflow<
488
473
  setHandler(signal, async (...args: unknown[]) => {
489
474
  // Extract single parameter (Temporal passes as args array)
490
475
  const input = args.length === 1 ? args[0] : args;
491
- let validatedInput: unknown;
492
- try {
493
- validatedInput = signalDef.input.parse(input);
494
- } catch (error) {
495
- if (error instanceof ZodError) {
496
- throw new SignalInputValidationError(signalName, error);
497
- }
498
- throw error;
476
+ const inputResult = await signalDef.input["~standard"].validate(input);
477
+ if (inputResult.issues) {
478
+ throw new SignalInputValidationError(signalName, inputResult.issues);
499
479
  }
500
- await (handler as SignalHandlerImplementation<SignalDefinition>)(validatedInput);
480
+ await (handler as SignalHandlerImplementation<SignalDefinition>)(inputResult.value);
501
481
  });
502
482
  }
503
483
  }
@@ -515,24 +495,37 @@ export function declareWorkflow<
515
495
  setHandler(query, (...args: unknown[]) => {
516
496
  // Extract single parameter (Temporal passes as args array)
517
497
  const input = args.length === 1 ? args[0] : args;
518
- let validatedInput: unknown;
519
- try {
520
- validatedInput = queryDef.input.parse(input);
521
- } catch (error) {
522
- if (error instanceof ZodError) {
523
- throw new QueryInputValidationError(queryName, error);
524
- }
525
- throw error;
498
+ // Note: Query handlers must be synchronous, so we need to handle validation synchronously
499
+ // Standard Schema validate can return sync or async results
500
+ const inputResult = queryDef.input["~standard"].validate(input);
501
+
502
+ // Handle both sync and async validation results
503
+ if (inputResult instanceof Promise) {
504
+ throw new Error(
505
+ `Query "${queryName}" validation must be synchronous. Use a schema library that supports synchronous validation for queries.`,
506
+ );
507
+ }
508
+
509
+ if (inputResult.issues) {
510
+ throw new QueryInputValidationError(queryName, inputResult.issues);
526
511
  }
527
- const result = (handler as QueryHandlerImplementation<QueryDefinition>)(validatedInput);
528
- try {
529
- return queryDef.output.parse(result);
530
- } catch (error) {
531
- if (error instanceof ZodError) {
532
- throw new QueryOutputValidationError(queryName, error);
533
- }
534
- throw error;
512
+
513
+ const result = (handler as QueryHandlerImplementation<QueryDefinition>)(
514
+ inputResult.value,
515
+ );
516
+
517
+ const outputResult = queryDef.output["~standard"].validate(result);
518
+ if (outputResult instanceof Promise) {
519
+ throw new Error(
520
+ `Query "${queryName}" output validation must be synchronous. Use a schema library that supports synchronous validation for queries.`,
521
+ );
535
522
  }
523
+
524
+ if (outputResult.issues) {
525
+ throw new QueryOutputValidationError(queryName, outputResult.issues);
526
+ }
527
+
528
+ return outputResult.value;
536
529
  });
537
530
  }
538
531
  }
@@ -550,26 +543,21 @@ export function declareWorkflow<
550
543
  setHandler(update, async (...args: unknown[]) => {
551
544
  // Extract single parameter (Temporal passes as args array)
552
545
  const input = args.length === 1 ? args[0] : args;
553
- let validatedInput: unknown;
554
- try {
555
- validatedInput = updateDef.input.parse(input);
556
- } catch (error) {
557
- if (error instanceof ZodError) {
558
- throw new UpdateInputValidationError(updateName, error);
559
- }
560
- throw error;
546
+ const inputResult = await updateDef.input["~standard"].validate(input);
547
+ if (inputResult.issues) {
548
+ throw new UpdateInputValidationError(updateName, inputResult.issues);
561
549
  }
550
+
562
551
  const result = await (handler as UpdateHandlerImplementation<UpdateDefinition>)(
563
- validatedInput,
552
+ inputResult.value,
564
553
  );
565
- try {
566
- return updateDef.output.parse(result);
567
- } catch (error) {
568
- if (error instanceof ZodError) {
569
- throw new UpdateOutputValidationError(updateName, error);
570
- }
571
- throw error;
554
+
555
+ const outputResult = await updateDef.output["~standard"].validate(result);
556
+ if (outputResult.issues) {
557
+ throw new UpdateOutputValidationError(updateName, outputResult.issues);
572
558
  }
559
+
560
+ return outputResult.value;
573
561
  });
574
562
  }
575
563
  }
@@ -607,15 +595,11 @@ export function declareWorkflow<
607
595
  const result = await implementation(context, validatedInput);
608
596
 
609
597
  // Validate workflow output
610
- try {
611
- return definition.output.parse(result) as WorkerInferOutput<
612
- TContract["workflows"][TWorkflowName]
613
- >;
614
- } catch (error) {
615
- if (error instanceof ZodError) {
616
- throw new WorkflowOutputValidationError(String(workflowName), error);
617
- }
618
- throw error;
598
+ const outputResult = await definition.output["~standard"].validate(result);
599
+ if (outputResult.issues) {
600
+ throw new WorkflowOutputValidationError(String(workflowName), outputResult.issues);
619
601
  }
602
+
603
+ return outputResult.value as WorkerInferOutput<TContract["workflows"][TWorkflowName]>;
620
604
  };
621
605
  }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ reporters: ["default"],
6
+ coverage: {
7
+ provider: "v8",
8
+ reporter: ["text", "json", "json-summary", "html"],
9
+ include: ["src/**"],
10
+ },
11
+ },
12
+ });