From 9f8c04f53cd020faa1f225ba728f79e20d692720 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:45:42 +0000 Subject: [PATCH 01/10] Initial plan From dc448f5ffd1266f331dca137409a440fb3225589 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:53:45 +0000 Subject: [PATCH 02/10] Fix documentation examples for Future/Result pattern usage - Update Getting Started guide with correct activity implementation patterns - Fix Activity Handlers guide to show Future/Result pattern - Update Worker Implementation guide with proper error handling examples - Correct Result Pattern guide to clarify activities vs workflows - Fix Client Usage guide to show Future/Result pattern correctly - Update package READMEs with consistent examples - Fix main README with proper example Co-authored-by: btravers <3476441+btravers@users.noreply.github.com> --- README.md | 26 ++- packages/client/README.md | 5 +- packages/worker/README.md | 28 ++- website/docs/guide/activity-handlers.md | 65 ++++-- website/docs/guide/client-usage.md | 39 +++- website/docs/guide/getting-started.md | 19 +- website/docs/guide/result-pattern.md | 241 ++++++++++++-------- website/docs/guide/worker-implementation.md | 100 ++++++-- 8 files changed, 366 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index b6a3cfed..9d1f0edf 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,34 @@ const contract = defineContract({ processOrder: { input: z.object({ orderId: z.string() }), output: z.object({ success: z.boolean() }), - activities: { /* ... */ } + activities: { + processPayment: { + input: z.object({ orderId: z.string() }), + output: z.object({ transactionId: z.string() }) + } + } } } }); -// Fully typed everywhere +// Implement activities with Future/Result pattern +import { declareActivitiesHandler, ActivityError } from '@temporal-contract/worker/activity'; +import { Future } from '@swan-io/boxed'; + +const activities = declareActivitiesHandler({ + contract, + activities: { + processPayment: ({ orderId }) => { + return Future.fromPromise(paymentService.process(orderId)) + .mapError((error) => + new ActivityError('PAYMENT_FAILED', 'Payment failed', error) + ) + .mapOk((txId) => ({ transactionId: txId })); + } + } +}); + +// Call from client - fully typed everywhere const result = await client.executeWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123' } // ✅ TypeScript knows! diff --git a/packages/client/README.md b/packages/client/README.md index 64f4aeb8..19315bef 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -14,10 +14,11 @@ pnpm add @temporal-contract/client @temporal-contract/contract @temporalio/clien ```typescript import { TypedClient } from '@temporal-contract/client'; -import { Connection } from '@temporalio/client'; +import { Connection, Client } from '@temporalio/client'; const connection = await Connection.connect({ address: 'localhost:7233' }); -const client = TypedClient.create(myContract, { connection }); +const temporalClient = new Client({ connection }); +const client = TypedClient.create(myContract, temporalClient); // Execute workflow (fully typed!) const result = await client.executeWorkflow('processOrder', { diff --git a/packages/worker/README.md b/packages/worker/README.md index 439ae24e..9baa3488 100644 --- a/packages/worker/README.md +++ b/packages/worker/README.md @@ -14,12 +14,23 @@ pnpm add @temporal-contract/worker @temporal-contract/contract @temporalio/workf ```typescript // activities.ts -import { declareActivitiesHandler } from '@temporal-contract/worker/activity'; +import { declareActivitiesHandler, ActivityError } from '@temporal-contract/worker/activity'; +import { Future, Result } from '@swan-io/boxed'; export const activities = declareActivitiesHandler({ contract: myContract, activities: { - sendEmail: async ({ to, body }) => ({ sent: true }) + sendEmail: ({ to, body }) => { + return Future.fromPromise(emailService.send({ to, body })) + .mapError((error) => + new ActivityError( + 'EMAIL_FAILED', + error instanceof Error ? error.message : 'Failed to send email', + error + ) + ) + .mapOk(() => ({ sent: true })); + } } }); @@ -30,27 +41,22 @@ export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: myContract, implementation: async (context, input) => { + // Activities return plain values (Result is unwrapped internally) await context.activities.sendEmail({ to: 'user@example.com', body: 'Done!' }); return { success: true }; } }); // worker.ts -import { NativeConnection } from '@temporalio/worker'; -import { createWorker } from '@temporal-contract/worker/worker'; +import { Worker } from '@temporalio/worker'; import { activities } from './activities'; import myContract from './contract'; async function run() { - const connection = await NativeConnection.connect({ - address: 'localhost:7233', - }); - - const worker = await createWorker({ - contract: myContract, - connection, + const worker = await Worker.create({ workflowsPath: require.resolve('./workflows'), activities, + taskQueue: myContract.taskQueue, }); await worker.run(); diff --git a/website/docs/guide/activity-handlers.md b/website/docs/guide/activity-handlers.md index 80e61dee..bb37373a 100644 --- a/website/docs/guide/activity-handlers.md +++ b/website/docs/guide/activity-handlers.md @@ -12,20 +12,36 @@ Instead of defining activity implementations inline, you can extract types for r ```typescript import type { ActivityHandlers } from '@temporal-contract/worker/activity'; +import { declareActivitiesHandler, ActivityError } from '@temporal-contract/worker/activity'; +import { Future, Result } from '@swan-io/boxed'; import { orderContract } from './contract'; // Extract all activity handler types from contract type OrderActivityHandlers = ActivityHandlers; -// Implement activities with explicit types -const sendEmail: OrderActivityHandlers['sendEmail'] = async ({ to, body }) => { - await emailService.send({ to, body }); - return { sent: true }; +// Implement activities with explicit types using Future/Result pattern +const sendEmail: OrderActivityHandlers['sendEmail'] = ({ to, body }) => { + return Future.fromPromise(emailService.send({ to, body })) + .mapError((error) => + new ActivityError( + 'EMAIL_FAILED', + error instanceof Error ? error.message : 'Failed to send email', + error + ) + ) + .mapOk(() => ({ sent: true })); }; -const processPayment: OrderActivityHandlers['processPayment'] = async ({ amount }) => { - const txId = await paymentGateway.charge(amount); - return { transactionId: txId }; +const processPayment: OrderActivityHandlers['processPayment'] = ({ amount }) => { + return Future.fromPromise(paymentGateway.charge(amount)) + .mapError((error) => + new ActivityError( + 'PAYMENT_FAILED', + error instanceof Error ? error.message : 'Payment failed', + error + ) + ) + .mapOk((txId) => ({ transactionId: txId })); }; // Use in handler @@ -77,26 +93,44 @@ Implement activities in separate files: ```typescript // activities/email.ts import type { ActivityHandlers } from '@temporal-contract/worker/activity'; +import { ActivityError } from '@temporal-contract/worker/activity'; +import { Future, Result } from '@swan-io/boxed'; import { orderContract } from '../contracts/order.contract'; type Handlers = ActivityHandlers; -export const sendEmail: Handlers['sendEmail'] = async ({ to, body }) => { - await emailService.send({ to, body }); - return { sent: true }; +export const sendEmail: Handlers['sendEmail'] = ({ to, body }) => { + return Future.fromPromise(emailService.send({ to, body })) + .mapError((error) => + new ActivityError( + 'EMAIL_FAILED', + error instanceof Error ? error.message : 'Failed to send email', + error + ) + ) + .mapOk(() => ({ sent: true })); }; ``` ```typescript // activities/payment.ts import type { ActivityHandlers } from '@temporal-contract/worker/activity'; +import { ActivityError } from '@temporal-contract/worker/activity'; +import { Future, Result } from '@swan-io/boxed'; import { orderContract } from '../contracts/order.contract'; type Handlers = ActivityHandlers; -export const processPayment: Handlers['processPayment'] = async ({ amount }) => { - const txId = await paymentGateway.charge(amount); - return { transactionId: txId }; +export const processPayment: Handlers['processPayment'] = ({ amount }) => { + return Future.fromPromise(paymentGateway.charge(amount)) + .mapError((error) => + new ActivityError( + 'PAYMENT_FAILED', + error instanceof Error ? error.message : 'Payment failed', + error + ) + ) + .mapOk((txId) => ({ transactionId: txId })); }; ``` @@ -165,13 +199,14 @@ Mock activities with correct types: ```typescript import type { ActivityHandlers } from '@temporal-contract/worker/activity'; +import { Future, Result } from '@swan-io/boxed'; type Handlers = ActivityHandlers; // Create mock activities for testing const mockActivities: Handlers = { - sendEmail: async ({ to, body }) => ({ sent: true }), - processPayment: async ({ amount }) => ({ transactionId: 'TEST-TXN' }) + sendEmail: ({ to, body }) => Future.value(Result.Ok({ sent: true })), + processPayment: ({ amount }) => Future.value(Result.Ok({ transactionId: 'TEST-TXN' })) }; // Use in tests diff --git a/website/docs/guide/client-usage.md b/website/docs/guide/client-usage.md index 91a1b7ad..e6b03f72 100644 --- a/website/docs/guide/client-usage.md +++ b/website/docs/guide/client-usage.md @@ -33,10 +33,10 @@ const client = TypedClient.create(myContract, temporalClient); ### Basic Execution -Execute a workflow and wait for completion: +Execute a workflow and wait for completion using the Result/Future pattern: ```typescript -const result = await client.executeWorkflow('processOrder', { +const resultFuture = client.executeWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123', @@ -44,8 +44,18 @@ const result = await client.executeWorkflow('processOrder', { }, }); -// Result is fully typed from your contract! -console.log(result.status); // TypeScript knows the shape +// await the Future to get the Result +const result = await resultFuture; + +// Handle the Result with pattern matching +result.match({ + Ok: (output) => { + console.log('Order processed:', output.status); // TypeScript knows the shape! + }, + Error: (error) => { + console.error('Workflow failed:', error); + }, +}); ``` ### Start Without Waiting @@ -53,7 +63,7 @@ console.log(result.status); // TypeScript knows the shape Start a workflow without waiting for completion: ```typescript -const handle = await client.startWorkflow('processOrder', { +const handleResult = await client.startWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123', @@ -61,11 +71,22 @@ const handle = await client.startWorkflow('processOrder', { }, }); -// Get workflow ID -console.log('Started workflow:', handle.workflowId); +handleResult.match({ + Ok: async (handle) => { + // Get workflow ID + console.log('Started workflow:', handle.workflowId); -// Wait for result later -const result = await handle.result(); + // Wait for result later + const result = await handle.result(); + result.match({ + Ok: (output) => console.log('Completed:', output), + Error: (error) => console.error('Failed:', error), + }); + }, + Error: (error) => { + console.error('Failed to start workflow:', error); + }, +}); ``` ## Type Safety diff --git a/website/docs/guide/getting-started.md b/website/docs/guide/getting-started.md index 2439d768..6e5c4782 100644 --- a/website/docs/guide/getting-started.md +++ b/website/docs/guide/getting-started.md @@ -199,8 +199,8 @@ import { activities } from './activities'; const worker = await Worker.create({ workflowsPath: require.resolve('./workflows'), - activities: activities.activities, - taskQueue: activities.contract.taskQueue, + activities, + taskQueue: 'orders', // or activities.contract.taskQueue }); await worker.run(); @@ -219,13 +219,22 @@ const connection = await Connection.connect({ const temporalClient = new Client({ connection }); const client = TypedClient.create(orderContract, temporalClient); -// Fully typed workflow execution -const result = await client.executeWorkflow('processOrder', { +// Fully typed workflow execution with Result/Future pattern +const resultFuture = client.executeWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123', customerId: 'CUST-456' }, }); -console.log(result.status); // 'success' | 'failed' — fully typed! +const result = await resultFuture; + +result.match({ + Ok: (output) => { + console.log(output.status); // 'success' | 'failed' — fully typed! + }, + Error: (error) => { + console.error('Workflow failed:', error); + }, +}); ``` ## What's Next? diff --git a/website/docs/guide/result-pattern.md b/website/docs/guide/result-pattern.md index eb9c9946..12d9fc03 100644 --- a/website/docs/guide/result-pattern.md +++ b/website/docs/guide/result-pattern.md @@ -167,52 +167,66 @@ For explicit conversion between the two (rarely needed), see the [@temporal-cont ## Pattern Matching -Use `.match()` for elegant error handling: +Activities return plain values when called from workflows. If an activity fails, it will throw an error: ```typescript -const result = await context.activities.processPayment({ amount: 100 }); - -return result.match({ - Ok: (payment) => { - console.log('Payment succeeded:', payment.transactionId); - return Result.Ok({ success: true }); - }, - Error: (error) => { - console.error('Payment failed:', error); - return Result.Error({ type: 'PaymentFailed', error }); +export const processOrder = declareWorkflow({ + workflowName: 'processOrder', + contract: orderContract, + implementation: async (context, input) => { + try { + // Activity returns plain value (Result is unwrapped internally) + const payment = await context.activities.processPayment({ amount: 100 }); + console.log('Payment succeeded:', payment.transactionId); + + return { success: true, transactionId: payment.transactionId }; + } catch (error) { + // Activity errors are thrown + console.error('Payment failed:', error); + return { success: false, transactionId: '' }; + } } }); ``` -## Chaining Results +Note: For child workflows, you do get Result objects. See the Child Workflows section below. -Chain operations with `.flatMap()`: +## Chaining Activities -```typescript -const result = await context.activities.processPayment({ amount: 100 }) - .flatMap(async (payment) => { - // Only runs if payment succeeded - return context.activities.sendEmail({ - to: 'customer@example.com', - body: `Payment ${payment.transactionId} processed` - }); - }) - .flatMap(async (email) => { - // Only runs if both payment and email succeeded - return context.activities.updateDatabase({ - status: 'completed' - }); - }); +When calling multiple activities, use standard async/await with try/catch: -return result.match({ - Ok: () => Result.Ok({ success: true }), - Error: (error) => Result.Error({ type: 'WorkflowFailed', error }) +```typescript +export const processOrder = declareWorkflow({ + workflowName: 'processOrder', + contract: orderContract, + implementation: async (context, input) => { + try { + // Activities return plain values + const payment = await context.activities.processPayment({ amount: 100 }); + + // Next activity only runs if payment succeeded + await context.activities.sendEmail({ + to: 'customer@example.com', + body: `Payment ${payment.transactionId} processed` + }); + + // Update database + await context.activities.updateDatabase({ + status: 'completed' + }); + + return { success: true }; + } catch (error) { + console.error('Workflow failed:', error); + return { success: false }; + } + } }); ``` ## Error Types -Define typed errors: +Define typed errors in your activities: ```typescript type PaymentError = @@ -224,14 +238,18 @@ type EmailError = | { type: 'InvalidEmail' } | { type: 'ServiceUnavailable' }; -// Activities return typed errors +// Activities return Future with typed errors processPayment: ({ amount }) => { return Future.fromPromise(paymentGateway.charge(amount)) - .map<{ transactionId: string }>(txId => ({ transactionId: txId })) - .mapError(error => ({ - type: 'CardDeclined', - // error qualification logic - })); + .mapError(error => { + // Wrap domain errors in ActivityError for Temporal retry policies + return new ActivityError( + 'PAYMENT_FAILED', + error instanceof Error ? error.message : 'Payment failed', + error + ); + }) + .mapOk((txId) => ({ transactionId: txId })); } ``` @@ -239,78 +257,125 @@ processPayment: ({ amount }) => { ### 1. Explicit Error Handling -Errors are part of the type system: +Activities use the Result pattern internally, while workflows use try/catch: ```typescript -// TypeScript knows this can fail -const result: Future = - context.activities.processPayment({ amount: 100 }); +// Activity implementation (uses Result pattern) +const processPayment = ({ amount }) => { + return Future.fromPromise(paymentGateway.charge(amount)) + .mapError((error) => + new ActivityError('PAYMENT_FAILED', 'Payment failed', error) + ) + .mapOk((txId) => ({ transactionId: txId })); +}; -// Must handle error case -if (result.isError()) { - // Handle error -} +// Workflow (uses standard try/catch for activities) +export const processOrder = declareWorkflow({ + workflowName: 'processOrder', + contract: myContract, + implementation: async (context, input) => { + try { + // Activity returns plain value + const payment = await context.activities.processPayment({ amount: 100 }); + return { success: true, transactionId: payment.transactionId }; + } catch (error) { + // Handle activity error + return { success: false }; + } + } +}); ``` -### 2. No Hidden Exceptions +### 2. No Hidden Exceptions in Activities -All failures are explicit in the return type: +Activities explicitly return Results instead of throwing: ```typescript -// ✅ Clear - returns Result -async function processOrder(): Promise> { /* ... */ } +// ✅ Clear - activity returns Future +const processPayment = ({ amount }) => { + return Future.fromPromise(paymentGateway.charge(amount)) + .mapError((error) => + new ActivityError('PAYMENT_FAILED', 'Payment failed', error) + ) + .mapOk((txId) => ({ transactionId: txId })); +}; // ❌ Unclear - might throw anything -async function processOrder(): Promise { /* ... */ } +async function processPayment({ amount }) { + const txId = await paymentGateway.charge(amount); + return { transactionId: txId }; +} ``` -### 3. Railway-Oriented Programming +### 3. Railway-Oriented Programming (Activities) -Chain operations that short-circuit on error: +Activity implementations can chain operations that short-circuit on error: ```mermaid graph LR - A[validateOrder] -->|Ok| B[checkInventory] + A[validateInput] -->|Ok| B[callAPI] A -->|Error| E[Error Path] - B -->|Ok| C[processPayment] + B -->|Ok| C[processResponse] B -->|Error| E - C -->|Ok| D[sendConfirmation] + C -->|Ok| D[Success] C -->|Error| E - D -->|Ok| F[Success] - D -->|Error| E style A fill:#3b82f6,stroke:#1e40af,color:#fff - style F fill:#10b981,stroke:#059669,color:#fff + style D fill:#10b981,stroke:#059669,color:#fff style E fill:#ef4444,stroke:#dc2626,color:#fff ``` ```typescript -return await validateOrder({ orderId }) - .flatMap(() => checkInventory({ orderId })) - .flatMap(() => processPayment({ amount })) - .flatMap(() => sendConfirmation({ orderId })); -// Stops at first error +// Activity implementation with chaining +const processOrder = ({ orderId }) => { + return validateOrderId(orderId) + .flatMap((validId) => fetchOrder(validId)) + .flatMap((order) => processPayment(order)) + .flatMap((payment) => updateDatabase(payment)) + .mapError((error) => + new ActivityError('ORDER_FAILED', 'Order processing failed', error) + ); + // Stops at first error +}; ``` ### 4. Partial Success Handling -Track partial success in complex workflows: +Track partial success in complex workflows using try/catch blocks: ```typescript -const paymentResult = await processPayment({ amount }); -if (paymentResult.isError()) { - return Result.Error({ step: 'payment', error: paymentResult.getError() }); -} - -const shipmentResult = await scheduleShipment({ orderId }); -if (shipmentResult.isError()) { - // Payment succeeded, shipment failed - can handle specially - return Result.Error({ - step: 'shipment', - error: shipmentResult.getError(), - completedSteps: { payment: paymentResult.get() } - }); -} +export const processOrder = declareWorkflow({ + workflowName: 'processOrder', + contract: orderContract, + implementation: async (context, input) => { + let paymentTransactionId: string | undefined; + + try { + // Step 1: Process payment + const payment = await context.activities.processPayment({ amount: input.amount }); + paymentTransactionId = payment.transactionId; + + // Step 2: Schedule shipment + await context.activities.scheduleShipment({ orderId: input.orderId }); + + return { success: true, transactionId: paymentTransactionId }; + } catch (error) { + // Payment succeeded but shipment failed - can handle specially + if (paymentTransactionId) { + // Rollback payment + await context.activities.refundPayment({ transactionId: paymentTransactionId }); + + return { + success: false, + message: 'Shipment failed, payment refunded', + completedSteps: { payment: paymentTransactionId } + }; + } + + return { success: false, message: 'Payment failed' }; + } + } +}); ``` ## Child Workflows @@ -409,19 +474,17 @@ export const orderWorkflow = declareWorkflow({ ## When to Use -### Use Result Pattern When: +### Use Result/Future Pattern When: -- You need explicit error types -- You want to track partial success -- You prefer functional programming style -- You need fine-grained error handling +- **In Activity Implementations**: Always use Future/Result pattern for explicit error handling +- **For Child Workflows**: Child workflows return Results for explicit error handling +- **For Type-Safe Errors**: When you need ActivityError with proper retry policies -### Use Standard Pattern When: +### Use Standard async/await When: -- You're comfortable with exceptions -- You prefer imperative style -- You have simple error handling needs -- You want less boilerplate +- **In Workflow Logic**: Use try/catch when calling activities from workflows +- **For Simple Error Handling**: When standard exception handling is sufficient +- **For Deterministic Code**: Workflows must remain deterministic ## See Also diff --git a/website/docs/guide/worker-implementation.md b/website/docs/guide/worker-implementation.md index ce1f7653..2dce7cf5 100644 --- a/website/docs/guide/worker-implementation.md +++ b/website/docs/guide/worker-implementation.md @@ -31,25 +31,40 @@ sequenceDiagram ## Activities Handler -Create a handler for all activities: +Create a handler for all activities using the Result/Future pattern: ```typescript -import { declareActivitiesHandler } from '@temporal-contract/worker/activity'; +import { declareActivitiesHandler, ActivityError } from '@temporal-contract/worker/activity'; +import { Future, Result } from '@swan-io/boxed'; import { myContract } from './contract'; export const activities = declareActivitiesHandler({ contract: myContract, activities: { - // Global activities - sendEmail: async ({ to, subject, body }) => { - await emailService.send({ to, subject, body }); - return { sent: true }; + // Global activities - use Future/Result for explicit error handling + sendEmail: ({ to, subject, body }) => { + return Future.fromPromise(emailService.send({ to, subject, body })) + .mapError((error) => + new ActivityError( + 'EMAIL_FAILED', + error instanceof Error ? error.message : 'Failed to send email', + error + ) + ) + .mapOk(() => ({ sent: true })); }, // Workflow-specific activities - processPayment: async ({ customerId, amount }) => { - const txId = await paymentGateway.charge(customerId, amount); - return { transactionId: txId, success: true }; + processPayment: ({ customerId, amount }) => { + return Future.fromPromise(paymentGateway.charge(customerId, amount)) + .mapError((error) => + new ActivityError( + 'PAYMENT_FAILED', + error instanceof Error ? error.message : 'Payment failed', + error + ) + ) + .mapOk((txId) => ({ transactionId: txId, success: true })); }, }, }); @@ -57,7 +72,7 @@ export const activities = declareActivitiesHandler({ ## Workflow Implementation -Implement workflows with typed context: +Implement workflows with typed context. Activities called from workflows return plain values (Result is unwrapped internally): ```typescript import { declareWorkflow } from '@temporal-contract/worker/workflow'; @@ -68,6 +83,7 @@ export const processOrder = declareWorkflow({ contract: myContract, implementation: async (context, input) => { // context.activities is fully typed + // Activities return plain values (Result is unwrapped by the framework) const payment = await context.activities.processPayment({ customerId: input.customerId, amount: 100 @@ -79,6 +95,7 @@ export const processOrder = declareWorkflow({ body: 'Your order has been processed' }); + // Return plain object (not Result) return { status: payment.success ? 'success' : 'failed', transactionId: payment.transactionId @@ -97,8 +114,8 @@ import { activities } from './activities'; const worker = await Worker.create({ workflowsPath: require.resolve('./workflows'), - activities: activities.activities, - taskQueue: activities.contract.taskQueue, + activities, + taskQueue: 'my-task-queue', // or myContract.taskQueue }); await worker.run(); @@ -366,23 +383,58 @@ export const createActivities = (services: { ### 3. Error Handling -Handle errors appropriately: +Activities use the Future/Result pattern for explicit error handling: ```typescript +import { declareActivitiesHandler, ActivityError } from '@temporal-contract/worker/activity'; +import { Future, Result } from '@swan-io/boxed'; + export const activities = declareActivitiesHandler({ contract: myContract, activities: { - processPayment: async ({ customerId, amount }) => { - try { - const txId = await paymentGateway.charge(customerId, amount); - return { transactionId: txId, success: true }; - } catch (error) { - // Log error - logger.error('Payment failed', error); - - // Return typed error response - return { transactionId: '', success: false }; - } + processPayment: ({ customerId, amount }) => { + return Future.fromPromise(paymentGateway.charge(customerId, amount)) + .mapError((error) => { + // Wrap technical errors in ActivityError + // This enables proper retry policies and error handling + return new ActivityError( + 'PAYMENT_FAILED', + error instanceof Error ? error.message : 'Payment failed', + error + ); + }) + .mapOk((txId) => ({ transactionId: txId, success: true })); + } + } +}); +``` + +In workflows, activities return plain values. If an activity fails, it will throw an error that can be caught: + +```typescript +export const processOrder = declareWorkflow({ + workflowName: 'processOrder', + contract: myContract, + implementation: async (context, input) => { + try { + // Activity returns plain value if successful + const payment = await context.activities.processPayment({ + customerId: input.customerId, + amount: 100 + }); + + return { + status: 'success', + transactionId: payment.transactionId + }; + } catch (error) { + // Activity errors are thrown and can be caught + console.error('Payment failed:', error); + + return { + status: 'failed', + transactionId: '' + }; } } }); From edfee8a30af3f69deabded723cd1d7da59da5da8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:55:37 +0000 Subject: [PATCH 03/10] Fix worker-usage guide and sample READMEs - Remove references to non-existent createWorker function - Update to use standard Worker.create from @temporalio/worker - Fix child workflow examples to show Result pattern - Update sample READMEs to reflect actual samples - Remove references to non-existent worker-boxed sample Co-authored-by: btravers <3476441+btravers@users.noreply.github.com> --- samples/order-processing-client/README.md | 32 ++++++------- samples/order-processing-worker/README.md | 5 ++- website/docs/guide/worker-usage.md | 55 ++++++++++------------- 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/samples/order-processing-client/README.md b/samples/order-processing-client/README.md index 769d8f92..ad78bce8 100644 --- a/samples/order-processing-client/README.md +++ b/samples/order-processing-client/README.md @@ -8,10 +8,10 @@ This sample demonstrates that a single client can interact with any worker imple This client package demonstrates that: -- Both `order-processing-worker` and `order-processing-worker-boxed` workers implement the **same unified contract** +- Both `order-processing-worker` and `order-processing-worker-nestjs` workers implement the **same unified contract** - A single client works with any worker implementation - From the client's perspective, all workers are identical -- The only difference is in how workers handle errors internally (Promise-based vs. Result/Future pattern) +- Workers handle errors internally using the Result/Future pattern with ActivityError ## Running the Sample @@ -34,17 +34,17 @@ pnpm install && pnpm build 1. Start a worker (choose one): -**Option A: Basic Worker** +**Option A: Standard Worker** ```bash cd ../order-processing-worker pnpm dev ``` -**Option B: Boxed Worker** +**Option B: NestJS Worker** ```bash -cd ../order-processing-worker-boxed +cd ../order-processing-worker-nestjs pnpm dev ``` @@ -89,19 +89,19 @@ The unified contract (`orderProcessingContract`) defines: ### Worker Implementations -Both workers implement the exact same contract but with different patterns: +Both workers implement the exact same contract but with different frameworks: -1. **Basic Worker** (`samples/order-processing-worker`) +1. **Standard Worker** (`samples/order-processing-worker`) - Uses `@temporal-contract/worker` - - Promise-based activities - - Traditional error handling with try/catch - - Simpler to understand and implement - -2. **Boxed Worker** (`samples/order-processing-worker-boxed`) - - Uses `@temporal-contract/worker-boxed` - - Result/Future pattern with `@temporal-contract/boxed` - - Explicit error types in function signatures - - Better type safety and functional composition + - Activities use Result/Future pattern with ActivityError + - Clean Architecture with dependency injection + - Standalone TypeScript application + +2. **NestJS Worker** (`samples/order-processing-worker-nestjs`) + - Uses `@temporal-contract/worker-nestjs` + - Activities use Result/Future pattern with ActivityError + - NestJS dependency injection and decorators + - Better for NestJS-based applications ### Client Perspective diff --git a/samples/order-processing-worker/README.md b/samples/order-processing-worker/README.md index 566e770d..4d7c34d7 100644 --- a/samples/order-processing-worker/README.md +++ b/samples/order-processing-worker/README.md @@ -1,6 +1,6 @@ -# Basic Order Processing +# Order Processing Worker -> Standard Promise-based workflow with Clean Architecture +> Type-safe order processing worker with Clean Architecture and Result/Future pattern ## Running @@ -15,6 +15,7 @@ pnpm dev ## What It Demonstrates - ✅ Type-safe contracts with Zod +- ✅ Result/Future pattern with ActivityError for explicit error handling - ✅ Clean Architecture (Domain → Infrastructure → Application) - ✅ Dependency injection for testability - ✅ Error handling with compensating actions diff --git a/website/docs/guide/worker-usage.md b/website/docs/guide/worker-usage.md index 9945d174..0a6694cb 100644 --- a/website/docs/guide/worker-usage.md +++ b/website/docs/guide/worker-usage.md @@ -83,22 +83,15 @@ export const processOrder = declareWorkflow({ ## Starting a Worker ```typescript -import { NativeConnection, Worker } from '@temporalio/worker'; -import { createWorker } from '@temporal-contract/worker/worker'; +import { Worker } from '@temporalio/worker'; import { myContract } from './contract'; import { activities } from './activities'; async function main() { - const connection = await NativeConnection.connect({ - address: 'localhost:7233', - }); - - const worker = await createWorker({ - contract: myContract, - connection, - namespace: 'default', + const worker = await Worker.create({ workflowsPath: require.resolve('./workflows'), activities, + taskQueue: myContract.taskQueue, }); console.log('Worker started, listening on task queue:', myContract.taskQueue); @@ -170,7 +163,7 @@ implementation: async ({ activities, info, sleep }, input) => { ## Child Workflows -Execute child workflows with type safety: +Execute child workflows with type safety using the Result/Future pattern: ```typescript import { declareWorkflow } from '@temporal-contract/worker/workflow'; @@ -178,9 +171,9 @@ import { declareWorkflow } from '@temporal-contract/worker/workflow'; export const parentWorkflow = declareWorkflow({ workflowName: 'parentWorkflow', contract: myContract, - implementation: async ({ executeChildWorkflow }, input) => { - // Execute child workflow and wait - const childOutput = await executeChildWorkflow( + implementation: async (context, input) => { + // Execute child workflow - returns Future + const childResult = await context.executeChildWorkflow( myContract, 'processPayment', { @@ -189,11 +182,17 @@ export const parentWorkflow = declareWorkflow({ } ); - // Child workflow returns plain values - return { - success: true, - transactionId: childOutput.transactionId - }; + // Handle the Result with pattern matching + return childResult.match({ + Ok: (output) => ({ + success: true, + transactionId: output.transactionId + }), + Error: (error) => ({ + success: false, + error: error.message + }), + }); }, }); ``` @@ -204,12 +203,10 @@ Handle shutdown signals properly: ```typescript async function main() { - const worker = await createWorker({ - contract: myContract, - connection, - namespace: 'default', + const worker = await Worker.create({ workflowsPath: require.resolve('./workflows'), activities, + taskQueue: myContract.taskQueue, }); // Graceful shutdown @@ -232,20 +229,16 @@ async function main() { Run multiple workers with different contracts: ```typescript -const orderWorker = await createWorker({ - contract: orderContract, - connection, - namespace: 'default', +const orderWorker = await Worker.create({ workflowsPath: require.resolve('./order-workflows'), activities: orderActivities, + taskQueue: orderContract.taskQueue, }); -const paymentWorker = await createWorker({ - contract: paymentContract, - connection, - namespace: 'default', +const paymentWorker = await Worker.create({ workflowsPath: require.resolve('./payment-workflows'), activities: paymentActivities, + taskQueue: paymentContract.taskQueue, }); // Run both workers concurrently From 5daa289816b61383d47f8ead6ba6600bc859a0c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:57:05 +0000 Subject: [PATCH 04/10] Fix homepage examples and remove createWorker references - Update index.md with correct Worker.create usage - Fix client example to show Future/Result pattern - Add proper Connection setup in client example - Ensure all worker examples use standard Temporal Worker API Co-authored-by: btravers <3476441+btravers@users.noreply.github.com> --- website/docs/index.md | 48 +++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/website/docs/index.md b/website/docs/index.md index 9d298950..6c2f7012 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -105,12 +105,21 @@ const contract = defineContract({ }); // ✅ Type-safe client -const result = await client.executeWorkflow('processOrder', { +const resultFuture = client.executeWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123', customerId: 'CUST-456' }, // TypeScript knows! }); -console.log(result.status); // 'success' | 'failed' — full autocomplete! +const result = await resultFuture; + +result.match({ + Ok: (output) => { + console.log(output.status); // 'success' | 'failed' — full autocomplete! + }, + Error: (error) => { + console.error('Workflow failed:', error); + }, +}); ``` ## Quick Example @@ -230,22 +239,15 @@ export const processOrder = declareWorkflow({ ``` ```typescript [4. worker.ts] -import { NativeConnection } from '@temporalio/worker'; -import { createWorker } from '@temporal-contract/worker/worker'; +import { Worker } from '@temporalio/worker'; import { orderContract } from './contract'; import { activities } from './activities'; // ✅ Start the worker -const connection = await NativeConnection.connect({ - address: 'localhost:7233', -}); - -const worker = await createWorker({ - contract: orderContract, - connection, - namespace: 'default', - workflowsPath: './workflows', +const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), activities, + taskQueue: orderContract.taskQueue, }); await worker.run(); // Worker is now listening! @@ -253,14 +255,15 @@ await worker.run(); // Worker is now listening! ```typescript [5. client.ts] import { TypedClient } from '@temporal-contract/client'; -import { Client } from '@temporalio/client'; +import { Connection, Client } from '@temporalio/client'; import { orderContract } from './contract'; // ✅ Type-safe client calls +const connection = await Connection.connect({ address: 'localhost:7233' }); const temporalClient = new Client({ connection }); const client = TypedClient.create(orderContract, temporalClient); -const result = await client.executeWorkflow('processOrder', { +const resultFuture = client.executeWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123', @@ -269,9 +272,18 @@ const result = await client.executeWorkflow('processOrder', { }, }); -// Full autocomplete on result! -console.log(result.status); // 'success' | 'failed' -console.log(result.transactionId); // string | undefined +const result = await resultFuture; + +// Handle Result with pattern matching +result.match({ + Ok: (output) => { + console.log(output.status); // 'success' | 'failed' + console.log(output.transactionId); // string | undefined + }, + Error: (error) => { + console.error('Workflow failed:', error); + }, +}); ``` ::: From 9c702644d7d56f514fe2231400bf4e92f34a1e0e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 17:06:26 +0000 Subject: [PATCH 05/10] Destructure context parameter in all workflow examples Changed all workflow implementations from: implementation: async (context, input) => {} to: implementation: async ({ activities, executeChildWorkflow, ... }, input) => {} This follows the codebase convention of destructuring the context parameter directly. Co-authored-by: btravers <3476441+btravers@users.noreply.github.com> --- packages/worker/README.md | 8 ++--- website/docs/guide/entry-points.md | 10 +++--- website/docs/guide/result-pattern.md | 36 ++++++++++----------- website/docs/guide/worker-implementation.md | 32 +++++++++--------- website/docs/guide/worker-usage.md | 4 +-- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/worker/README.md b/packages/worker/README.md index 9baa3488..4b9462aa 100644 --- a/packages/worker/README.md +++ b/packages/worker/README.md @@ -76,9 +76,9 @@ import { declareWorkflow } from '@temporal-contract/worker/workflow'; export const parentWorkflow = declareWorkflow({ workflowName: 'parentWorkflow', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ executeChildWorkflow }, input) => { // Execute child workflow from same contract and wait for result - const childResult = await context.executeChildWorkflow(myContract, 'processPayment', { + const childResult = await executeChildWorkflow(myContract, 'processPayment', { workflowId: `payment-${input.orderId}`, args: { amount: input.totalAmount } }); @@ -89,13 +89,13 @@ export const parentWorkflow = declareWorkflow({ }); // Execute child workflow from another contract (another worker) - const notificationResult = await context.executeChildWorkflow(notificationContract, 'sendNotification', { + const notificationResult = await executeChildWorkflow(notificationContract, 'sendNotification', { workflowId: `notification-${input.orderId}`, args: { message: 'Order received' } }); // Or start child workflow without waiting - const handleResult = await context.startChildWorkflow(myContract, 'sendEmail', { + const handleResult = await startChildWorkflow(myContract, 'sendEmail', { workflowId: `email-${input.orderId}`, args: { to: 'user@example.com', body: 'Order received' } }); diff --git a/website/docs/guide/entry-points.md b/website/docs/guide/entry-points.md index b914e3eb..da4916b5 100644 --- a/website/docs/guide/entry-points.md +++ b/website/docs/guide/entry-points.md @@ -149,12 +149,12 @@ import { orderContract } from 'contract-package'; export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: orderContract, - implementation: async (context, { orderId }) => { - const payment = await context.activities.processPayment({ + implementation: async ({ activities }, { orderId }) => { + const payment = await activities.processPayment({ amount: 100 }); - await context.activities.sendEmail({ + await activities.sendEmail({ to: 'customer@example.com', body: `Order ${orderId} processed` }); @@ -276,9 +276,9 @@ declareActivitiesHandler({ declareWorkflow({ workflowName: 'processOrder', contract, - implementation: async (context, input) => { + implementation: async ({ activities }, input) => { // ✅ TypeScript knows processPayment exists - const result = await context.activities.processPayment({ + const result = await activities.processPayment({ amount: 100 // ✅ Type checked }); diff --git a/website/docs/guide/result-pattern.md b/website/docs/guide/result-pattern.md index 12d9fc03..35b097ce 100644 --- a/website/docs/guide/result-pattern.md +++ b/website/docs/guide/result-pattern.md @@ -173,10 +173,10 @@ Activities return plain values when called from workflows. If an activity fails, export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: orderContract, - implementation: async (context, input) => { + implementation: async ({ activities }, input) => { try { // Activity returns plain value (Result is unwrapped internally) - const payment = await context.activities.processPayment({ amount: 100 }); + const payment = await activities.processPayment({ amount: 100 }); console.log('Payment succeeded:', payment.transactionId); return { success: true, transactionId: payment.transactionId }; @@ -199,19 +199,19 @@ When calling multiple activities, use standard async/await with try/catch: export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: orderContract, - implementation: async (context, input) => { + implementation: async ({ activities }, input) => { try { // Activities return plain values - const payment = await context.activities.processPayment({ amount: 100 }); + const payment = await activities.processPayment({ amount: 100 }); // Next activity only runs if payment succeeded - await context.activities.sendEmail({ + await activities.sendEmail({ to: 'customer@example.com', body: `Payment ${payment.transactionId} processed` }); // Update database - await context.activities.updateDatabase({ + await activities.updateDatabase({ status: 'completed' }); @@ -273,10 +273,10 @@ const processPayment = ({ amount }) => { export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ activities }, input) => { try { // Activity returns plain value - const payment = await context.activities.processPayment({ amount: 100 }); + const payment = await activities.processPayment({ amount: 100 }); return { success: true, transactionId: payment.transactionId }; } catch (error) { // Handle activity error @@ -347,23 +347,23 @@ Track partial success in complex workflows using try/catch blocks: export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: orderContract, - implementation: async (context, input) => { + implementation: async ({ activities }, input) => { let paymentTransactionId: string | undefined; try { // Step 1: Process payment - const payment = await context.activities.processPayment({ amount: input.amount }); + const payment = await activities.processPayment({ amount: input.amount }); paymentTransactionId = payment.transactionId; // Step 2: Schedule shipment - await context.activities.scheduleShipment({ orderId: input.orderId }); + await activities.scheduleShipment({ orderId: input.orderId }); return { success: true, transactionId: paymentTransactionId }; } catch (error) { // Payment succeeded but shipment failed - can handle specially if (paymentTransactionId) { // Rollback payment - await context.activities.refundPayment({ transactionId: paymentTransactionId }); + await activities.refundPayment({ transactionId: paymentTransactionId }); return { success: false, @@ -390,9 +390,9 @@ import { declareWorkflow } from '@temporal-contract/worker/workflow'; export const parentWorkflow = declareWorkflow({ workflowName: 'parentWorkflow', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ executeChildWorkflow }, input) => { // Execute child workflow and wait for result - const result = await context.executeChildWorkflow(myContract, 'processPayment', { + const result = await executeChildWorkflow(myContract, 'processPayment', { workflowId: `payment-${input.orderId}`, args: { amount: input.totalAmount } }); @@ -417,9 +417,9 @@ export const parentWorkflow = declareWorkflow({ export const parentWorkflow = declareWorkflow({ workflowName: 'parentWorkflow', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ startChildWorkflow }, input) => { // Start child workflow without waiting - const handleResult = await context.startChildWorkflow(myContract, 'sendNotification', { + const handleResult = await startChildWorkflow(myContract, 'sendNotification', { workflowId: `notification-${input.orderId}`, args: { message: 'Order received' } }); @@ -450,9 +450,9 @@ import { orderContract, notificationContract } from './contracts'; export const orderWorkflow = declareWorkflow({ workflowName: 'processOrder', contract: orderContract, - implementation: async (context, input) => { + implementation: async ({ executeChildWorkflow }, input) => { // Child workflow from another contract - const notifyResult = await context.executeChildWorkflow( + const notifyResult = await executeChildWorkflow( notificationContract, 'sendOrderConfirmation', { diff --git a/website/docs/guide/worker-implementation.md b/website/docs/guide/worker-implementation.md index 2dce7cf5..1dd33e95 100644 --- a/website/docs/guide/worker-implementation.md +++ b/website/docs/guide/worker-implementation.md @@ -81,15 +81,15 @@ import { myContract } from './contract'; export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ activities }, input) => { // context.activities is fully typed // Activities return plain values (Result is unwrapped by the framework) - const payment = await context.activities.processPayment({ + const payment = await activities.processPayment({ customerId: input.customerId, amount: 100 }); - await context.activities.sendEmail({ + await activities.sendEmail({ to: input.customerId, subject: 'Order Confirmed', body: 'Your order has been processed' @@ -158,13 +158,13 @@ return { txId: 'TXN-123' }; // Wrong field name The workflow context is fully typed based on your contract: ```typescript -implementation: async (context, input) => { +implementation: async ({ activities }, input) => { // TypeScript knows all available activities - context.activities.processPayment // ✅ Available - context.activities.unknownActivity // ❌ TypeScript error + activities.processPayment // ✅ Available + activities.unknownActivity // ❌ TypeScript error // Full autocomplete for parameters - await context.activities.processPayment({ + await activities.processPayment({ // IDE shows: customerId: string, amount: number }); } @@ -207,9 +207,9 @@ import { myContract, notificationContract } from './contracts'; export const parentWorkflow = declareWorkflow({ workflowName: 'parentWorkflow', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ executeChildWorkflow }, input) => { // Execute child workflow from same contract and wait for result - const result = await context.executeChildWorkflow(myContract, 'processPayment', { + const result = await executeChildWorkflow(myContract, 'processPayment', { workflowId: `payment-${input.orderId}`, args: { amount: input.totalAmount } }); @@ -232,9 +232,9 @@ Invoke child workflows from different contracts and workers: export const orderWorkflow = declareWorkflow({ workflowName: 'processOrder', contract: orderContract, - implementation: async (context, input) => { + implementation: async ({ executeChildWorkflow }, input) => { // Process payment in same contract - const paymentResult = await context.executeChildWorkflow( + const paymentResult = await executeChildWorkflow( orderContract, 'processPayment', { @@ -248,7 +248,7 @@ export const orderWorkflow = declareWorkflow({ } // Send notification using another worker's contract - const notificationResult = await context.executeChildWorkflow( + const notificationResult = await executeChildWorkflow( notificationContract, 'sendOrderConfirmation', { @@ -273,9 +273,9 @@ Use `startChildWorkflow` to start a child workflow without waiting for its resul export const orderWorkflow = declareWorkflow({ workflowName: 'processOrder', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ startChildWorkflow }, input) => { // Start background notification workflow - const handleResult = await context.startChildWorkflow( + const handleResult = await startChildWorkflow( notificationContract, 'sendEmail', { @@ -415,10 +415,10 @@ In workflows, activities return plain values. If an activity fails, it will thro export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ activities }, input) => { try { // Activity returns plain value if successful - const payment = await context.activities.processPayment({ + const payment = await activities.processPayment({ customerId: input.customerId, amount: 100 }); diff --git a/website/docs/guide/worker-usage.md b/website/docs/guide/worker-usage.md index 0a6694cb..2afc8219 100644 --- a/website/docs/guide/worker-usage.md +++ b/website/docs/guide/worker-usage.md @@ -171,9 +171,9 @@ import { declareWorkflow } from '@temporal-contract/worker/workflow'; export const parentWorkflow = declareWorkflow({ workflowName: 'parentWorkflow', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ executeChildWorkflow }, input) => { // Execute child workflow - returns Future - const childResult = await context.executeChildWorkflow( + const childResult = await executeChildWorkflow( myContract, 'processPayment', { From b4dab792942481a55367f4eab1f8dd8c3adb6e51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 17:14:05 +0000 Subject: [PATCH 06/10] Changes before error encountered Co-authored-by: btravers <3476441+btravers@users.noreply.github.com> --- website/docs/guide/worker-implementation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guide/worker-implementation.md b/website/docs/guide/worker-implementation.md index 1dd33e95..9e303612 100644 --- a/website/docs/guide/worker-implementation.md +++ b/website/docs/guide/worker-implementation.md @@ -82,7 +82,7 @@ export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: myContract, implementation: async ({ activities }, input) => { - // context.activities is fully typed + // activities is fully typed // Activities return plain values (Result is unwrapped by the framework) const payment = await activities.processPayment({ customerId: input.customerId, From c9bd00057618ca91cf93384840dac064fd9b6be4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 17:31:28 +0000 Subject: [PATCH 07/10] Fix formatting issues (remove trailing whitespace) Run oxfmt to fix CI formatting issues. Co-authored-by: btravers <3476441+btravers@users.noreply.github.com> --- website/docs/guide/result-pattern.md | 18 +++++++++--------- website/docs/guide/worker-implementation.md | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/website/docs/guide/result-pattern.md b/website/docs/guide/result-pattern.md index 35b097ce..26d9c368 100644 --- a/website/docs/guide/result-pattern.md +++ b/website/docs/guide/result-pattern.md @@ -178,7 +178,7 @@ export const processOrder = declareWorkflow({ // Activity returns plain value (Result is unwrapped internally) const payment = await activities.processPayment({ amount: 100 }); console.log('Payment succeeded:', payment.transactionId); - + return { success: true, transactionId: payment.transactionId }; } catch (error) { // Activity errors are thrown @@ -203,18 +203,18 @@ export const processOrder = declareWorkflow({ try { // Activities return plain values const payment = await activities.processPayment({ amount: 100 }); - + // Next activity only runs if payment succeeded await activities.sendEmail({ to: 'customer@example.com', body: `Payment ${payment.transactionId} processed` }); - + // Update database await activities.updateDatabase({ status: 'completed' }); - + return { success: true }; } catch (error) { console.error('Workflow failed:', error); @@ -349,29 +349,29 @@ export const processOrder = declareWorkflow({ contract: orderContract, implementation: async ({ activities }, input) => { let paymentTransactionId: string | undefined; - + try { // Step 1: Process payment const payment = await activities.processPayment({ amount: input.amount }); paymentTransactionId = payment.transactionId; - + // Step 2: Schedule shipment await activities.scheduleShipment({ orderId: input.orderId }); - + return { success: true, transactionId: paymentTransactionId }; } catch (error) { // Payment succeeded but shipment failed - can handle specially if (paymentTransactionId) { // Rollback payment await activities.refundPayment({ transactionId: paymentTransactionId }); - + return { success: false, message: 'Shipment failed, payment refunded', completedSteps: { payment: paymentTransactionId } }; } - + return { success: false, message: 'Payment failed' }; } } diff --git a/website/docs/guide/worker-implementation.md b/website/docs/guide/worker-implementation.md index 9e303612..eed2a695 100644 --- a/website/docs/guide/worker-implementation.md +++ b/website/docs/guide/worker-implementation.md @@ -422,7 +422,7 @@ export const processOrder = declareWorkflow({ customerId: input.customerId, amount: 100 }); - + return { status: 'success', transactionId: payment.transactionId @@ -430,7 +430,7 @@ export const processOrder = declareWorkflow({ } catch (error) { // Activity errors are thrown and can be caught console.error('Payment failed:', error); - + return { status: 'failed', transactionId: '' From 5fdecbc7552dacc9d38e172d2f7ab6af5afedfe7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 17:45:39 +0000 Subject: [PATCH 08/10] Fix worker README to destructure context parameter Updated packages/worker/README.md to use destructured context parameter pattern: - Changed from: implementation: async (context, input) => {} - Changed to: implementation: async ({ activities }, input) => {} This ensures consistency across all documentation examples. Co-authored-by: btravers <3476441+btravers@users.noreply.github.com> --- packages/worker/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/worker/README.md b/packages/worker/README.md index 4b9462aa..73d76fcd 100644 --- a/packages/worker/README.md +++ b/packages/worker/README.md @@ -40,9 +40,9 @@ import { declareWorkflow } from '@temporal-contract/worker/workflow'; export const processOrder = declareWorkflow({ workflowName: 'processOrder', contract: myContract, - implementation: async (context, input) => { + implementation: async ({ activities }, input) => { // Activities return plain values (Result is unwrapped internally) - await context.activities.sendEmail({ to: 'user@example.com', body: 'Done!' }); + await activities.sendEmail({ to: 'user@example.com', body: 'Done!' }); return { success: true }; } }); From bddc86ef9eb46c5bdf40cd497ce456a42f8c2e20 Mon Sep 17 00:00:00 2001 From: Benoit Travers Date: Sun, 21 Dec 2025 12:03:09 -0600 Subject: [PATCH 09/10] Update website/docs/guide/result-pattern.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- website/docs/guide/result-pattern.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/guide/result-pattern.md b/website/docs/guide/result-pattern.md index 26d9c368..addfea9d 100644 --- a/website/docs/guide/result-pattern.md +++ b/website/docs/guide/result-pattern.md @@ -189,7 +189,8 @@ export const processOrder = declareWorkflow({ }); ``` -Note: For child workflows, you do get Result objects. See the Child Workflows section below. +> [!NOTE] +> For child workflows, you do get Result objects. See the Child Workflows section below. ## Chaining Activities From 48106678ec8ade9199eef9fff191ca7827ea5147 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 18:06:43 +0000 Subject: [PATCH 10/10] Simplify variable naming: resultFuture -> future Changed variable name from 'resultFuture' to 'future' across all documentation to avoid redundancy since it's already clear from context that it's a Future. Updated files: - website/docs/index.md (2 instances) - website/docs/guide/getting-started.md - website/docs/guide/client-usage.md Co-authored-by: btravers <3476441+btravers@users.noreply.github.com> --- website/docs/guide/client-usage.md | 4 ++-- website/docs/guide/getting-started.md | 4 ++-- website/docs/index.md | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/website/docs/guide/client-usage.md b/website/docs/guide/client-usage.md index e6b03f72..6175cc9f 100644 --- a/website/docs/guide/client-usage.md +++ b/website/docs/guide/client-usage.md @@ -36,7 +36,7 @@ const client = TypedClient.create(myContract, temporalClient); Execute a workflow and wait for completion using the Result/Future pattern: ```typescript -const resultFuture = client.executeWorkflow('processOrder', { +const future = client.executeWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123', @@ -45,7 +45,7 @@ const resultFuture = client.executeWorkflow('processOrder', { }); // await the Future to get the Result -const result = await resultFuture; +const result = await future; // Handle the Result with pattern matching result.match({ diff --git a/website/docs/guide/getting-started.md b/website/docs/guide/getting-started.md index 6e5c4782..4b4bd243 100644 --- a/website/docs/guide/getting-started.md +++ b/website/docs/guide/getting-started.md @@ -220,12 +220,12 @@ const temporalClient = new Client({ connection }); const client = TypedClient.create(orderContract, temporalClient); // Fully typed workflow execution with Result/Future pattern -const resultFuture = client.executeWorkflow('processOrder', { +const future = client.executeWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123', customerId: 'CUST-456' }, }); -const result = await resultFuture; +const result = await future; result.match({ Ok: (output) => { diff --git a/website/docs/index.md b/website/docs/index.md index 6c2f7012..e9da8ee2 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -105,12 +105,12 @@ const contract = defineContract({ }); // ✅ Type-safe client -const resultFuture = client.executeWorkflow('processOrder', { +const future = client.executeWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123', customerId: 'CUST-456' }, // TypeScript knows! }); -const result = await resultFuture; +const result = await future; result.match({ Ok: (output) => { @@ -263,7 +263,7 @@ const connection = await Connection.connect({ address: 'localhost:7233' }); const temporalClient = new Client({ connection }); const client = TypedClient.create(orderContract, temporalClient); -const resultFuture = client.executeWorkflow('processOrder', { +const future = client.executeWorkflow('processOrder', { workflowId: 'order-123', args: { orderId: 'ORD-123', @@ -272,7 +272,7 @@ const resultFuture = client.executeWorkflow('processOrder', { }, }); -const result = await resultFuture; +const result = await future; // Handle Result with pattern matching result.match({