From 5292b3a076fffd8fd3f5605d888cc5d19bb29a85 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 23 Feb 2026 15:06:48 -0500 Subject: [PATCH 01/50] Add back in TypeScript directory - not yet edited at all. --- references/typescript/advanced-features.md | 373 ++++++++++++++++++ references/typescript/data-handling.md | 256 +++++++++++++ references/typescript/determinism.md | 133 +++++++ references/typescript/error-handling.md | 180 +++++++++ references/typescript/gotchas.md | 403 ++++++++++++++++++++ references/typescript/observability.md | 231 +++++++++++ references/typescript/patterns.md | 422 +++++++++++++++++++++ references/typescript/testing.md | 128 +++++++ references/typescript/typescript.md | 121 ++++++ references/typescript/versioning.md | 307 +++++++++++++++ 10 files changed, 2554 insertions(+) create mode 100644 references/typescript/advanced-features.md create mode 100644 references/typescript/data-handling.md create mode 100644 references/typescript/determinism.md create mode 100644 references/typescript/error-handling.md create mode 100644 references/typescript/gotchas.md create mode 100644 references/typescript/observability.md create mode 100644 references/typescript/patterns.md create mode 100644 references/typescript/testing.md create mode 100644 references/typescript/typescript.md create mode 100644 references/typescript/versioning.md diff --git a/references/typescript/advanced-features.md b/references/typescript/advanced-features.md new file mode 100644 index 0000000..188d692 --- /dev/null +++ b/references/typescript/advanced-features.md @@ -0,0 +1,373 @@ +# TypeScript SDK Advanced Features + +## Continue-as-New + +Use continue-as-new to prevent unbounded history growth in long-running workflows. + +```typescript +import { continueAsNew, workflowInfo } from '@temporalio/workflow'; + +export async function batchProcessingWorkflow(state: ProcessingState): Promise { + while (!state.isComplete) { + // Process next batch + state = await processNextBatch(state); + + // Check history size and continue-as-new if needed + const info = workflowInfo(); + if (info.historyLength > 10000) { + await continueAsNew(state); + } + } + + return 'completed'; +} +``` + +### Continue-as-New with Options + +```typescript +import { continueAsNew } from '@temporalio/workflow'; + +// Continue with modified options +await continueAsNew(newState, { + memo: { lastProcessed: itemId }, + searchAttributes: { BatchNumber: [state.batch + 1] }, +}); +``` + +## Workflow Updates + +Updates allow synchronous interaction with running workflows. + +### Defining Update Handlers + +```typescript +import { defineUpdate, setHandler, condition } from '@temporalio/workflow'; + +// Define the update +export const addItemUpdate = defineUpdate('addItem'); +export const addItemValidatedUpdate = defineUpdate('addItemValidated'); + +export async function orderWorkflow(): Promise { + const items: string[] = []; + let completed = false; + + // Simple update handler + setHandler(addItemUpdate, (item: string) => { + items.push(item); + return items.length; + }); + + // Update handler with validator + setHandler( + addItemValidatedUpdate, + (item: string) => { + items.push(item); + return items.length; + }, + { + validator: (item: string) => { + if (!item) throw new Error('Item cannot be empty'); + if (items.length >= 100) throw new Error('Order is full'); + }, + } + ); + + // Wait for completion signal + await condition(() => completed); + return `Order with ${items.length} items completed`; +} +``` + +### Calling Updates from Client + +```typescript +import { Client } from '@temporalio/client'; +import { addItemUpdate } from './workflows'; + +const client = new Client(); +const handle = client.workflow.getHandle('order-123'); + +// Execute update and wait for result +const count = await handle.executeUpdate(addItemUpdate, { args: ['new-item'] }); +console.log(`Order now has ${count} items`); +``` + +## Nexus Operations + +### WHY: Cross-namespace and cross-cluster service communication +### WHEN: +- **Multi-namespace architectures** - Call operations across Temporal namespaces +- **Service-oriented design** - Expose workflow capabilities as reusable services +- **Cross-cluster communication** - Interact with workflows in different Temporal clusters + +### Defining a Nexus Service + +Define the service interface shared between caller and handler: + +```typescript +// api.ts - shared service definition +import * as nexus from 'nexus-rpc'; + +export const helloService = nexus.service('hello', { + // Synchronous operation + echo: nexus.operation(), + // Workflow-backed operation + hello: nexus.operation(), +}); + +export interface EchoInput { message: string; } +export interface EchoOutput { message: string; } +export interface HelloInput { name: string; language: string; } +export interface HelloOutput { message: string; } +``` + +### Implementing Nexus Service Handlers + +```typescript +// service/handler.ts +import * as nexus from 'nexus-rpc'; +import * as temporalNexus from '@temporalio/nexus'; +import { helloService, EchoInput, EchoOutput, HelloInput, HelloOutput } from '../api'; +import { helloWorkflow } from './workflows'; + +export const helloServiceHandler = nexus.serviceHandler(helloService, { + // Synchronous operation - simple async function + echo: async (ctx, input: EchoInput): Promise => { + // Can access Temporal client via temporalNexus.getClient() + return input; + }, + + // Workflow-backed operation + hello: new temporalNexus.WorkflowRunOperationHandler( + async (ctx, input: HelloInput) => { + return await temporalNexus.startWorkflow(ctx, helloWorkflow, { + args: [input], + workflowId: ctx.requestId ?? crypto.randomUUID(), + }); + }, + ), +}); +``` + +### Calling Nexus Operations from Workflows + +```typescript +// caller/workflows.ts +import * as wf from '@temporalio/workflow'; +import { helloService } from '../api'; + +const HELLO_SERVICE_ENDPOINT = 'my-nexus-endpoint-name'; + +export async function callerWorkflow(name: string): Promise { + const nexusClient = wf.createNexusClient({ + service: helloService, + endpoint: HELLO_SERVICE_ENDPOINT, + }); + + const result = await nexusClient.executeOperation( + 'hello', + { name, language: 'en' }, + { scheduleToCloseTimeout: '10s' }, + ); + + return result.message; +} +``` + +## Activity Cancellation and Heartbeating + +### ActivityCancellationType + +Control how activities respond to workflow cancellation: + +```typescript +import { proxyActivities, ActivityCancellationType, isCancellation, log } from '@temporalio/workflow'; +import type * as activities from './activities'; + +const { longRunningActivity } = proxyActivities({ + startToCloseTimeout: '60s', + heartbeatTimeout: '3s', + // TRY_CANCEL (default): Request cancellation, resolve/reject immediately + // WAIT_CANCELLATION_COMPLETED: Wait for activity to acknowledge cancellation + // WAIT_CANCELLATION_REQUESTED: Wait for cancellation request to be delivered + // ABANDON: Don't request cancellation + cancellationType: ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, +}); + +export async function workflowWithCancellation(): Promise { + try { + await longRunningActivity(); + } catch (err) { + if (isCancellation(err)) { + log.info('Workflow cancelled along with its activity'); + // Use CancellationScope.nonCancellable for cleanup + } + throw err; + } +} +``` + +### Activity Heartbeat Details for Resumption + +Use heartbeat details to resume long-running activities from where they left off: + +```typescript +// activities.ts +import { activityInfo, log, sleep, CancelledFailure, heartbeat } from '@temporalio/activity'; + +export async function processWithProgress(sleepIntervalMs = 1000): Promise { + try { + // Resume from last heartbeat on retry + const startingPoint = activityInfo().heartbeatDetails || 1; + log.info('Starting activity at progress', { startingPoint }); + + for (let progress = startingPoint; progress <= 100; ++progress) { + log.info('Progress', { progress }); + await sleep(sleepIntervalMs); + // Heartbeat with progress - allows resuming on retry + heartbeat(progress); + } + } catch (err) { + if (err instanceof CancelledFailure) { + log.warn('Activity cancelled', { message: err.message }); + // Cleanup code here + } + throw err; + } +} +``` + +## Schedules + +Create recurring workflow executions. + +```typescript +import { Client, ScheduleOverlapPolicy } from '@temporalio/client'; + +const client = new Client(); + +// Create a schedule +const schedule = await client.schedule.create({ + scheduleId: 'daily-report', + spec: { + intervals: [{ every: '1 day' }], + }, + action: { + type: 'startWorkflow', + workflowType: 'dailyReportWorkflow', + taskQueue: 'reports', + args: [], + }, + policies: { + overlap: ScheduleOverlapPolicy.SKIP, + }, +}); + +// Manage schedules +const handle = client.schedule.getHandle('daily-report'); +await handle.pause('Maintenance window'); +await handle.unpause(); +await handle.trigger(); // Run immediately +await handle.delete(); +``` + +## Sinks + +Sinks allow workflows to emit events for side effects (logging, metrics). + +```typescript +import { proxySinks, Sinks } from '@temporalio/workflow'; + +// Define sink interface +export interface LoggerSinks extends Sinks { + logger: { + info(message: string, attrs: Record): void; + error(message: string, attrs: Record): void; + }; +} + +// Use in workflow +const { logger } = proxySinks(); + +export async function myWorkflow(input: string): Promise { + logger.info('Workflow started', { input }); + + const result = await someActivity(input); + + logger.info('Workflow completed', { result }); + return result; +} + +// Implement sink in worker +const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + activities, + taskQueue: 'my-queue', + sinks: { + logger: { + info: { + fn(workflowInfo, message, attrs) { + console.log(`[${workflowInfo.workflowId}] ${message}`, attrs); + }, + callDuringReplay: false, // Don't log during replay + }, + error: { + fn(workflowInfo, message, attrs) { + console.error(`[${workflowInfo.workflowId}] ${message}`, attrs); + }, + callDuringReplay: false, + }, + }, + }, +}); +``` + +## CancellationScope Patterns + +Advanced cancellation control within workflows. + +```typescript +import { + CancellationScope, + CancelledFailure, + sleep, +} from '@temporalio/workflow'; + +export async function workflowWithCancellation(): Promise { + // Non-cancellable scope - runs to completion even if workflow cancelled + const criticalResult = await CancellationScope.nonCancellable(async () => { + return await criticalActivity(); + }); + + // Cancellable scope with timeout + try { + await CancellationScope.cancellable(async () => { + await Promise.race([ + longRunningActivity(), + sleep('5 minutes').then(() => { + CancellationScope.current().cancel(); + }), + ]); + }); + } catch (err) { + if (err instanceof CancelledFailure) { + // Handle cancellation + await cleanupActivity(); + } + throw err; + } + + return criticalResult; +} +``` + +## Best Practices + +1. Use continue-as-new for long-running workflows to prevent history growth +2. Prefer updates over signals when you need a response +3. Use sinks with `callDuringReplay: false` for logging +4. Use CancellationScope.nonCancellable for critical cleanup operations +5. Use `ActivityCancellationType.WAIT_CANCELLATION_COMPLETED` when cleanup is important +6. Store progress in heartbeat details for resumable long-running activities +7. Use Nexus for cross-namespace service communication diff --git a/references/typescript/data-handling.md b/references/typescript/data-handling.md new file mode 100644 index 0000000..88aad84 --- /dev/null +++ b/references/typescript/data-handling.md @@ -0,0 +1,256 @@ +# TypeScript SDK Data Handling + +## Overview + +The TypeScript SDK uses data converters to serialize/deserialize workflow inputs, outputs, and activity parameters. + +## Default Data Converter + +The default converter handles: +- `undefined` and `null` +- `Uint8Array` (as binary) +- Protobuf messages (if configured) +- JSON-serializable types + +## Search Attributes + +Custom searchable fields for workflow visibility. + +### Setting Search Attributes at Start + +```typescript +import { Client } from '@temporalio/client'; + +const client = new Client(); + +await client.workflow.start('orderWorkflow', { + taskQueue: 'orders', + workflowId: `order-${orderId}`, + args: [order], + searchAttributes: { + OrderId: [orderId], + CustomerType: ['premium'], + OrderTotal: [99.99], + CreatedAt: [new Date()], + }, +}); +``` + +### Upserting Search Attributes from Workflow + +```typescript +import { upsertSearchAttributes, workflowInfo } from '@temporalio/workflow'; + +export async function orderWorkflow(order: Order): Promise { + // Update status as workflow progresses + upsertSearchAttributes({ + OrderStatus: ['processing'], + }); + + await processOrder(order); + + upsertSearchAttributes({ + OrderStatus: ['completed'], + }); + + return 'done'; +} +``` + +### Reading Search Attributes + +```typescript +import { workflowInfo } from '@temporalio/workflow'; + +export async function orderWorkflow(): Promise { + const info = workflowInfo(); + const searchAttrs = info.searchAttributes; + const orderId = searchAttrs?.OrderId?.[0]; + // ... +} +``` + +### Querying Workflows by Search Attributes + +```typescript +const client = new Client(); + +// List workflows using search attributes +for await (const workflow of client.workflow.list({ + query: 'OrderStatus = "processing" AND CustomerType = "premium"', +})) { + console.log(`Workflow ${workflow.workflowId} is still processing`); +} +``` + +## Workflow Memo + +Store arbitrary metadata with workflows (not searchable). + +```typescript +// Set memo at workflow start +await client.workflow.start('orderWorkflow', { + taskQueue: 'orders', + workflowId: `order-${orderId}`, + args: [order], + memo: { + customerName: order.customerName, + notes: 'Priority customer', + }, +}); + +// Read memo from workflow +import { workflowInfo } from '@temporalio/workflow'; + +export async function orderWorkflow(): Promise { + const info = workflowInfo(); + const customerName = info.memo?.customerName; + // ... +} +``` + +## Custom Data Converter + +Create custom converters for special serialization needs. + +```typescript +import { + DataConverter, + PayloadConverter, + defaultPayloadConverter, +} from '@temporalio/common'; + +class CustomPayloadConverter implements PayloadConverter { + toPayload(value: unknown): Payload | undefined { + // Custom serialization logic + return defaultPayloadConverter.toPayload(value); + } + + fromPayload(payload: Payload): T { + // Custom deserialization logic + return defaultPayloadConverter.fromPayload(payload); + } +} + +const dataConverter: DataConverter = { + payloadConverter: new CustomPayloadConverter(), +}; + +// Apply to client +const client = new Client({ + dataConverter, +}); + +// Apply to worker +const worker = await Worker.create({ + dataConverter, + // ... +}); +``` + +## Payload Codec (Encryption) + +Encrypt sensitive workflow data. + +```typescript +import { PayloadCodec, Payload } from '@temporalio/common'; + +class EncryptionCodec implements PayloadCodec { + private readonly encryptionKey: Uint8Array; + + constructor(key: Uint8Array) { + this.encryptionKey = key; + } + + async encode(payloads: Payload[]): Promise { + return Promise.all( + payloads.map(async (payload) => ({ + metadata: { + encoding: 'binary/encrypted', + }, + data: await this.encrypt(payload.data ?? new Uint8Array()), + })) + ); + } + + async decode(payloads: Payload[]): Promise { + return Promise.all( + payloads.map(async (payload) => { + if (payload.metadata?.encoding === 'binary/encrypted') { + return { + ...payload, + data: await this.decrypt(payload.data ?? new Uint8Array()), + }; + } + return payload; + }) + ); + } + + private async encrypt(data: Uint8Array): Promise { + // Implement encryption (e.g., using Web Crypto API) + return data; + } + + private async decrypt(data: Uint8Array): Promise { + // Implement decryption + return data; + } +} + +// Apply codec +const dataConverter: DataConverter = { + payloadCodec: new EncryptionCodec(encryptionKey), +}; +``` + +## Protobuf Support + +Using Protocol Buffers for type-safe serialization. + +```typescript +import { DefaultPayloadConverterWithProtobufs } from '@temporalio/common/lib/protobufs'; + +const dataConverter: DataConverter = { + payloadConverter: new DefaultPayloadConverterWithProtobufs({ + protobufRoot: myProtobufRoot, + }), +}; +``` + +## Large Payloads + +For large data, consider: + +1. **Store externally**: Put large data in S3/GCS, pass references in workflows +2. **Use compression codec**: Compress payloads automatically +3. **Chunk data**: Split large arrays across multiple activities + +```typescript +// Example: Reference pattern for large data +import { proxyActivities } from '@temporalio/workflow'; + +const { uploadToStorage, downloadFromStorage } = proxyActivities({ + startToCloseTimeout: '5 minutes', +}); + +export async function processLargeDataWorkflow(dataRef: string): Promise { + // Download data from storage using reference + const data = await downloadFromStorage(dataRef); + + // Process data... + const result = await processData(data); + + // Upload result and return reference + const resultRef = await uploadToStorage(result); +} +``` + +## Best Practices + +1. Keep payloads small (< 2MB recommended) +2. Use search attributes for business-level visibility and filtering +3. Encrypt sensitive data with PayloadCodec +4. Store large data externally with references +5. Use memo for non-searchable metadata +6. Configure the same data converter on both client and worker diff --git a/references/typescript/determinism.md b/references/typescript/determinism.md new file mode 100644 index 0000000..e7db427 --- /dev/null +++ b/references/typescript/determinism.md @@ -0,0 +1,133 @@ +# TypeScript SDK Determinism + +## Overview + +The TypeScript SDK runs workflows in an isolated V8 sandbox that automatically provides determinism. + +## Why Determinism Matters + +Temporal provides durable execution through **History Replay**. When a Worker needs to restore workflow state (after a crash, cache eviction, or to continue after a long timer), it re-executes the workflow code from the beginning. + +**The Critical Rule**: A Workflow is deterministic if every execution of its code produces the same Commands, in the same sequence, given the same input. + +During replay, the Worker: +1. Re-executes your workflow code +2. Compares generated Commands to Events in the history +3. Uses stored results from history instead of re-executing Activities + +If the Commands don't match the history, the Worker cannot accurately restore state, causing a **non-deterministic error**. + +### Example of Non-Determinism + +```typescript +// WRONG - Non-deterministic! +export async function badWorkflow(): Promise { + await importData(); + + // Random value changes on each execution + if (Math.random() > 0.5) { // Would be a problem without sandbox + await sleep('30 minutes'); + } + + return await sendReport(); +} +``` + +Without the sandbox, if the random number was 0.8 on first run (timer started) but 0.3 on replay (no timer), the Worker would see a `StartTimer` command that doesn't match history, causing a non-deterministic error. + +**Good news**: The TypeScript sandbox automatically makes `Math.random()` deterministic, so this specific code actually works. But the concept is important for understanding WHY the sandbox exists. + +## Automatic Replacements + +The sandbox replaces non-deterministic APIs with deterministic versions: + +| Original | Replacement | +|----------|-------------| +| `Math.random()` | Seeded PRNG per workflow | +| `Date.now()` | Workflow task start time | +| `Date` constructor | Deterministic time | +| `setTimeout` | Workflow timer | + +## Safe Operations + +```typescript +import { sleep } from '@temporalio/workflow'; + +// These are all safe in workflows: +Math.random(); // Deterministic +Date.now(); // Deterministic +new Date(); // Deterministic +await sleep('1 hour'); // Durable timer + +// Object iteration is deterministic in JavaScript +for (const key in obj) { } +Object.keys(obj).forEach(k => { }); +``` + +## Forbidden Operations + +```typescript +// DO NOT do these in workflows: +import fs from 'fs'; // Node.js modules +fetch('https://...'); // Network I/O +console.log(); // Side effects (use workflow.log) +``` + +## Type-Only Activity Imports + +```typescript +// CORRECT - type-only import +import type * as activities from './activities'; + +const { myActivity } = proxyActivities({ + startToCloseTimeout: '5 minutes', +}); + +// WRONG - actual import brings in implementation +import * as activities from './activities'; +``` + +## Workflow Bundling + +Workflows are bundled by the worker using Webpack. The bundled code runs in isolation. + +```typescript +const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), // Gets bundled + activities, // Not bundled, runs in Node.js + taskQueue: 'my-queue', +}); +``` + +## Patching for Versioning + +Use `patched()` to safely change workflow code while maintaining compatibility with running workflows: + +```typescript +import { patched, deprecatePatch } from '@temporalio/workflow'; + +export async function myWorkflow(): Promise { + if (patched('my-change')) { + // New code path + return await newImplementation(); + } else { + // Old code path (for replay) + return await oldImplementation(); + } +} + +// Later, after all old workflows complete: +export async function myWorkflow(): Promise { + deprecatePatch('my-change'); + return await newImplementation(); +} +``` + +## Best Practices + +1. Use type-only imports for activities in workflow files +2. Match all @temporalio package versions +3. Use `sleep()` from workflow package, never `setTimeout` directly +4. Keep workflows focused on orchestration +5. Test with replay to verify determinism +6. Use `patched()` when changing workflow logic for running workflows diff --git a/references/typescript/error-handling.md b/references/typescript/error-handling.md new file mode 100644 index 0000000..4057043 --- /dev/null +++ b/references/typescript/error-handling.md @@ -0,0 +1,180 @@ +# TypeScript SDK Error Handling + +## Overview + +The TypeScript SDK uses `ApplicationFailure` for application errors with support for non-retryable marking. + +## Application Failures + +```typescript +import { ApplicationFailure } from '@temporalio/workflow'; + +export async function myWorkflow(): Promise { + throw ApplicationFailure.create({ + message: 'Invalid input', + type: 'ValidationError', + nonRetryable: true, + }); +} +``` + +## Activity Errors + +```typescript +import { ApplicationFailure } from '@temporalio/activity'; + +export async function validateActivity(input: string): Promise { + if (!isValid(input)) { + throw ApplicationFailure.create({ + message: `Invalid input: ${input}`, + type: 'ValidationError', + nonRetryable: true, + }); + } +} +``` + +## Handling Errors in Workflows + +```typescript +import { proxyActivities, ApplicationFailure } from '@temporalio/workflow'; +import type * as activities from './activities'; + +const { riskyActivity } = proxyActivities({ + startToCloseTimeout: '5 minutes', +}); + +export async function workflowWithErrorHandling(): Promise { + try { + return await riskyActivity(); + } catch (err) { + if (err instanceof ApplicationFailure) { + console.log(`Activity failed: ${err.type} - ${err.message}`); + } + throw err; + } +} +``` + +## Retry Configuration + +```typescript +const { myActivity } = proxyActivities({ + startToCloseTimeout: '10 minutes', + retry: { + initialInterval: '1s', + backoffCoefficient: 2, + maximumInterval: '1m', + maximumAttempts: 5, + nonRetryableErrorTypes: ['ValidationError', 'PaymentError'], + }, +}); +``` + +## Timeout Configuration + +```typescript +const { myActivity } = proxyActivities({ + startToCloseTimeout: '5 minutes', // Single attempt + scheduleToCloseTimeout: '30 minutes', // Including retries + heartbeatTimeout: '30 seconds', // Between heartbeats +}); +``` + +## Cancellation Handling in Activities + +```typescript +import { CancelledFailure, heartbeat } from '@temporalio/activity'; + +export async function cancellableActivity(): Promise { + try { + while (true) { + heartbeat(); + await doWork(); + } + } catch (err) { + if (err instanceof CancelledFailure) { + await cleanup(); + } + throw err; + } +} +``` + +## Idempotency Patterns + +Activities may be executed more than once due to retries. Design activities to be idempotent to prevent duplicate side effects. + +### Why Activities Need Idempotency + +Consider this scenario: +1. Worker polls and accepts an Activity Task +2. Activity function completes successfully +3. Worker crashes before notifying the Cluster +4. Cluster retries the Activity (doesn't know it completed) + +If the Activity charged a credit card, the customer would be charged twice. + +### Using Idempotency Keys + +Use the Workflow Run ID + Activity ID as an idempotency key - this is constant across retries but unique across workflow executions: + +```typescript +import { info } from '@temporalio/activity'; + +export async function chargePayment( + customerId: string, + amount: number +): Promise { + // Create idempotency key from workflow context + const idempotencyKey = `${info().workflowRunId}-${info().activityId}`; + + // Pass to external service (e.g., Stripe, payment processor) + const result = await paymentService.charge({ + customerId, + amount, + idempotencyKey, // Service ignores duplicate requests with same key + }); + + return result.transactionId; +} +``` + +**Important**: Use `workflowRunId` (not `workflowId`) because workflow IDs can be reused. + +### Granular Activities + +Make activities more granular to reduce the scope of potential retries: + +```typescript +// BETTER - Three small activities +export async function lookupCustomer(customerId: string): Promise { + return await db.findCustomer(customerId); +} + +export async function processPayment(paymentInfo: PaymentInfo): Promise { + const idempotencyKey = `${info().workflowRunId}-${info().activityId}`; + return await paymentService.process(paymentInfo, idempotencyKey); +} + +export async function sendReceipt(transactionId: string): Promise { + await emailService.sendReceipt(transactionId); +} + +// WORSE - One large activity doing multiple things +export async function processOrder(order: Order): Promise { + const customer = await db.findCustomer(order.customerId); + await paymentService.process(order.payment); // If this fails here... + await emailService.sendReceipt(order.id); // ...all three retry +} +``` + +## Best Practices + +1. Use specific error types for different failure modes +2. Set `nonRetryable: true` for permanent failures +3. Configure `nonRetryableErrorTypes` in retry policy +4. Handle `CancelledFailure` in activities that need cleanup +5. Always re-throw errors after handling +6. Use idempotency keys for activities with external side effects +7. Make activities granular to minimize retry scope diff --git a/references/typescript/gotchas.md b/references/typescript/gotchas.md new file mode 100644 index 0000000..f648a83 --- /dev/null +++ b/references/typescript/gotchas.md @@ -0,0 +1,403 @@ +# TypeScript Gotchas + +TypeScript-specific mistakes and anti-patterns. See also [Common Gotchas](../core/gotchas.md) for language-agnostic concepts. + +## Idempotency + +```typescript +// BAD - May charge customer multiple times on retry +export async function chargePayment(orderId: string, amount: number): Promise { + return await paymentApi.charge(customerId, amount); +} + +// GOOD - Safe for retries +export async function chargePayment(orderId: string, amount: number): Promise { + return await paymentApi.charge(customerId, amount, { + idempotencyKey: `order-${orderId}`, + }); +} +``` + +## Replay Safety + +### Side Effects in Workflows + +```typescript +// BAD - console.log runs on every replay +export async function notificationWorkflow(): Promise { + console.log('Starting workflow'); // Runs on replay too + await sendSlackNotification('Started'); // Side effect in workflow! + await activities.doWork(); +} + +// GOOD - Use workflow logger and activities for side effects +import { log } from '@temporalio/workflow'; + +export async function notificationWorkflow(): Promise { + log.info('Starting workflow'); // Only logs on first execution + await activities.sendNotification('Started'); +} +``` + +### Non-Deterministic Operations + +The TypeScript SDK automatically replaces some non-deterministic operations: + +```typescript +// These are SAFE - automatically replaced by SDK +const now = Date.now(); // Deterministic +const random = Math.random(); // Deterministic +const id = crypto.randomUUID(); // Deterministic (if using workflow's crypto) + +// For explicit deterministic UUID, use: +import { uuid4 } from '@temporalio/workflow'; +const id = uuid4(); +``` + +## Query Handlers + +### Modifying State + +```typescript +// BAD - Query modifies state +const queues = new Map(); + +export const getNextItemQuery = defineQuery('getNextItem'); + +export async function queueWorkflow(queueId: string): Promise { + const queue: string[] = []; + + setHandler(getNextItemQuery, () => { + return queue.shift(); // Mutates state! + }); + + await condition(() => false); +} + +// GOOD - Query reads, Update modifies +export const peekQuery = defineQuery('peek'); +export const dequeueUpdate = defineUpdate('dequeue'); + +export async function queueWorkflow(): Promise { + const queue: string[] = []; + + setHandler(peekQuery, () => queue[0]); + + setHandler(dequeueUpdate, () => queue.shift()); + + await condition(() => false); +} +``` + +### Blocking in Queries + +```typescript +// BAD - Queries cannot await +setHandler(getDataQuery, async () => { + if (!data) { + data = await activities.fetchData(); // Cannot await in query! + } + return data; +}); + +// GOOD - Query returns state, signal triggers refresh +setHandler(refreshSignal, async () => { + data = await activities.fetchData(); +}); + +setHandler(getDataQuery, () => data); +``` + +## Activity Imports + +### Importing Implementations Instead of Types + +**The Problem**: Importing activity implementations brings Node.js code into the V8 workflow sandbox, causing bundling errors or runtime failures. + +```typescript +// BAD - Brings actual code into workflow sandbox +import * as activities from './activities'; + +const { greet } = proxyActivities({ + startToCloseTimeout: '1 minute', +}); + +// GOOD - Type-only import +import type * as activities from './activities'; + +const { greet } = proxyActivities({ + startToCloseTimeout: '1 minute', +}); +``` + +### Importing Node.js Modules in Workflows + +```typescript +// BAD - fs is not available in workflow sandbox +import * as fs from 'fs'; + +export async function myWorkflow(): Promise { + const data = fs.readFileSync('file.txt'); // Will fail! +} + +// GOOD - File I/O belongs in activities +export async function myWorkflow(): Promise { + const data = await activities.readFile('file.txt'); +} +``` + +## Bundling Issues + +### Missing Dependencies in Workflow Bundle + +```typescript +// If using external packages in workflows, ensure they're bundled + +// worker.ts +const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + bundlerOptions: { + // Include specific packages if needed + ignoreModules: ['some-node-only-package'], + }, +}); +``` + +### Package Version Mismatches + +All `@temporalio/*` packages must have the same version: + +```json +// BAD - Version mismatch +{ + "dependencies": { + "@temporalio/client": "1.9.0", + "@temporalio/worker": "1.8.0", + "@temporalio/workflow": "1.9.1" + } +} + +// GOOD - All versions match +{ + "dependencies": { + "@temporalio/client": "1.9.0", + "@temporalio/worker": "1.9.0", + "@temporalio/workflow": "1.9.0" + } +} +``` + +## Error Handling + +### Swallowing Errors + +```typescript +// BAD - Error is hidden +export async function riskyWorkflow(): Promise { + try { + await activities.riskyOperation(); + } catch { + // Error is lost! + } +} + +// GOOD - Handle appropriately +import { log } from '@temporalio/workflow'; + +export async function riskyWorkflow(): Promise { + try { + await activities.riskyOperation(); + } catch (err) { + log.error('Activity failed', { error: err }); + throw err; // Or use fallback, compensate, etc. + } +} +``` + +### Wrong Retry Classification + +```typescript +// BAD - Network errors should be retried +export async function callApi(): Promise { + try { + return await fetch(url); + } catch (err) { + throw ApplicationFailure.nonRetryable('Connection failed'); + } +} + +// GOOD - Only permanent failures are non-retryable +export async function callApi(): Promise { + try { + return await fetch(url); + } catch (err) { + if (err instanceof InvalidCredentialsError) { + throw ApplicationFailure.nonRetryable('Invalid API key'); + } + throw err; // Let Temporal retry network errors + } +} +``` + +## Retry Policies + +### Too Aggressive + +```typescript +// BAD - Gives up too easily +const result = await activities.flakyApiCall({ + scheduleToCloseTimeout: '30 seconds', + retry: { maximumAttempts: 1 }, +}); + +// GOOD - Resilient to transient failures +const result = await activities.flakyApiCall({ + scheduleToCloseTimeout: '10 minutes', + retry: { + initialInterval: '1 second', + maximumInterval: '1 minute', + backoffCoefficient: 2, + maximumAttempts: 10, + }, +}); +``` + +## Cancellation + +### Not Handling Cancellation + +```typescript +// BAD - Cleanup doesn't run on cancellation +export async function workflowWithCleanup(): Promise { + await activities.acquireResource(); + await activities.doWork(); + await activities.releaseResource(); // Never runs if cancelled! +} + +// GOOD - Use CancellationScope for cleanup +import { CancellationScope } from '@temporalio/workflow'; + +export async function workflowWithCleanup(): Promise { + await activities.acquireResource(); + try { + await activities.doWork(); + } finally { + // Run cleanup even on cancellation + await CancellationScope.nonCancellable(async () => { + await activities.releaseResource(); + }); + } +} +``` + +## Heartbeating + +### Forgetting to Heartbeat Long Activities + +```typescript +// BAD - No heartbeat, can't detect stuck activities +export async function processLargeFile(path: string): Promise { + for await (const chunk of readChunks(path)) { + await processChunk(chunk); // Takes hours, no heartbeat + } +} + +// GOOD - Regular heartbeats with progress +import { heartbeat } from '@temporalio/activity'; + +export async function processLargeFile(path: string): Promise { + let i = 0; + for await (const chunk of readChunks(path)) { + heartbeat(`Processing chunk ${i++}`); + await processChunk(chunk); + } +} +``` + +### Heartbeat Timeout Too Short + +```typescript +// BAD - Heartbeat timeout shorter than processing time +const { processChunk } = proxyActivities({ + startToCloseTimeout: '30 minutes', + heartbeatTimeout: '10 seconds', // Too short! +}); + +// GOOD - Heartbeat timeout allows for processing variance +const { processChunk } = proxyActivities({ + startToCloseTimeout: '30 minutes', + heartbeatTimeout: '2 minutes', +}); +``` + +## Testing + +### Not Testing Failures + +```typescript +import { TestWorkflowEnvironment } from '@temporalio/testing'; +import { Worker } from '@temporalio/worker'; + +test('handles activity failure', async () => { + const env = await TestWorkflowEnvironment.createTimeSkipping(); + + const worker = await Worker.create({ + connection: env.nativeConnection, + taskQueue: 'test', + workflowsPath: require.resolve('./workflows'), + activities: { + // Activity that always fails + riskyOperation: async () => { + throw ApplicationFailure.nonRetryable('Simulated failure'); + }, + }, + }); + + await worker.runUntil(async () => { + await expect( + env.client.workflow.execute(riskyWorkflow, { + workflowId: 'test-failure', + taskQueue: 'test', + }) + ).rejects.toThrow('Simulated failure'); + }); + + await env.teardown(); +}); +``` + +### Not Testing Replay + +```typescript +import { Worker } from '@temporalio/worker'; + +test('replay compatibility', async () => { + const history = await import('./fixtures/workflow_history.json'); + + // Fails if current code is incompatible with history + await Worker.runReplayHistory({ + workflowsPath: require.resolve('./workflows'), + history, + }); +}); +``` + +## Timers and Sleep + +### Using JavaScript setTimeout + +```typescript +// BAD - setTimeout is not durable +export async function delayedWorkflow(): Promise { + await new Promise(resolve => setTimeout(resolve, 60000)); // Not durable! + await activities.doWork(); +} + +// GOOD - Use workflow sleep +import { sleep } from '@temporalio/workflow'; + +export async function delayedWorkflow(): Promise { + await sleep('1 minute'); // Durable, survives restarts + await activities.doWork(); +} +``` diff --git a/references/typescript/observability.md b/references/typescript/observability.md new file mode 100644 index 0000000..74e24bc --- /dev/null +++ b/references/typescript/observability.md @@ -0,0 +1,231 @@ +# TypeScript SDK Observability + +## Overview + +The TypeScript SDK provides replay-aware logging, metrics, and OpenTelemetry integration for production observability. + +## Replay-Aware Logging + +Temporal's logger automatically suppresses duplicate messages during replay, preventing log spam when workflows recover state. + +### Workflow Logging + +```typescript +import { log } from '@temporalio/workflow'; + +export async function orderWorkflow(orderId: string): Promise { + log.info('Processing order', { orderId }); + + const result = await processPayment(orderId); + log.debug('Payment processed', { orderId, result }); + + return result; +} +``` + +**Log levels**: `log.debug()`, `log.info()`, `log.warn()`, `log.error()` + +### Activity Logging + +```typescript +import * as activity from '@temporalio/activity'; + +export async function processPayment(orderId: string): Promise { + const context = activity.Context.current(); + context.log.info('Processing payment', { orderId }); + + // Activity logs don't need replay suppression + // since completed activities aren't re-executed + return 'payment-id-123'; +} +``` + +## Customizing the Logger + +### Basic Configuration + +```typescript +import { DefaultLogger, Runtime } from '@temporalio/worker'; + +const logger = new DefaultLogger('DEBUG', ({ level, message }) => { + console.log(`Custom logger: ${level} - ${message}`); +}); +Runtime.install({ logger }); +``` + +### Winston Integration + +```typescript +import winston from 'winston'; +import { DefaultLogger, Runtime } from '@temporalio/worker'; + +const winstonLogger = winston.createLogger({ + level: 'debug', + format: winston.format.json(), + transports: [ + new winston.transports.File({ filename: 'temporal.log' }) + ], +}); + +const logger = new DefaultLogger('DEBUG', (entry) => { + winstonLogger.log({ + label: entry.meta?.activityId ? 'activity' : entry.meta?.workflowId ? 'workflow' : 'worker', + level: entry.level.toLowerCase(), + message: entry.message, + timestamp: Number(entry.timestampNanos / 1_000_000n), + ...entry.meta, + }); +}); + +Runtime.install({ logger }); +``` + +## OpenTelemetry Integration + +The `@temporalio/interceptors-opentelemetry` package provides tracing for workflows and activities. + +### Setup + +```typescript +// instrumentation.ts - require before other imports +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; +import { Resource } from '@opentelemetry/resources'; +import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; + +export const resource = new Resource({ + [ATTR_SERVICE_NAME]: 'my-temporal-service', +}); + +// Use OTLP exporter for production +export const traceExporter = new OTLPTraceExporter({ + url: 'http://127.0.0.1:4317', + timeoutMillis: 1000, +}); + +export const otelSdk = new NodeSDK({ + resource, + traceExporter, +}); + +otelSdk.start(); +``` + +### Worker Configuration + +```typescript +import { Worker } from '@temporalio/worker'; +import { + OpenTelemetryActivityInboundInterceptor, + OpenTelemetryActivityOutboundInterceptor, + makeWorkflowExporter, +} from '@temporalio/interceptors-opentelemetry/lib/worker'; +import { resource, traceExporter } from './instrumentation'; +import * as activities from './activities'; + +const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + activities, + taskQueue: 'my-queue', + + // OpenTelemetry sinks and interceptors + sinks: { + exporter: makeWorkflowExporter(traceExporter, resource), + }, + interceptors: { + workflowModules: [require.resolve('./workflows')], + activity: [ + (ctx) => ({ + inbound: new OpenTelemetryActivityInboundInterceptor(ctx), + outbound: new OpenTelemetryActivityOutboundInterceptor(ctx), + }), + ], + }, +}); +``` + +## Metrics + +### Prometheus Metrics + +```typescript +import { Runtime } from '@temporalio/worker'; + +Runtime.install({ + telemetryOptions: { + metrics: { + prometheus: { + bindAddress: '127.0.0.1:9091', + }, + }, + }, +}); +``` + +### OTLP Metrics + +```typescript +Runtime.install({ + telemetryOptions: { + metrics: { + otel: { + url: 'http://127.0.0.1:4317', + metricsExportInterval: '1s', + }, + }, + }, +}); +``` + +## Debugging with Event History + +### Viewing Event History + +Use the Temporal CLI or Web UI to inspect workflow execution history: + +```bash +# CLI +temporal workflow show --workflow-id my-workflow + +# Get history as JSON +temporal workflow show --workflow-id my-workflow --output json +``` + +### Key Events to Look For + +| Event | Indicates | +|-------|-----------| +| `ActivityTaskScheduled` | Activity was requested | +| `ActivityTaskStarted` | Worker started executing activity | +| `ActivityTaskCompleted` | Activity completed successfully | +| `ActivityTaskFailed` | Activity threw an error | +| `ActivityTaskTimedOut` | Activity exceeded timeout | +| `TimerStarted` | `sleep()` called | +| `TimerFired` | Sleep completed | +| `WorkflowTaskFailed` | Non-deterministic error or workflow bug | + +### Debugging Non-Determinism + +If you see `WorkflowTaskFailed` with a non-determinism error: + +1. Export the history: `temporal workflow show -w -o json > history.json` +2. Run replay test to reproduce: + +```typescript +import { Worker } from '@temporalio/worker'; + +await Worker.runReplayHistory( + { workflowsPath: require.resolve('./workflows') }, + history +); +``` + +## Best Practices + +1. Use `log` from `@temporalio/workflow` - never `console.log` in workflows +2. Include correlation IDs (orderId, customerId) in log messages +3. Configure Winston or similar for production log aggregation +4. Enable OpenTelemetry for distributed tracing across services +5. Monitor Prometheus metrics for worker health +6. Use Event History for debugging workflow issues diff --git a/references/typescript/patterns.md b/references/typescript/patterns.md new file mode 100644 index 0000000..c6a606d --- /dev/null +++ b/references/typescript/patterns.md @@ -0,0 +1,422 @@ +# TypeScript SDK Patterns + +## Signals + +**WHY**: Signals allow external clients or other workflows to send data to a running workflow asynchronously. Unlike queries (read-only), signals can mutate workflow state. + +**WHEN to use**: +- Sending events to a running workflow (e.g., approval, cancellation request) +- Adding items to a workflow's queue or collection +- Notifying a workflow about external state changes +- Implementing human-in-the-loop workflows + +```typescript +import { defineSignal, setHandler, condition } from '@temporalio/workflow'; + +const approveSignal = defineSignal<[boolean]>('approve'); +const addItemSignal = defineSignal<[string]>('addItem'); + +export async function orderWorkflow(): Promise { + let approved = false; + const items: string[] = []; + + setHandler(approveSignal, (value) => { + approved = value; + }); + + setHandler(addItemSignal, (item) => { + items.push(item); + }); + + await condition(() => approved); + return `Processed ${items.length} items`; +} +``` + +## Queries + +**WHY**: Queries provide a synchronous, read-only way to inspect workflow state. They execute instantly without modifying workflow state or history. + +**WHEN to use**: +- Exposing workflow progress or status to external systems +- Building dashboards or monitoring UIs +- Debugging workflow state during development +- Implementing "get current state" endpoints + +```typescript +import { defineQuery, setHandler } from '@temporalio/workflow'; + +const statusQuery = defineQuery('status'); +const progressQuery = defineQuery('progress'); + +export async function progressWorkflow(): Promise { + let status = 'running'; + let progress = 0; + + setHandler(statusQuery, () => status); + setHandler(progressQuery, () => progress); + + for (let i = 0; i < 100; i++) { + progress = i; + await doWork(); + } + status = 'completed'; +} +``` + +## Updates + +**WHY**: Updates combine the state mutation capability of signals with the synchronous response of queries. The caller waits for the update handler to complete and receives a return value. + +**WHEN to use**: +- Operations that modify state AND need to return a result (e.g., "add item and return new count") +- Validation before accepting a change (use validators to reject invalid updates) +- Synchronous request-response patterns within a workflow +- Replacing signal+query combos where you signal then immediately query + +### Defining Update Handlers + +```typescript +import { defineUpdate, setHandler, condition } from '@temporalio/workflow'; + +// Define the update - specify return type and argument types +export const addItemUpdate = defineUpdate('addItem'); +export const addItemValidatedUpdate = defineUpdate('addItemValidated'); + +export async function orderWorkflow(): Promise { + const items: string[] = []; + let completed = false; + + // Simple update handler - returns new item count + setHandler(addItemUpdate, (item: string) => { + items.push(item); + return items.length; + }); + + // Update handler with validator - rejects invalid input before execution + setHandler( + addItemValidatedUpdate, + (item: string) => { + items.push(item); + return items.length; + }, + { + validator: (item: string) => { + if (!item) throw new Error('Item cannot be empty'); + if (items.length >= 100) throw new Error('Order is full'); + }, + } + ); + + await condition(() => completed); + return `Order with ${items.length} items completed`; +} +``` + +### Calling Updates from Client + +```typescript +import { Client } from '@temporalio/client'; +import { addItemUpdate } from './workflows'; + +const client = new Client(); +const handle = client.workflow.getHandle('order-123'); + +// Execute update and wait for result +const count = await handle.executeUpdate(addItemUpdate, { args: ['new-item'] }); +console.log(`Order now has ${count} items`); + +// Start update and get handle for later result retrieval +const updateHandle = await handle.startUpdate(addItemUpdate, { + args: ['another-item'], + waitForStage: 'ACCEPTED', +}); +const result = await updateHandle.result(); +``` + +## Signal-with-Start + +**WHY**: Atomically starts a workflow and sends it a signal in a single operation. Avoids race conditions where the workflow might complete before receiving the signal. + +**WHEN to use**: +- Starting a workflow and immediately sending it data +- Idempotent "create or update" patterns +- Ensuring a signal is delivered even if the workflow needs to be started first + +```typescript +import { Client } from '@temporalio/client'; +import { orderSignal } from './workflows'; + +const client = new Client(); + +const handle = await client.workflow.signalWithStart('orderWorkflow', { + workflowId: `order-${customerId}`, + taskQueue: 'orders', + args: [customerId], + signal: orderSignal, + signalArgs: [{ item: 'product-123', quantity: 2 }], +}); +``` + +## Child Workflows + +**WHY**: Child workflows decompose complex workflows into smaller, reusable units. Each child has its own history, preventing history bloat. + +**WHEN to use**: +- Breaking down large workflows to prevent history growth +- Reusing workflow logic across multiple parent workflows +- Isolating failures - a child can fail without failing the parent + +```typescript +import { executeChild } from '@temporalio/workflow'; + +export async function parentWorkflow(orders: Order[]): Promise { + const results: string[] = []; + + for (const order of orders) { + const result = await executeChild(processOrderWorkflow, { + args: [order], + workflowId: `order-${order.id}`, + }); + results.push(result); + } + + return results; +} +``` + +### Child Workflow Options + +```typescript +import { executeChild, ParentClosePolicy, ChildWorkflowCancellationType } from '@temporalio/workflow'; + +const result = await executeChild(childWorkflow, { + args: [input], + workflowId: `child-${workflowInfo().workflowId}`, + + // ParentClosePolicy - what happens to child when parent closes + // TERMINATE (default), ABANDON, REQUEST_CANCEL + parentClosePolicy: ParentClosePolicy.TERMINATE, + + // ChildWorkflowCancellationType - how cancellation is handled + // WAIT_CANCELLATION_COMPLETED (default), WAIT_CANCELLATION_REQUESTED, TRY_CANCEL, ABANDON + cancellationType: ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED, +}); +``` + +## Parallel Execution + +**WHY**: Running multiple operations concurrently improves workflow performance when operations are independent. + +**WHEN to use**: +- Processing multiple independent items +- Calling multiple APIs that don't depend on each other +- Fan-out/fan-in patterns + +```typescript +export async function parallelWorkflow(items: string[]): Promise { + return await Promise.all( + items.map((item) => processItem(item)) + ); +} +``` + +## Continue-as-New + +**WHY**: Prevents unbounded history growth by completing the current workflow and starting a new run with the same workflow ID. + +**WHEN to use**: +- Long-running workflows that would accumulate too much history +- Entity/subscription workflows that run indefinitely +- Batch processing with large numbers of iterations + +```typescript +import { continueAsNew, workflowInfo } from '@temporalio/workflow'; + +export async function longRunningWorkflow(state: State): Promise { + while (true) { + state = await processNextBatch(state); + + if (state.isComplete) { + return 'done'; + } + + const info = workflowInfo(); + if (info.continueAsNewSuggested || info.historyLength > 10000) { + await continueAsNew(state); + } + } +} +``` + +## Cancellation Scopes + +**WHY**: Control how cancellation propagates to activities and child workflows. Essential for cleanup logic and timeout behavior. + +**WHEN to use**: +- Ensuring cleanup activities run even when workflow is cancelled +- Implementing timeouts for activity groups +- Manual cancellation of specific operations + +```typescript +import { CancellationScope, sleep } from '@temporalio/workflow'; + +export async function scopedWorkflow(): Promise { + // Non-cancellable scope - runs even if workflow cancelled + await CancellationScope.nonCancellable(async () => { + await cleanupActivity(); + }); + + // Timeout scope + await CancellationScope.withTimeout('5 minutes', async () => { + await longRunningActivity(); + }); + + // Manual cancellation + const scope = new CancellationScope(); + const promise = scope.run(() => someActivity()); + scope.cancel(); +} +``` + +## Saga Pattern + +**WHY**: Implement distributed transactions by tracking compensation actions. If any step fails, previously completed steps are rolled back in reverse order. + +**WHEN to use**: +- Multi-step business transactions that span multiple services +- Operations where partial completion requires cleanup +- Financial transactions, order processing, booking systems + +```typescript +export async function sagaWorkflow(order: Order): Promise { + const compensations: Array<() => Promise> = []; + + try { + await reserveInventory(order); + compensations.push(() => releaseInventory(order)); + + await chargePayment(order); + compensations.push(() => refundPayment(order)); + + await shipOrder(order); + return 'Order completed'; + } catch (err) { + for (const compensate of compensations.reverse()) { + try { + await compensate(); + } catch (compErr) { + console.log('Compensation failed', compErr); + } + } + throw err; + } +} +``` + +## Entity Workflow Pattern + +**WHY**: Model a long-lived entity as a single workflow that handles events over its lifetime. + +**WHEN to use**: +- Modeling stateful entities that exist for extended periods +- Subscription management, user sessions +- Any entity that receives events and must maintain consistent state + +```typescript +import { defineSignal, defineQuery, setHandler, condition, continueAsNew, workflowInfo } from '@temporalio/workflow'; + +const eventSignal = defineSignal<[Event]>('event'); +const stateQuery = defineQuery('state'); + +export async function entityWorkflow(entityId: string, initialState: EntityState): Promise { + let state = initialState; + + setHandler(stateQuery, () => state); + setHandler(eventSignal, (event: Event) => { + state = applyEvent(state, event); + }); + + while (!state.deleted) { + await condition(() => state.deleted || workflowInfo().continueAsNewSuggested); + if (workflowInfo().continueAsNewSuggested && !state.deleted) { + await continueAsNew(entityId, state); + } + } +} +``` + +## Triggers (Promise-like Signals) + +**WHY**: Triggers provide a one-shot promise that resolves when a signal is received. Cleaner than condition() for single-value signals. + +**WHEN to use**: +- Waiting for a single response (approval, completion notification) +- Converting signal-based events into awaitable promises + +```typescript +import { Trigger } from '@temporalio/workflow'; + +export async function triggerWorkflow(): Promise { + const approvalTrigger = new Trigger(); + + setHandler(approveSignal, (approved) => { + approvalTrigger.resolve(approved); + }); + + const approved = await approvalTrigger; + return approved ? 'Approved' : 'Rejected'; +} +``` + +## Timers + +**WHY**: Durable timers that survive worker restarts. Use sleep() for delays instead of JavaScript setTimeout. + +**WHEN to use**: +- Implementing delays between steps +- Scheduling future actions +- Timeout patterns (combined with cancellation scopes) + +```typescript +import { sleep, CancellationScope } from '@temporalio/workflow'; + +export async function timerWorkflow(): Promise { + await sleep('1 hour'); + + const timerScope = new CancellationScope(); + const timerPromise = timerScope.run(() => sleep('1 hour')); + + setHandler(cancelSignal, () => { + timerScope.cancel(); + }); + + try { + await timerPromise; + return 'Timer completed'; + } catch { + return 'Timer cancelled'; + } +} +``` + +## uuid4() Utility + +**WHY**: Generate deterministic UUIDs safe to use in workflows. Uses the workflow seeded PRNG, so the same UUID is generated during replay. + +**WHEN to use**: +- Generating unique IDs for child workflows +- Creating idempotency keys +- Any situation requiring unique identifiers in workflow code + +```typescript +import { uuid4 } from '@temporalio/workflow'; + +export async function workflowWithIds(): Promise { + const childWorkflowId = uuid4(); + await executeChild(childWorkflow, { + workflowId: childWorkflowId, + args: [input], + }); +} +``` diff --git a/references/typescript/testing.md b/references/typescript/testing.md new file mode 100644 index 0000000..70e4a0b --- /dev/null +++ b/references/typescript/testing.md @@ -0,0 +1,128 @@ +# TypeScript SDK Testing + +## Overview + +The TypeScript SDK provides `TestWorkflowEnvironment` for testing workflows with time-skipping and activity mocking support. + +## Test Environment Setup + +```typescript +import { TestWorkflowEnvironment } from '@temporalio/testing'; +import { Worker } from '@temporalio/worker'; + +describe('Workflow', () => { + let testEnv: TestWorkflowEnvironment; + + before(async () => { + testEnv = await TestWorkflowEnvironment.createLocal(); + }); + + after(async () => { + await testEnv?.teardown(); + }); + + it('runs workflow', async () => { + const { client, nativeConnection } = testEnv; + + const worker = await Worker.create({ + connection: nativeConnection, + taskQueue: 'test', + workflowsPath: require.resolve('./workflows'), + activities: require('./activities'), + }); + + await worker.runUntil(async () => { + const result = await client.workflow.execute(greetingWorkflow, { + taskQueue: 'test', + workflowId: 'test-workflow', + args: ['World'], + }); + expect(result).toEqual('Hello, World!'); + }); + }); +}); +``` + +## Time Skipping + +```typescript +// Create time-skipping environment +const testEnv = await TestWorkflowEnvironment.createTimeSkipping(); + +// Time automatically advances when workflows wait +await worker.runUntil(async () => { + const result = await client.workflow.execute(longRunningWorkflow, { + taskQueue: 'test', + workflowId: 'test-workflow', + }); + // Even if workflow has 1-hour timer, test completes instantly +}); + +// Manual time advancement +await testEnv.sleep('1 day'); +``` + +## Activity Mocking + +```typescript +const worker = await Worker.create({ + connection: nativeConnection, + taskQueue: 'test', + workflowsPath: require.resolve('./workflows'), + activities: { + // Mock activity implementation + greet: async (name: string) => `Mocked: ${name}`, + }, +}); +``` + +## Replay Testing + +```typescript +import { Worker } from '@temporalio/worker'; + +describe('Replay', () => { + it('replays workflow history', async () => { + const history = await fetchWorkflowHistory('workflow-id'); + + await Worker.runReplayHistory( + { + workflowsPath: require.resolve('./workflows'), + }, + history + ); + }); +}); +``` + +## Testing Signals and Queries + +```typescript +it('handles signals and queries', async () => { + await worker.runUntil(async () => { + const handle = await client.workflow.start(approvalWorkflow, { + taskQueue: 'test', + workflowId: 'approval-test', + }); + + // Query current state + const status = await handle.query('getStatus'); + expect(status).toEqual('pending'); + + // Send signal + await handle.signal('approve'); + + // Wait for completion + const result = await handle.result(); + expect(result).toEqual('Approved!'); + }); +}); +``` + +## Best Practices + +1. Use time-skipping for workflows with timers +2. Mock external dependencies in activities +3. Test replay compatibility when changing workflow code +4. Use unique workflow IDs per test +5. Clean up test environment after tests diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md new file mode 100644 index 0000000..961f910 --- /dev/null +++ b/references/typescript/typescript.md @@ -0,0 +1,121 @@ +# Temporal TypeScript SDK Reference + +## Overview + +The Temporal TypeScript SDK provides a modern async/await approach to building durable workflows. Workflows run in an isolated V8 sandbox for automatic determinism protection. + +**CRITICAL**: All `@temporalio/*` packages must have the same version number. + +## How Temporal Works: History Replay + +Understanding how Temporal achieves durable execution is essential for writing correct workflows. + +### The Replay Mechanism + +When a Worker executes workflow code, it creates **Commands** (requests for operations like starting an Activity or Timer) and sends them to the Temporal Cluster. The Cluster maintains an **Event History** - a durable log of everything that happened during the workflow execution. + +**Key insight**: During replay, the Worker re-executes your workflow code but uses the Event History to restore state instead of re-executing Activities. When it encounters an Activity call that has a corresponding `ActivityTaskCompleted` event in history, it returns the stored result instead of scheduling a new execution. + +This is why **determinism matters**: The Worker validates that Commands generated during replay match the Events in history. A mismatch causes a non-deterministic error because the Worker cannot reliably restore state. + +### Commands and Events + +| Workflow Code | Command Generated | Resulting Event | +|--------------|-------------------|-----------------| +| Activity call | `ScheduleActivityTask` | `ActivityTaskScheduled` | +| `sleep()` | `StartTimer` | `TimerStarted` | +| Child workflow | `StartChildWorkflowExecution` | `ChildWorkflowExecutionStarted` | +| Return/complete | `CompleteWorkflowExecution` | `WorkflowExecutionCompleted` | + +### When Replay Occurs + +- Worker crashes and recovers +- Worker's cache fills and evicts workflow state +- Workflow continues after long timer +- Testing with replay histories + +## Quick Start + +```typescript +// activities.ts +export async function greet(name: string): Promise { + return `Hello, ${name}!`; +} + +// workflows.ts +import { proxyActivities } from '@temporalio/workflow'; +import type * as activities from './activities'; + +const { greet } = proxyActivities({ + startToCloseTimeout: '1 minute', +}); + +export async function greetingWorkflow(name: string): Promise { + return await greet(name); +} + +// worker.ts +import { Worker } from '@temporalio/worker'; +import * as activities from './activities'; + +async function run() { + const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + activities, + taskQueue: 'greeting-queue', + }); + await worker.run(); +} +``` + +## Key Concepts + +### Workflow Definition +- Async functions exported from workflow file +- Use `proxyActivities()` with type-only imports +- Use `defineSignal()`, `defineQuery()`, `setHandler()` for handlers + +### Activity Definition +- Regular async functions +- Can perform I/O, network calls, etc. +- Use `heartbeat()` for long operations + +### Worker Setup +- Use `Worker.create()` with workflowsPath +- Import activities directly (not via proxy) + +## Determinism Rules + +The TypeScript SDK runs workflows in an isolated V8 sandbox. + +**Automatic replacements:** +- `Math.random()` → deterministic seeded PRNG +- `Date.now()` → workflow start time +- `setTimeout` → deterministic timer + +**Safe to use:** +- `sleep()` from `@temporalio/workflow` +- `condition()` for waiting +- Standard JavaScript operations + +See `determinism.md` for detailed rules. + +## Common Pitfalls + +1. **Importing activities without `type`** - Use `import type * as activities` +2. **Version mismatch** - All @temporalio packages must match +3. **Direct I/O in workflows** - Use activities for external calls +4. **Missing `proxyActivities`** - Required to call activities from workflows +5. **Forgetting to bundle workflows** - Worker needs workflowsPath + +## Additional Resources + +### Reference Files +- **`determinism.md`** - V8 sandbox, bundling, safe operations, WHY determinism matters +- **`error-handling.md`** - ApplicationFailure, retry policies, idempotency keys +- **`testing.md`** - TestWorkflowEnvironment, time-skipping +- **`patterns.md`** - Signals, queries, cancellation scopes +- **`observability.md`** - Replay-aware logging, metrics, OpenTelemetry, debugging +- **`advanced-features.md`** - Sinks, updates, schedules and more +- **`data-handling.md`** - Search attributes, workflow memo, data converters +- **`versioning.md`** - Patching API, workflow type versioning, Worker Versioning diff --git a/references/typescript/versioning.md b/references/typescript/versioning.md new file mode 100644 index 0000000..7ce2a62 --- /dev/null +++ b/references/typescript/versioning.md @@ -0,0 +1,307 @@ +# TypeScript SDK Versioning + +## Overview + +The TypeScript SDK provides multiple approaches to safely change Workflow code while maintaining compatibility with running Workflows: the Patching API, Workflow Type Versioning, and Worker Versioning. + +## Why Versioning Matters + +Temporal provides durable execution through **History Replay**. When a Worker needs to restore Workflow state, it re-executes the Workflow code from the beginning. If you change Workflow code while executions are still running, replay can fail because the new code produces different Commands than the original history. + +Versioning strategies allow you to safely deploy changes without breaking in-progress Workflow Executions. + +## Workflow Versioning with the Patching API + +The Patching API lets you change Workflow Definitions without causing non-deterministic behavior in running Workflows. + +### The patched() Function + +The `patched()` function takes a `patchId` string and returns a boolean: + +```typescript +import { patched } from '@temporalio/workflow'; + +export async function myWorkflow(): Promise { + if (patched('my-change-id')) { + // New code path + await newImplementation(); + } else { + // Old code path (for replay of existing executions) + await oldImplementation(); + } +} +``` + +**How it works:** +- If the Workflow is running for the first time, `patched()` returns `true` and inserts a marker into the Event History +- During replay, if the history contains a marker with the same `patchId`, `patched()` returns `true` +- During replay, if no matching marker exists, `patched()` returns `false` + +### Three-Step Patching Process + +Patching is a three-step process for safely deploying changes: + +#### Step 1: Patch in New Code + +Add the patch alongside the old code: + +```typescript +import { patched } from '@temporalio/workflow'; + +// Original code sent fax notifications +export async function shippingConfirmation(): Promise { + if (patched('changedNotificationType')) { + await sendEmail(); // New code + } else { + await sendFax(); // Old code for replay + } + await sleep('1 day'); +} +``` + +#### Step 2: Deprecate the Patch + +Once all Workflows using the old code have completed, deprecate the patch: + +```typescript +import { deprecatePatch } from '@temporalio/workflow'; + +export async function shippingConfirmation(): Promise { + deprecatePatch('changedNotificationType'); + await sendEmail(); + await sleep('1 day'); +} +``` + +The `deprecatePatch()` function records a marker that does not fail replay when Workflow code does not emit it, allowing a transition period. + +#### Step 3: Remove the Patch + +After all Workflows using `deprecatePatch` have completed, remove it entirely: + +```typescript +export async function shippingConfirmation(): Promise { + await sendEmail(); + await sleep('1 day'); +} +``` + +### Multiple Patches + +A Workflow can have multiple patches for different changes: + +```typescript +export async function shippingConfirmation(): Promise { + if (patched('sendEmail')) { + await sendEmail(); + } else if (patched('sendTextMessage')) { + await sendTextMessage(); + } else if (patched('sendTweet')) { + await sendTweet(); + } else { + await sendFax(); + } +} +``` + +You can use a single `patchId` for multiple changes deployed together. + +### Query Filters for Versioned Workflows + +Use List Filters to find Workflows by version: + +``` +# Find running Workflows with a specific patch +WorkflowType = "shippingConfirmation" AND ExecutionStatus = "Running" AND TemporalChangeVersion="changedNotificationType" + +# Find running Workflows without the patch (started before patching) +WorkflowType = "shippingConfirmation" AND ExecutionStatus = "Running" AND TemporalChangeVersion IS NULL +``` + +## Workflow Type Versioning + +An alternative to patching is creating new Workflow functions for incompatible changes: + +```typescript +// Original Workflow +export async function pizzaWorkflow(order: PizzaOrder): Promise { + // Original implementation +} + +// New version with incompatible changes +export async function pizzaWorkflowV2(order: PizzaOrder): Promise { + // Updated implementation +} +``` + +Register both Workflows with the Worker: + +```typescript +const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + taskQueue: 'pizza-queue', +}); +``` + +Update client code to start new Workflows with the new type: + +```typescript +// Start new executions with V2 +await client.workflow.start(pizzaWorkflowV2, { + workflowId: 'order-123', + taskQueue: 'pizza-queue', + args: [order], +}); +``` + +Use List Filters to check for remaining V1 executions: + +``` +WorkflowType = "pizzaWorkflow" AND ExecutionStatus = "Running" +``` + +After all V1 executions complete, remove the old Workflow function. + +## Worker Versioning + +Worker Versioning allows multiple Worker versions to run simultaneously, routing Workflows to specific versions without code-level patching. + +### Key Concepts + +- **Worker Deployment**: A logical name for your application (e.g., "order-service") +- **Worker Deployment Version**: A specific build of your code (deployment name + Build ID) + +### Configuring Workers for Versioning + +```typescript +const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + taskQueue: 'my-queue', + workerDeploymentOptions: { + useWorkerVersioning: true, + version: { + deploymentName: 'order-service', + buildId: '1.0.0', // Or git hash, build number, etc. + }, + defaultVersioningBehavior: 'PINNED', // Or 'AUTO_UPGRADE' + }, + connection: nativeConnection, +}); +``` + +**Configuration options:** +- `useWorkerVersioning`: Enables Worker Versioning +- `version.deploymentName`: Logical name for your service (consistent across versions) +- `version.buildId`: Unique identifier for this build (git hash, semver, build number) +- `defaultVersioningBehavior`: How Workflows behave when versions change + +### Versioning Behaviors + +#### PINNED Behavior + +Workflows are locked to the Worker version they started on: + +```typescript +workerDeploymentOptions: { + useWorkerVersioning: true, + version: { buildId: '1.0', deploymentName: 'order-service' }, + defaultVersioningBehavior: 'PINNED', +} +``` + +**Characteristics:** +- Workflows run only on their assigned version +- No patching required in Workflow code +- Cannot use other versioning APIs +- Ideal for short-running Workflows where consistency matters + +**Use PINNED when:** +- You want to eliminate version compatibility complexity +- Workflows are short-running +- Stability is more important than getting latest updates + +#### AUTO_UPGRADE Behavior + +Workflows can move to newer Worker versions: + +```typescript +workerDeploymentOptions: { + useWorkerVersioning: true, + version: { buildId: '1.0', deploymentName: 'order-service' }, + defaultVersioningBehavior: 'AUTO_UPGRADE', +} +``` + +**Characteristics:** +- Workflows can be rerouted to new versions +- Once moved to a newer version, cannot return to older ones +- May require patching to handle version transitions +- Ideal for long-running Workflows that need bug fixes + +**Use AUTO_UPGRADE when:** +- Workflows are long-running (weeks or months) +- You want Workflows to benefit from bug fixes +- Migrating from rolling deployments + +### Deployment Strategies + +#### Blue-Green Deployments + +Maintain two environments and switch traffic between them: + +1. Deploy new version to idle environment +2. Run validation tests +3. Switch traffic to new environment +4. Keep old environment for instant rollback + +#### Rainbow Deployments + +Multiple Worker versions run simultaneously: + +```typescript +// Version 1.0 Workers +const worker1 = await Worker.create({ + workerDeploymentOptions: { + useWorkerVersioning: true, + version: { buildId: '1.0', deploymentName: 'order-service' }, + defaultVersioningBehavior: 'PINNED', + }, + // ... +}); + +// Version 2.0 Workers (deployed alongside 1.0) +const worker2 = await Worker.create({ + workerDeploymentOptions: { + useWorkerVersioning: true, + version: { buildId: '2.0', deploymentName: 'order-service' }, + defaultVersioningBehavior: 'PINNED', + }, + // ... +}); +``` + +**Benefits:** +- Existing PINNED Workflows complete on their original version +- New Workflows use the latest version +- Add new versions without replacing existing ones +- Supports gradual traffic ramping + +## Choosing a Versioning Strategy + +| Strategy | Best For | Trade-offs | +|----------|----------|------------| +| Patching API | Incremental changes to long-running Workflows | Requires maintaining patch branches in code | +| Workflow Type Versioning | Major incompatible changes | Requires code duplication and client updates | +| Worker Versioning (PINNED) | Short-running Workflows, new applications | Requires infrastructure to run multiple versions | +| Worker Versioning (AUTO_UPGRADE) | Long-running Workflows, migrations | May require patching for safe transitions | + +## Best Practices + +1. Use descriptive `patchId` names that explain the change +2. Follow the three-step patching process completely before removing patches +3. Use List Filters to verify no running Workflows before removing version support +4. Keep Worker Deployment names consistent across all versions +5. Use unique, traceable Build IDs (git hashes, semver, timestamps) +6. Choose PINNED for new applications with short-running Workflows +7. Choose AUTO_UPGRADE when migrating from rolling deployments or for long-running Workflows +8. Test version transitions with replay tests before deploying From 24a3eaac926cbfd182fbbf1a96235d9a712d2fe3 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 23 Feb 2026 15:12:42 -0500 Subject: [PATCH 02/50] Revert "Remove TypeScript hints" This reverts commit 82bf9ad5de55018da11a8735d9570516324c4dec. --- SKILL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SKILL.md b/SKILL.md index 92b6f9a..19c6693 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,6 +1,6 @@ --- name: temporal-developer -description: This skill should be used when the user asks to "create a Temporal workflow", "write a Temporal activity", "debug stuck workflow", "fix non-determinism error", "Temporal Python", "workflow replay", "activity timeout", "signal workflow", "query workflow", "worker not starting", "activity keeps retrying", "Temporal heartbeat", "continue-as-new", "child workflow", "saga pattern", "workflow versioning", "durable execution", "reliable distributed systems", or mentions Temporal SDK development. +description: This skill should be used when the user asks to "create a Temporal workflow", "write a Temporal activity", "debug stuck workflow", "fix non-determinism error", "Temporal Python", "Temporal TypeScript", "workflow replay", "activity timeout", "signal workflow", "query workflow", "worker not starting", "activity keeps retrying", "Temporal heartbeat", "continue-as-new", "child workflow", "saga pattern", "workflow versioning", "durable execution", "reliable distributed systems", or mentions Temporal SDK development. version: 1.0.0 --- @@ -8,7 +8,7 @@ version: 1.0.0 ## Overview -Temporal is a durable execution platform that makes workflows survive failures automatically. This skill provides guidance for building Temporal applications in Python. +Temporal is a durable execution platform that makes workflows survive failures automatically. This skill provides guidance for building Temporal applications in Python and TypeScript. ## Core Architecture @@ -91,6 +91,7 @@ Once you've downloaded the file, extract the downloaded archive and add the temp 1. First, read the getting started guide for the language you are working in: - Python -> read `references/python/python.md` + - TypeScript -> read `references/typescript/typescript.md` 2. Second, read appropriate `core` and language-specific references for the task at hand. From 225b628d8a5a6502bb792d26442e04c24d8b402a Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 25 Feb 2026 15:01:00 -0500 Subject: [PATCH 03/50] work through typescript determinism --- SKILL.md | 2 +- references/core/determinism.md | 1 + .../typescript/determinism-protection.md | 53 +++++++++ references/typescript/determinism.md | 103 ++---------------- 4 files changed, 66 insertions(+), 93 deletions(-) create mode 100644 references/typescript/determinism-protection.md diff --git a/SKILL.md b/SKILL.md index 19c6693..bbe339e 100644 --- a/SKILL.md +++ b/SKILL.md @@ -109,7 +109,7 @@ Once you've downloaded the file, extract the downloaded archive and add the temp - **`references/core/interactive-workflows.md`** - Testing signals, updates, queries - **`references/core/dev-management.md`** - Dev cycle & management of server and workers - **`references/core/ai-patterns.md`** - AI/LLM pattern concepts - + Langauge-specific info at `references/{your_language}/determinism.md` + + Langauge-specific info at `references/{your_language}/determinism.md`, if available. Currently Python only. ## Additional Topics - **`references/{your_langauge}/observability.md`** - See for language-specific implementation guidance on observability in Temporal diff --git a/references/core/determinism.md b/references/core/determinism.md index c6e3a04..70e74b8 100644 --- a/references/core/determinism.md +++ b/references/core/determinism.md @@ -79,6 +79,7 @@ For a few simple cases, like timestamps, random values, UUIDs, etc. the Temporal Each Temporal SDK language provides a protection mechanism to make it easier to catch non-determinism errors earlier in development: - Python: The Python SDK runs workflows in a sandbox that intercepts and aborts non-deterministic calls at runtime. +- TypeScript: The TypeScript SDK runs workflows in an isolated V8 sandbox, intercepting many common sources of non-determinism and replacing them automatically with deterministic variants. ## Detecting Non-Determinism diff --git a/references/typescript/determinism-protection.md b/references/typescript/determinism-protection.md new file mode 100644 index 0000000..ac812f8 --- /dev/null +++ b/references/typescript/determinism-protection.md @@ -0,0 +1,53 @@ +# TypeScript Workflow V8 Sandboxing + +## Overview + +The TypeScript SDK runs workflows in an V8 sandbox that provides automatic protection against non-deterministic operations, and replaces common non-deterministic function calls with deterministic variants. This is unique to the TypeScript SDK. + +## Import Blocking + +The sandbox blocks imports of `fs`, `https` modules, and any Node/DOM APIs. Otherwise, workflow code can import any package as long as it does not reference Node.js or DOM APIs. + +**Note**: If you must use a library that references a Node.js or DOM API and you are certain that those APIs are not used at runtime, add that module to the `ignoreModules` list: + +```ts +const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + activities: require('./activities'), + taskQueue: 'my-task-queue', + bundlerOptions: { + // These modules may be imported (directly or transitively), + // but will be excluded from the Workflow bundle. + ignoreModules: ['fs', 'http', 'crypto'], + }, +}); +``` + + +Use this with *extreme caution*. + + +## Function Replacement + +Functions like `Math.random()`, `Date`, and `setTimeout()` are replaced by deterministic versions. + +Date-related functions will *deterministically* return the date at the *start of the workflow*, and will only progress in time when a semantic time operation occurs in Temporal, like a durable sleep. For example: + +```ts +import { sleep } from '@temporalio/workflow'; + +// this prints the *exact* same timestamp repeatedly +for (let x = 0; x < 10; ++x) { + console.log(Date.now()); +} + +// this prints timestamps increasing roughly 1s each iteration +for (let x = 0; x < 10; ++x) { + await sleep('1 second'); + console.log(Date.now()); +} +``` + +This means that if you want a workflow to truly be able to check current real-world physical time, you should retrieve the time in an activity. You should consider which is semantically appropriate for your situation. + +Additionally, `FinalizationRegistry` and `WeakRef` are removed because v8's garbage collector is not deterministic. diff --git a/references/typescript/determinism.md b/references/typescript/determinism.md index e7db427..38b7468 100644 --- a/references/typescript/determinism.md +++ b/references/typescript/determinism.md @@ -6,26 +6,17 @@ The TypeScript SDK runs workflows in an isolated V8 sandbox that automatically p ## Why Determinism Matters -Temporal provides durable execution through **History Replay**. When a Worker needs to restore workflow state (after a crash, cache eviction, or to continue after a long timer), it re-executes the workflow code from the beginning. +Temporal provides durable execution through **History Replay**. When a Worker needs to restore workflow state (after a crash, cache eviction, or to continue after a long timer), it re-executes the workflow code from the beginning, which requires the workflow code to be **deterministic**. -**The Critical Rule**: A Workflow is deterministic if every execution of its code produces the same Commands, in the same sequence, given the same input. +## Temporal's V8 Sandbox -During replay, the Worker: -1. Re-executes your workflow code -2. Compares generated Commands to Events in the history -3. Uses stored results from history instead of re-executing Activities +The Temporal TypeScript SDK executes all workflow code in sandbox, which (among other things), replaces common non-deterministic functions with deterministic variants. As an example, consider the code below: -If the Commands don't match the history, the Worker cannot accurately restore state, causing a **non-deterministic error**. - -### Example of Non-Determinism - -```typescript -// WRONG - Non-deterministic! +```ts export async function badWorkflow(): Promise { await importData(); - // Random value changes on each execution - if (Math.random() > 0.5) { // Would be a problem without sandbox + if (Math.random() > 0.5) { await sleep('30 minutes'); } @@ -33,36 +24,9 @@ export async function badWorkflow(): Promise { } ``` -Without the sandbox, if the random number was 0.8 on first run (timer started) but 0.3 on replay (no timer), the Worker would see a `StartTimer` command that doesn't match history, causing a non-deterministic error. - -**Good news**: The TypeScript sandbox automatically makes `Math.random()` deterministic, so this specific code actually works. But the concept is important for understanding WHY the sandbox exists. +The Temporal workflow sandbox will use the same random seed when replaying a workflow, so the above code will **deterministically** generate pseudo-random numbers. -## Automatic Replacements - -The sandbox replaces non-deterministic APIs with deterministic versions: - -| Original | Replacement | -|----------|-------------| -| `Math.random()` | Seeded PRNG per workflow | -| `Date.now()` | Workflow task start time | -| `Date` constructor | Deterministic time | -| `setTimeout` | Workflow timer | - -## Safe Operations - -```typescript -import { sleep } from '@temporalio/workflow'; - -// These are all safe in workflows: -Math.random(); // Deterministic -Date.now(); // Deterministic -new Date(); // Deterministic -await sleep('1 hour'); // Durable timer - -// Object iteration is deterministic in JavaScript -for (const key in obj) { } -Object.keys(obj).forEach(k => { }); -``` +See `references/typescript/determinism-protection.md` for more information about the sandbox. ## Forbidden Operations @@ -73,61 +37,16 @@ fetch('https://...'); // Network I/O console.log(); // Side effects (use workflow.log) ``` -## Type-Only Activity Imports +Most non-determinism and side effects, such as the above, should be wrapped in Activities. -```typescript -// CORRECT - type-only import -import type * as activities from './activities'; - -const { myActivity } = proxyActivities({ - startToCloseTimeout: '5 minutes', -}); +## Testing Replay Compatibility -// WRONG - actual import brings in implementation -import * as activities from './activities'; -``` - -## Workflow Bundling - -Workflows are bundled by the worker using Webpack. The bundled code runs in isolation. - -```typescript -const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), // Gets bundled - activities, // Not bundled, runs in Node.js - taskQueue: 'my-queue', -}); -``` - -## Patching for Versioning - -Use `patched()` to safely change workflow code while maintaining compatibility with running workflows: - -```typescript -import { patched, deprecatePatch } from '@temporalio/workflow'; - -export async function myWorkflow(): Promise { - if (patched('my-change')) { - // New code path - return await newImplementation(); - } else { - // Old code path (for replay) - return await oldImplementation(); - } -} - -// Later, after all old workflows complete: -export async function myWorkflow(): Promise { - deprecatePatch('my-change'); - return await newImplementation(); -} -``` +Use the `Replayer` class to verify your code changes are compatible with existing histories. See the Workflow Replay Testing section of `references/python/testing.md`. ## Best Practices 1. Use type-only imports for activities in workflow files 2. Match all @temporalio package versions -3. Use `sleep()` from workflow package, never `setTimeout` directly +3. Use `sleep()` from workflow package, not `setTimeout` directly 4. Keep workflows focused on orchestration 5. Test with replay to verify determinism -6. Use `patched()` when changing workflow logic for running workflows From 47929466f38223da1b36c79105dc98ce302a8cec Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 25 Feb 2026 15:09:51 -0500 Subject: [PATCH 04/50] improve cross-references --- references/typescript/determinism.md | 2 +- references/typescript/typescript.md | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/references/typescript/determinism.md b/references/typescript/determinism.md index 38b7468..4778fd0 100644 --- a/references/typescript/determinism.md +++ b/references/typescript/determinism.md @@ -41,7 +41,7 @@ Most non-determinism and side effects, such as the above, should be wrapped in A ## Testing Replay Compatibility -Use the `Replayer` class to verify your code changes are compatible with existing histories. See the Workflow Replay Testing section of `references/python/testing.md`. +Use the `Replayer` class to verify your code changes are compatible with existing histories. See the Workflow Replay Testing section of `references/typescript/testing.md`. ## Best Practices diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 961f910..47b2551 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -108,14 +108,20 @@ See `determinism.md` for detailed rules. 4. **Missing `proxyActivities`** - Required to call activities from workflows 5. **Forgetting to bundle workflows** - Worker needs workflowsPath +## Writing Tests + +See `references/typescript/testing.md` for info on writing tests. + ## Additional Resources ### Reference Files -- **`determinism.md`** - V8 sandbox, bundling, safe operations, WHY determinism matters -- **`error-handling.md`** - ApplicationFailure, retry policies, idempotency keys -- **`testing.md`** - TestWorkflowEnvironment, time-skipping -- **`patterns.md`** - Signals, queries, cancellation scopes -- **`observability.md`** - Replay-aware logging, metrics, OpenTelemetry, debugging -- **`advanced-features.md`** - Sinks, updates, schedules and more -- **`data-handling.md`** - Search attributes, workflow memo, data converters -- **`versioning.md`** - Patching API, workflow type versioning, Worker Versioning +- **`references/python/patterns.md`** - Signals, queries, child workflows, saga pattern, etc. +- **`references/python/determinism.md`** - Essentials of determinism in TypeScript +- **`references/python/gotchas.md`** - TypeScript-specific mistakes and anti-patterns +- **`references/python/error-handling.md`** - ApplicationError, retry policies, non-retryable errors, idempotency +- **`references/python/observability.md`** - Logging, metrics, tracing, Search Attributes +- **`references/python/testing.md`** - TestWorkflowEnvironment, time-skipping, activity mocking +- **`references/python/advanced-features.md`** - Schedules, worker tuning, and more +- **`references/python/data-handling.md`** - Data converters, payload encryption, etc. +- **`references/python/versioning.md`** - Patching API, workflow type versioning, Worker Versioning +- **`references/python/determinism-protection.md`** - V8 sandbox and bundling From e8a3bdd300cf7ef8de278580e7faa4fa2c888710 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 25 Feb 2026 16:02:18 -0500 Subject: [PATCH 05/50] align with python patterns. --- references/typescript/patterns.md | 323 +++++++++++++++--------------- 1 file changed, 156 insertions(+), 167 deletions(-) diff --git a/references/typescript/patterns.md b/references/typescript/patterns.md index c6a606d..760f112 100644 --- a/references/typescript/patterns.md +++ b/references/typescript/patterns.md @@ -2,14 +2,6 @@ ## Signals -**WHY**: Signals allow external clients or other workflows to send data to a running workflow asynchronously. Unlike queries (read-only), signals can mutate workflow state. - -**WHEN to use**: -- Sending events to a running workflow (e.g., approval, cancellation request) -- Adding items to a workflow's queue or collection -- Notifying a workflow about external state changes -- Implementing human-in-the-loop workflows - ```typescript import { defineSignal, setHandler, condition } from '@temporalio/workflow'; @@ -33,15 +25,32 @@ export async function orderWorkflow(): Promise { } ``` -## Queries +## Dynamic Signal Handlers -**WHY**: Queries provide a synchronous, read-only way to inspect workflow state. They execute instantly without modifying workflow state or history. +For handling signals with names not known at compile time: -**WHEN to use**: -- Exposing workflow progress or status to external systems -- Building dashboards or monitoring UIs -- Debugging workflow state during development -- Implementing "get current state" endpoints +```typescript +import { setHandler, condition } from '@temporalio/workflow'; + +export async function dynamicSignalWorkflow(): Promise> { + const signals: Record = {}; + + setHandler( + (signalName: string) => true, // Accept all signal names + (signalName: string, ...args: unknown[]) => { + if (!signals[signalName]) { + signals[signalName] = []; + } + signals[signalName].push(args); + } + ); + + await condition(() => signals['done'] !== undefined); + return signals; +} +``` + +## Queries ```typescript import { defineQuery, setHandler } from '@temporalio/workflow'; @@ -64,17 +73,31 @@ export async function progressWorkflow(): Promise { } ``` -## Updates +## Dynamic Query Handlers -**WHY**: Updates combine the state mutation capability of signals with the synchronous response of queries. The caller waits for the update handler to complete and receives a return value. +For handling queries with names not known at compile time: -**WHEN to use**: -- Operations that modify state AND need to return a result (e.g., "add item and return new count") -- Validation before accepting a change (use validators to reject invalid updates) -- Synchronous request-response patterns within a workflow -- Replacing signal+query combos where you signal then immediately query +```typescript +import { setHandler } from '@temporalio/workflow'; + +export async function dynamicQueryWorkflow(): Promise { + const state: Record = { + status: 'running', + progress: 0, + }; + + setHandler( + (queryName: string) => true, // Accept all query names + (queryName: string) => { + return state[queryName]; + } + ); + + // ... workflow logic +} +``` -### Defining Update Handlers +## Updates ```typescript import { defineUpdate, setHandler, condition } from '@temporalio/workflow'; @@ -113,60 +136,8 @@ export async function orderWorkflow(): Promise { } ``` -### Calling Updates from Client - -```typescript -import { Client } from '@temporalio/client'; -import { addItemUpdate } from './workflows'; - -const client = new Client(); -const handle = client.workflow.getHandle('order-123'); - -// Execute update and wait for result -const count = await handle.executeUpdate(addItemUpdate, { args: ['new-item'] }); -console.log(`Order now has ${count} items`); - -// Start update and get handle for later result retrieval -const updateHandle = await handle.startUpdate(addItemUpdate, { - args: ['another-item'], - waitForStage: 'ACCEPTED', -}); -const result = await updateHandle.result(); -``` - -## Signal-with-Start - -**WHY**: Atomically starts a workflow and sends it a signal in a single operation. Avoids race conditions where the workflow might complete before receiving the signal. - -**WHEN to use**: -- Starting a workflow and immediately sending it data -- Idempotent "create or update" patterns -- Ensuring a signal is delivered even if the workflow needs to be started first - -```typescript -import { Client } from '@temporalio/client'; -import { orderSignal } from './workflows'; - -const client = new Client(); - -const handle = await client.workflow.signalWithStart('orderWorkflow', { - workflowId: `order-${customerId}`, - taskQueue: 'orders', - args: [customerId], - signal: orderSignal, - signalArgs: [{ item: 'product-123', quantity: 2 }], -}); -``` - ## Child Workflows -**WHY**: Child workflows decompose complex workflows into smaller, reusable units. Each child has its own history, preventing history bloat. - -**WHEN to use**: -- Breaking down large workflows to prevent history growth -- Reusing workflow logic across multiple parent workflows -- Isolating failures - a child can fail without failing the parent - ```typescript import { executeChild } from '@temporalio/workflow'; @@ -204,14 +175,24 @@ const result = await executeChild(childWorkflow, { }); ``` -## Parallel Execution +## Handles to External Workflows -**WHY**: Running multiple operations concurrently improves workflow performance when operations are independent. +```typescript +import { getExternalWorkflowHandle } from '@temporalio/workflow'; +import { mySignal } from './other-workflows'; -**WHEN to use**: -- Processing multiple independent items -- Calling multiple APIs that don't depend on each other -- Fan-out/fan-in patterns +export async function coordinatorWorkflow(targetWorkflowId: string): Promise { + const handle = getExternalWorkflowHandle(targetWorkflowId); + + // Signal the external workflow + await handle.signal(mySignal, { data: 'payload' }); + + // Or cancel it + await handle.cancel(); +} +``` + +## Parallel Execution ```typescript export async function parallelWorkflow(items: string[]): Promise { @@ -223,13 +204,6 @@ export async function parallelWorkflow(items: string[]): Promise { ## Continue-as-New -**WHY**: Prevents unbounded history growth by completing the current workflow and starting a new run with the same workflow ID. - -**WHEN to use**: -- Long-running workflows that would accumulate too much history -- Entity/subscription workflows that run indefinitely -- Batch processing with large numbers of iterations - ```typescript import { continueAsNew, workflowInfo } from '@temporalio/workflow'; @@ -249,45 +223,8 @@ export async function longRunningWorkflow(state: State): Promise { } ``` -## Cancellation Scopes - -**WHY**: Control how cancellation propagates to activities and child workflows. Essential for cleanup logic and timeout behavior. - -**WHEN to use**: -- Ensuring cleanup activities run even when workflow is cancelled -- Implementing timeouts for activity groups -- Manual cancellation of specific operations - -```typescript -import { CancellationScope, sleep } from '@temporalio/workflow'; - -export async function scopedWorkflow(): Promise { - // Non-cancellable scope - runs even if workflow cancelled - await CancellationScope.nonCancellable(async () => { - await cleanupActivity(); - }); - - // Timeout scope - await CancellationScope.withTimeout('5 minutes', async () => { - await longRunningActivity(); - }); - - // Manual cancellation - const scope = new CancellationScope(); - const promise = scope.run(() => someActivity()); - scope.cancel(); -} -``` - ## Saga Pattern -**WHY**: Implement distributed transactions by tracking compensation actions. If any step fails, previously completed steps are rolled back in reverse order. - -**WHEN to use**: -- Multi-step business transactions that span multiple services -- Operations where partial completion requires cleanup -- Financial transactions, order processing, booking systems - ```typescript export async function sagaWorkflow(order: Order): Promise { const compensations: Array<() => Promise> = []; @@ -314,35 +251,28 @@ export async function sagaWorkflow(order: Order): Promise { } ``` -## Entity Workflow Pattern +## Cancellation Scopes -**WHY**: Model a long-lived entity as a single workflow that handles events over its lifetime. - -**WHEN to use**: -- Modeling stateful entities that exist for extended periods -- Subscription management, user sessions -- Any entity that receives events and must maintain consistent state +Cancellation scopes control how cancellation propagates to activities and child workflows. Use them for cleanup logic, timeouts, and manual cancellation. ```typescript -import { defineSignal, defineQuery, setHandler, condition, continueAsNew, workflowInfo } from '@temporalio/workflow'; - -const eventSignal = defineSignal<[Event]>('event'); -const stateQuery = defineQuery('state'); +import { CancellationScope, sleep } from '@temporalio/workflow'; -export async function entityWorkflow(entityId: string, initialState: EntityState): Promise { - let state = initialState; +export async function scopedWorkflow(): Promise { + // Non-cancellable scope - runs even if workflow cancelled + await CancellationScope.nonCancellable(async () => { + await cleanupActivity(); + }); - setHandler(stateQuery, () => state); - setHandler(eventSignal, (event: Event) => { - state = applyEvent(state, event); + // Timeout scope + await CancellationScope.withTimeout('5 minutes', async () => { + await longRunningActivity(); }); - while (!state.deleted) { - await condition(() => state.deleted || workflowInfo().continueAsNewSuggested); - if (workflowInfo().continueAsNewSuggested && !state.deleted) { - await continueAsNew(entityId, state); - } - } + // Manual cancellation + const scope = new CancellationScope(); + const promise = scope.run(() => someActivity()); + scope.cancel(); } ``` @@ -369,14 +299,76 @@ export async function triggerWorkflow(): Promise { } ``` -## Timers +## Wait Condition with Timeout -**WHY**: Durable timers that survive worker restarts. Use sleep() for delays instead of JavaScript setTimeout. +```typescript +import { condition, CancelledFailure } from '@temporalio/workflow'; -**WHEN to use**: -- Implementing delays between steps -- Scheduling future actions -- Timeout patterns (combined with cancellation scopes) +export async function approvalWorkflow(): Promise { + let approved = false; + + setHandler(approveSignal, () => { + approved = true; + }); + + // Wait for approval with 24-hour timeout + const gotApproval = await condition(() => approved, '24 hours'); + + if (gotApproval) { + return 'approved'; + } else { + return 'auto-rejected due to timeout'; + } +} +``` + +## Waiting for All Handlers to Finish + +### WHY: Ensure all signal/update handlers complete before workflow exits +### WHEN: +- **Workflows with async handlers** - Prevent data loss from in-flight handlers +- **Before continue-as-new** - Ensure handlers complete before resetting + +```typescript +import { condition, workflowInfo } from '@temporalio/workflow'; + +export async function handlerAwareWorkflow(): Promise { + // ... main workflow logic ... + + // Before exiting, wait for all handlers to finish + await condition(() => workflowInfo().unsafe.isReplaying || allHandlersFinished()); + return 'done'; +} +``` + +## Activity Heartbeat Details + +### WHY: Resume activity progress after worker failure +### WHEN: +- **Long-running activities** - Track progress for resumability +- **Checkpointing** - Save progress periodically + +```typescript +import { heartbeat, activityInfo } from '@temporalio/activity'; + +export async function processLargeFile(filePath: string): Promise { + const info = activityInfo(); + // Get heartbeat details from previous attempt (if any) + const startLine: number = info.heartbeatDetails ?? 0; + + const lines = await readFileLines(filePath); + + for (let i = startLine; i < lines.length; i++) { + await processLine(lines[i]); + // Heartbeat with progress + heartbeat(i + 1); + } + + return 'completed'; +} +``` + +## Timers ```typescript import { sleep, CancellationScope } from '@temporalio/workflow'; @@ -400,23 +392,20 @@ export async function timerWorkflow(): Promise { } ``` -## uuid4() Utility +## Local Activities -**WHY**: Generate deterministic UUIDs safe to use in workflows. Uses the workflow seeded PRNG, so the same UUID is generated during replay. - -**WHEN to use**: -- Generating unique IDs for child workflows -- Creating idempotency keys -- Any situation requiring unique identifiers in workflow code +**Purpose**: Reduce latency for short, lightweight operations by skipping the task queue. ONLY use these when necessary for performance. Do NOT use these by default, as they are not durable and distributed. ```typescript -import { uuid4 } from '@temporalio/workflow'; +import { executeLocalActivity } from '@temporalio/workflow'; +import type * as activities from './activities'; -export async function workflowWithIds(): Promise { - const childWorkflowId = uuid4(); - await executeChild(childWorkflow, { - workflowId: childWorkflowId, - args: [input], - }); +const { quickLookup } = proxyLocalActivities({ + startToCloseTimeout: '5 seconds', +}); + +export async function localActivityWorkflow(): Promise { + const result = await quickLookup('key'); + return result; } ``` From 1b207f8fbf89391f1103bd05bfdf682e7204f629 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 25 Feb 2026 18:43:39 -0500 Subject: [PATCH 06/50] bulk alignment analysis + editing prep --- references/python/gotchas.edits.md | 63 + references/python/versioning.edits.md | 47 + .../typescript/advanced-features.edits.md | 173 ++ references/typescript/alignment_checking.md | 1469 +++++++++++++++++ references/typescript/correctness_checking.md | 312 ++++ references/typescript/data-handling.edits.md | 55 + references/typescript/determinism.edits.md | 53 + references/typescript/error-handling.edits.md | 113 ++ references/typescript/gotchas.edits.md | 105 ++ references/typescript/observability.edits.md | 50 + references/typescript/patterns.edits.md | 93 ++ references/typescript/patterns.md | 32 +- references/typescript/testing.edits.md | 151 ++ references/typescript/typescript.edits.md | 204 +++ 14 files changed, 2901 insertions(+), 19 deletions(-) create mode 100644 references/python/gotchas.edits.md create mode 100644 references/python/versioning.edits.md create mode 100644 references/typescript/advanced-features.edits.md create mode 100644 references/typescript/alignment_checking.md create mode 100644 references/typescript/correctness_checking.md create mode 100644 references/typescript/data-handling.edits.md create mode 100644 references/typescript/determinism.edits.md create mode 100644 references/typescript/error-handling.edits.md create mode 100644 references/typescript/gotchas.edits.md create mode 100644 references/typescript/observability.edits.md create mode 100644 references/typescript/patterns.edits.md create mode 100644 references/typescript/testing.edits.md create mode 100644 references/typescript/typescript.edits.md diff --git a/references/python/gotchas.edits.md b/references/python/gotchas.edits.md new file mode 100644 index 0000000..856a457 --- /dev/null +++ b/references/python/gotchas.edits.md @@ -0,0 +1,63 @@ +# Python gotchas.md Edits + +## Status: PENDING + +--- + +## Content to ADD + +### 1. Timers and Sleep section + +**Location:** After "Testing" section (at the end of file) + +**Add this section:** +```markdown +## Timers and Sleep + +### Using asyncio.sleep + +```python +# BAD: asyncio.sleep is not deterministic during replay +import asyncio + +@workflow.defn +class BadWorkflow: + @workflow.run + async def run(self) -> None: + await asyncio.sleep(60) # Non-deterministic! +``` + +```python +# GOOD: Use workflow.sleep for deterministic timers +from temporalio import workflow +from datetime import timedelta + +@workflow.defn +class GoodWorkflow: + @workflow.run + async def run(self) -> None: + await workflow.sleep(timedelta(seconds=60)) # Deterministic + # Or with string duration: + await workflow.sleep("1 minute") +``` + +**Why this matters:** `asyncio.sleep` uses the system clock, which differs between original execution and replay. `workflow.sleep` creates a durable timer in the event history, ensuring consistent behavior during replay. +``` + +--- + +## Content to DELETE + +None. + +--- + +## Content to FIX + +None. + +--- + +## Order Changes + +None - Python gotchas.md order is the reference order. diff --git a/references/python/versioning.edits.md b/references/python/versioning.edits.md new file mode 100644 index 0000000..3d8f867 --- /dev/null +++ b/references/python/versioning.edits.md @@ -0,0 +1,47 @@ +# Python versioning.md Edits + +## Status: PENDING + +--- + +## Content to ADD + +### 1. Choosing a Strategy section + +**Location:** After "Worker Versioning" section, before "Best Practices" + +**Add this section:** +```markdown +## Choosing a Strategy + +| Scenario | Recommended Approach | +|----------|---------------------| +| Minor bug fix, compatible change | Patching API (`patched()`) | +| Major logic change, incompatible | Workflow Type Versioning (new workflow name) | +| Infrastructure change, gradual rollout | Worker Versioning (Build ID) | +| Need to query/signal old workflows | Patching (keeps same workflow type) | +| Clean break, no backward compatibility | Workflow Type Versioning | + +**Decision factors:** +- **Patching API**: Best for incremental changes where you need to maintain the same workflow type and can gradually migrate +- **Workflow Type Versioning**: Best for major changes where a clean break is acceptable +- **Worker Versioning**: Best for infrastructure-level changes or when you need fine-grained deployment control +``` + +--- + +## Content to DELETE + +None. + +--- + +## Content to FIX + +None. + +--- + +## Order Changes + +None - Python versioning.md order is already the reference order. diff --git a/references/typescript/advanced-features.edits.md b/references/typescript/advanced-features.edits.md new file mode 100644 index 0000000..07ee3e2 --- /dev/null +++ b/references/typescript/advanced-features.edits.md @@ -0,0 +1,173 @@ +# TypeScript advanced-features.md Edits + +## Status: PENDING + +--- + +## Content to DELETE + +### 1. Continue-as-New section + +**Location:** Entire "Continue-as-New" section + +**Action:** DELETE this entire section. + +**Reason:** DUPLICATE - Already exists in patterns.md (TS#10). Remove from advanced-features.md. + +--- + +### 2. Workflow Updates section + +**Location:** Entire "Workflow Updates" section + +**Action:** DELETE this entire section. + +**Reason:** DUPLICATE - Already exists in patterns.md (TS#5 "Updates"). Remove from advanced-features.md. + +--- + +### 3. Nexus Operations section + +**Location:** Entire "Nexus Operations" section (includes service definition, handlers, workflow calling) + +**Action:** DELETE this entire section. + +**Reason:** Too advanced for reference docs. Users needing Nexus should consult official Temporal documentation. + +--- + +### 4. Activity Cancellation and Heartbeating section + +**Location:** Entire "Activity Cancellation and Heartbeating" section (includes ActivityCancellationType, Heartbeat Details) + +**Action:** DELETE this entire section. + +**Reason:** +- Heartbeat Details already in patterns.md (TS#16) +- ActivityCancellationType coverage not needed per user decision + +--- + +### 5. CancellationScope Patterns section + +**Location:** Entire "CancellationScope Patterns" section + +**Action:** DELETE this entire section. + +**Reason:** DUPLICATE - Already exists in patterns.md (TS#12 "Cancellation Scopes"). Remove from advanced-features.md. + +--- + +### 6. Best Practices section + +**Location:** Entire "Best Practices" section at the end + +**Action:** DELETE this entire section. + +**Reason:** Best practices are covered in individual sections throughout the reference docs. A generic list here is redundant. + +--- + +## Content to ADD + +### 7. Async Activity Completion section + +**Location:** After "Schedules" section + +**Add this section:** +```markdown +## Async Activity Completion + +Complete an activity asynchronously from outside the activity function. Useful when the activity needs to wait for an external event. + +**In the activity - return the task token:** +```typescript +import { Context } from '@temporalio/activity'; + +export async function asyncActivity(): Promise { + const ctx = Context.current(); + const taskToken = ctx.info.taskToken; + + // Store taskToken somewhere (database, queue, etc.) + await saveTaskToken(taskToken); + + // Throw to indicate async completion + throw Context.current().createAsyncCompletionHandle(); +} +``` + +**External completion (from another process):** +```typescript +import { Client } from '@temporalio/client'; + +async function completeActivity(taskToken: Uint8Array, result: string) { + const client = new Client(); + + await client.activity.complete(taskToken, result); + // Or for failure: + // await client.activity.fail(taskToken, new Error('Failed')); +} +``` + +**When to use:** +- Waiting for human approval +- Waiting for external webhook callback +- Long-polling external systems +``` + +--- + +### 8. Worker Tuning section + +**Location:** After "Async Activity Completion" section + +**Add this section:** +```markdown +## Worker Tuning + +Configure worker capacity for production workloads: + +```typescript +import { Worker, NativeConnection } from '@temporalio/worker'; + +const worker = await Worker.create({ + connection: await NativeConnection.connect({ address: 'temporal:7233' }), + taskQueue: 'my-queue', + workflowsPath: require.resolve('./workflows'), + activities, + + // Workflow execution concurrency + maxConcurrentWorkflowTaskExecutions: 100, + + // Activity execution concurrency + maxConcurrentActivityTaskExecutions: 100, + + // Graceful shutdown timeout + shutdownGraceTime: '30 seconds', + + // Enable sticky workflow cache (default: true) + enableStickyQueues: true, + + // Max cached workflows (memory vs latency tradeoff) + maxCachedWorkflows: 1000, +}); +``` + +**Key settings:** +- `maxConcurrentWorkflowTaskExecutions`: Max workflows running simultaneously +- `maxConcurrentActivityTaskExecutions`: Max activities running simultaneously +- `shutdownGraceTime`: Time to wait for in-progress work before forced shutdown +- `maxCachedWorkflows`: Number of workflows to keep in cache (reduces replay on cache hit) +``` + +--- + +## Order Changes + +After deletions and additions, the sections should be: +1. Schedules +2. Async Activity Completion (new) +3. Worker Tuning (new) +4. Sinks (TS-specific, keep) + +This provides a focused advanced-features file matching the pattern of Python's version. diff --git a/references/typescript/alignment_checking.md b/references/typescript/alignment_checking.md new file mode 100644 index 0000000..8a75a37 --- /dev/null +++ b/references/typescript/alignment_checking.md @@ -0,0 +1,1469 @@ +# Alignment Checking + +## Purpose + +Track content alignment (do files have the right sections?) and style alignment (is prose level appropriate?) across reference files. + +**Style target:** Python is the reference style (code-first, minimal prose). TypeScript should match. + +**Resume instructions:** Find sections marked `unchecked` or `needs review` and continue from there. + +--- + +## Section Inventory + +Shows which sections exist in each language. Organized by file. + +**Legend:** +- `✓` = present +- ` ` (empty) = missing, unknown if intentional (needs review) +- `—` = missing, intentional (language doesn't need this) +- `TODO` = missing, should add +- `DEL` = present, should remove or merge +- `Py#` / `TS#` = section order in file (should monotonically increase if order is aligned) + +### patterns.md + +| Section | Core | Python | Py# | TypeScript | TS# | Go | +|---------|------|--------|-----|------------|-----|-----| +| Signals | ✓ | ✓ | 1 | ✓ | 1 | | +| Dynamic Signal Handlers | — | ✓ | 2 | ✓ | 2 | | +| Queries | ✓ | ✓ | 3 | ✓ | 3 | | +| Dynamic Query Handlers | — | ✓ | 4 | ✓ | 4 | | +| Updates | ✓ | ✓ | 5 | ✓ | 5 | | +| Child Workflows | ✓ | ✓ | 6 | ✓ | 6 | | +| Child Workflow Options | — | — | — | ✓ | 7 | | +| Handles to External Workflows | — | ✓ | 7 | ✓ | 8 | | +| Parallel Execution | ✓ | ✓ | 8 | ✓ | 9 | | +| Deterministic Asyncio Alternatives | — | ✓ | 9 | — | — | | +| Continue-as-New | ✓ | ✓ | 10 | ✓ | 10 | | +| Saga Pattern | ✓ | ✓ | 11 | ✓ | 11 | | +| Cancellation Handling (asyncio) | — | ✓ | 12 | — | — | | +| Cancellation Scopes | — | — | — | ✓ | 12 | | +| Triggers | — | — | — | ✓ | 13 | | +| Wait Condition with Timeout | — | ✓ | 13 | ✓ | 14 | | +| Waiting for All Handlers to Finish | — | ✓ | 14 | ✓ | 15 | | +| Activity Heartbeat Details | — | ✓ | 15 | ✓ | 16 | | +| Timers | ✓ | ✓ | 16 | ✓ | 17 | | +| Local Activities | ✓ | ✓ | 17 | ✓ | 18 | | +| Entity Workflow Pattern | ✓ | — | — | — | — | | +| Polling Patterns | ✓ | — | — | — | — | | +| Idempotency Patterns | ✓ | — | — | — | — | | +| Using Pydantic Models | — | ✓ | 18 | — | — | | + +### data-handling.md + +| Section | Core | Python | Py# | TypeScript | TS# | Go | +|---------|------|--------|-----|------------|-----|-----| +| Overview | — | ✓ | 1 | ✓ | 1 | | +| Default Data Converter | — | ✓ | 2 | ✓ | 2 | | +| Pydantic Integration | — | ✓ | 3 | — | — | | +| Custom Data Converter | — | ✓ | 4 | ✓ | 5 | | +| Payload Encryption | — | ✓ | 5 | ✓ | 6 | | +| Search Attributes | — | ✓ | 6 | ✓ | 3 | | +| Workflow Memo | — | ✓ | 7 | ✓ | 4 | | +| Protobuf Support | — | — | — | ✓ | 7 | | +| Large Payloads | — | ✓ | 8 | ✓ | 8 | | +| Deterministic APIs for Values | — | ✓ | 9 | — | — | | +| Best Practices | — | ✓ | 10 | ✓ | 9 | | + +### error-handling.md + +| Section | Core | Python | Py# | TypeScript | TS# | Go | +|---------|------|--------|-----|------------|-----|-----| +| Overview | — | ✓ | 1 | ✓ | 1 | | +| Application Errors/Failures | — | ✓ | 2 | ✓ | 2 | | +| Non-Retryable Errors | — | ✓ | 3 | — | — | | +| Activity Errors | — | — | — | ✓ | 3 | | +| Handling Activity Errors in Workflows | — | ✓ | 4 | ✓ | 4 | | +| Retry Configuration | — | ✓ | 5 | ✓ | 5 | | +| Timeout Configuration | — | ✓ | 6 | ✓ | 6 | | +| Workflow Failure | — | ✓ | 7 | TODO | — | | +| Cancellation Handling in Activities | — | — | — | DEL | 7 | | +| Idempotency Patterns | — | — | — | DEL | 8 | | +| Best Practices | — | ✓ | 8 | ✓ | 9 | | + +### gotchas.md + +| Section | Core | Core# | Python | Py# | TypeScript | TS# | Go | +|---------|------|-------|--------|-----|------------|-----|-----| +| Idempotency / Non-Idempotent Activities | ✓ | 1 | — | — | DEL | 1 | | +| Replay Safety / Side Effects & Non-Determinism | ✓ | 2 | — | — | DEL | 2 | | +| Multiple Workers with Different Code | ✓ | 3 | — | — | — | — | | +| Retry Policies / Failing Activities Too Quickly | ✓ | 4 | — | — | DEL | 7 | | +| Query Handlers / Query Handler Mistakes | ✓ | 5 | — | — | DEL | 3 | | +| File Organization | ✓ | 6 | ✓ | 1 | — | — | | +| Activity Imports | — | — | — | — | ✓ | 4 | | +| Bundling Issues | — | — | — | — | ✓ | 5 | | +| Async vs Sync Activities | — | — | ✓ | 2 | — | — | | +| Error Handling | ✓ | 8 | — | — | DEL | 6 | | +| Wrong Retry Classification | ✓ | 8 | ✓ | 3 | TODO | — | | +| Cancellation | — | — | — | — | ✓ | 8 | | +| Heartbeating | — | — | ✓ | 4 | ✓ | 9 | | +| Testing | ✓ | 7 | ✓ | 5 | ✓ | 10 | | +| Timers and Sleep | — | — | TODO | — | ✓ | 11 | | + +### observability.md + +| Section | Core | Python | Py# | TypeScript | TS# | Go | +|---------|------|--------|-----|------------|-----|-----| +| Overview | — | ✓ | 1 | ✓ | 1 | | +| Logging / Replay-Aware Logging | — | ✓ | 2 | ✓ | 2 | | +| Customizing the Logger | — | ✓ | 2 | ✓ | 3 | | +| OpenTelemetry Integration | — | — | — | DEL | 4 | | +| Metrics | — | ✓ | 3 | ✓ | 5 | | +| Search Attributes (Visibility) | — | ✓ | 4 | — | — | | +| Debugging with Event History | — | — | — | DEL | 6 | | +| Best Practices | — | ✓ | 5 | ✓ | 7 | | + +### testing.md + +| Section | Core | Python | Py# | TypeScript | TS# | Go | +|---------|------|--------|-----|------------|-----|-----| +| Overview | — | ✓ | 1 | ✓ | 1 | | +| Test Environment Setup | — | ✓ | 2 | ✓ | 2 | | +| Time Skipping | — | — | — | DEL | 3 | | +| Activity Mocking | — | ✓ | 3 | ✓ | 4 | | +| Testing Signals and Queries | — | ✓ | 4 | ✓ | 6 | | +| Testing Failure Cases | — | ✓ | 5 | TODO | — | | +| Replay Testing | — | ✓ | 6 | ✓ | 5 | | +| Activity Testing | — | ✓ | 7 | TODO | — | | +| Best Practices | — | ✓ | 8 | ✓ | 7 | | + +### versioning.md + +| Section | Core | Core# | Python | Py# | TypeScript | TS# | Go | +|---------|------|-------|--------|-----|------------|-----|-----| +| Overview | ✓ | 1 | ✓ | 1 | ✓ | 1 | | +| Why Versioning is Needed | ✓ | 2 | ✓ | 2 | ✓ | 2 | | +| Patching API | ✓ | 3 | ✓ | 3 | ✓ | 3 | | +| Workflow Type Versioning | ✓ | 4 | ✓ | 4 | ✓ | 4 | | +| Worker Versioning | ✓ | 5 | ✓ | 5 | ✓ | 5 | | +| Choosing a Strategy | ✓ | 6 | TODO | — | ✓ | 6 | | +| Best Practices | ✓ | 7 | ✓ | 6 | ✓ | 7 | | +| Finding Workflows by Version | ✓ | 8 | — | — | — | — | | +| Common Mistakes | ✓ | 9 | — | — | — | — | | + +### {language}.md (top-level files) + +| Section | Core | Python | Py# | TypeScript | TS# | Go | +|---------|------|--------|-----|------------|-----|-----| +| Overview | — | ✓ | 1 | ✓ | 1 | | +| How Temporal Works: History Replay | — | — | — | DEL | 2 | | +| Quick Start / Quick Demo | — | ✓ | 2 | ✓ | 3 | | +| Key Concepts | — | ✓ | 3 | ✓ | 4 | | +| Determinism Rules | — | — | — | ✓ | 5 | | +| File Organization Best Practice | — | ✓ | 4 | TODO | — | | +| Common Pitfalls | — | ✓ | 5 | ✓ | 6 | | +| Writing Tests | — | ✓ | 6 | ✓ | 7 | | +| Additional Resources | — | ✓ | 7 | ✓ | 8 | | + +### determinism-protection.md + +| Section | Core | Python | Py# | TypeScript | TS# | Go | +|---------|------|--------|-----|------------|-----|-----| +| Overview | — | ✓ | 1 | ✓ | 1 | | +| How the Sandbox Works | — | ✓ | 2 | — | — | | +| Import Blocking | — | — | — | ✓ | 2 | | +| Forbidden Operations | — | ✓ | 3 | — | — | | +| Function Replacement | — | — | — | ✓ | 3 | | +| Pass-Through Pattern | — | ✓ | 4 | — | — | | +| Importing Activities | — | ✓ | 5 | — | — | | +| Disabling the Sandbox | — | ✓ | 6 | — | — | | +| Customizing Invalid Module Members | — | ✓ | 7 | — | — | | +| Import Notification Policy | — | ✓ | 8 | — | — | | +| Disable Lazy sys.modules Passthrough | — | ✓ | 9 | — | — | | +| File Organization | — | ✓ | 10 | — | — | | +| Common Issues | — | ✓ | 11 | — | — | | +| Best Practices | — | ✓ | 12 | — | — | | + +### determinism.md + +| Section | Core | Core# | Python | Py# | TypeScript | TS# | Go | +|---------|------|-------|--------|-----|------------|-----|-----| +| Overview | ✓ | 1 | ✓ | 1 | ✓ | 1 | | +| Why Determinism Matters | ✓ | 2 | ✓ | 2 | ✓ | 2 | | +| Sources of Non-Determinism | ✓ | 3 | — | — | — | — | | +| Central Concept: Activities | ✓ | 4 | — | — | — | — | | +| SDK Protection / Sandbox | ✓ | 5 | ✓ | 6 | ✓ | 3 | | +| Forbidden Operations | — | — | ✓ | 3 | ✓ | 4 | | +| Safe Builtin Alternatives | — | — | ✓ | 4 | — | — | | +| Detecting Non-Determinism | ✓ | 6 | — | — | — | — | | +| Recovery from Non-Determinism | ✓ | 7 | — | — | — | — | | +| Testing Replay Compatibility | — | — | ✓ | 5 | ✓ | 5 | | +| Best Practices | ✓ | 8 | ✓ | 7 | ✓ | 6 | | + +--- + +## Style Alignment: TypeScript vs Python + +### patterns.md + +#### Signals +- **Python:** Code only +- **TypeScript:** Code only +- **Decision:** all good + +--- + +#### Dynamic Signal Handlers +- **Python:** One-liner intro + code +- **TypeScript:** One-liner intro + code +- **Decision:** all good + +--- + +#### Queries +- **Python:** One-liner note ("must NOT modify state") + code +- **TypeScript:** Code only +- **Decision:** ⚠️ needs fix — TS should add "**Important:** Queries must NOT modify workflow state or have side effects" note + +--- + +#### Dynamic Query Handlers +- **Python:** Code only +- **TypeScript:** One-liner intro + code +- **Decision:** all good + +--- + +#### Updates +- **Python:** Code only (validator shown inline) +- **TypeScript:** Code only (shows both simple and validated handlers) +- **Decision:** all good + +--- + +#### Child Workflows +- **Python:** Code only (shows parent_close_policy inline) +- **TypeScript:** Code only + separate "Child Workflow Options" subsection +- **Decision:** all good (TS has more options to show, subsection is appropriate) + +--- + +#### Handles to External Workflows +- **Python:** Code only +- **TypeScript:** Code only +- **Decision:** all good + +--- + +#### Parallel Execution +- **Python:** Code + "Deterministic Alternatives to asyncio" subsection +- **TypeScript:** Code only (just Promise.all) +- **Decision:** all good (Python needs asyncio alternatives, TS doesn't) + +--- + +#### Continue-as-New +- **Python:** Code only +- **TypeScript:** Code only +- **Decision:** all good + +--- + +#### Saga Pattern +- **Python:** One-liner note ("compensation activities should be idempotent") + code with detailed comments explaining WHY save compensation BEFORE activity +- **TypeScript:** Code only (simpler example, no inline comments) +- **Decision:** ⚠️ needs fix + - TS should add "**Important:** Compensation activities should be idempotent" note + - TS should add comments explaining WHY compensation is saved BEFORE the activity (critical edge case) + - ⚠️ **BUG:** TS uses `console.log` in catch block — should use `log` from `@temporalio/workflow` for replay-safe logging + +--- + +#### Cancellation Handling (Python) vs Cancellation Scopes (TypeScript) +- **Python:** "Cancellation Handling - leverages standard asyncio cancellation" - code showing asyncio.CancelledError +- **TypeScript:** "Cancellation Scopes" - one-liner + code showing CancellationScope patterns +- **Decision:** all good (different language idioms - Python uses asyncio, TS uses CancellationScope) + +--- + +#### Triggers (TypeScript only) +- **Python:** N/A (no equivalent) +- **TypeScript:** WHY/WHEN + code +- **Decision:** all good (TS-specific pattern, needs explanation) + +--- + +#### Wait Condition with Timeout +- **Python:** Code only +- **TypeScript:** Code only +- **Decision:** all good + +--- + +#### Waiting for All Handlers to Finish +- **Python:** WHY/WHEN (as ### headers) + code +- **TypeScript:** WHY/WHEN (as ### headers) + code +- **Decision:** all good (both have same structure, important pattern worth explaining) + +--- + +#### Activity Heartbeat Details +- **Python:** WHY/WHEN (as ### headers) + code +- **TypeScript:** WHY/WHEN (as ### headers) + code +- **Decision:** all good (both have same structure) + +--- + +#### Timers +- **Python:** Code only (simple sleep example) +- **TypeScript:** Code only (shows sleep + cancellable timer with CancellationScope) +- **Decision:** all good (TS shows more because CancellationScope is TS-specific) + +--- + +#### Local Activities +- **Python:** Purpose note + code +- **TypeScript:** Purpose note + code +- **Decision:** all good (both have same structure, warning is important) + +--- + +### data-handling.md + +#### Overview +- **Python:** One-liner describing data converters +- **TypeScript:** One-liner describing data converters +- **Decision:** all good + +--- + +#### Default Data Converter +- **Python:** Bullet list of supported types +- **TypeScript:** Bullet list of supported types +- **Decision:** all good + +--- + +#### Pydantic Integration (Python only) +- **Python:** Explanation + two code blocks (model definition + client setup) +- **TypeScript:** N/A (no equivalent) +- **Decision:** all good (Python-specific feature) + +--- + +#### Custom Data Converter +- **Python:** Brief explanation + links to example files +- **TypeScript:** Brief explanation + full code example +- **Decision:** all good (TS inline example is appropriate; Python linking to samples is also valid) + +--- + +#### Payload Encryption +- **Python:** Code only (with inline comment about GIL) +- **TypeScript:** Code only +- **Decision:** all good + +--- + +#### Search Attributes +- **Python:** Code examples for setting at start, upserting, querying (with typed SearchAttributeKey) +- **TypeScript:** Code examples for setting at start, upserting, reading, querying +- **Decision:** all good (TypeScript has extra "Reading" subsection which is fine) + +--- + +#### Workflow Memo +- **Python:** Code for setting + reading (two separate blocks) +- **TypeScript:** Code for setting + reading (combined block) +- **Decision:** all good + +--- + +#### Protobuf Support (TypeScript only) +- **Python:** N/A +- **TypeScript:** One-liner + code +- **Decision:** all good (TS-specific section) + +--- + +#### Large Payloads +- **Python:** Bullet list + code example (reference pattern) +- **TypeScript:** Bullet list + code example (reference pattern) +- **Decision:** all good + +--- + +#### Deterministic APIs for Values (Python only) +- **Python:** One-liner + code showing workflow.uuid4() and workflow.random() +- **TypeScript:** N/A +- **Decision:** all good (Python-specific; TS doesn't need this section) + +--- + +#### Best Practices +- **Python:** Numbered list (6 items) +- **TypeScript:** Numbered list (6 items) +- **Decision:** all good (content differs slightly but appropriate per language) + +--- + +### error-handling.md + +#### Overview +- **Python:** One-liner describing ApplicationError and retry policy; notes applicability to activities, child workflows, Nexus +- **TypeScript:** One-liner describing ApplicationFailure with non-retryable marking +- **Decision:** all good + +--- + +#### Application Errors/Failures +- **Python:** "Application Errors" - code example in activity context +- **TypeScript:** "Application Failures" - code example in workflow context +- **Decision:** all good (different names match SDK terminology) + +--- + +#### Non-Retryable Errors (Python only) +- **Python:** Dedicated section with detailed code example showing non_retryable=True +- **TypeScript:** N/A (covered inline in Application Failures section) +- **Decision:** all good (TS shows nonRetryable inline, Python splits it out for emphasis) + +--- + +#### Activity Errors (TypeScript only) +- **Python:** N/A (covered in Application Errors) +- **TypeScript:** Separate section showing ApplicationFailure in activity context +- **Decision:** all good (TS splits workflow vs activity contexts) + +--- + +#### Handling Activity Errors in Workflows +- **Python:** Code showing try/except with ActivityError, uses `workflow.logger` +- **TypeScript:** Code showing try/catch with ApplicationFailure instanceof check +- **Decision:** ⚠️ **BUG:** TS uses `console.log` in workflow — should use `log` from `@temporalio/workflow` + +--- + +#### Retry Configuration +- **Python:** Code + note about preferring defaults ("Only set options... if you have a domain-specific reason to") +- **TypeScript:** Code only +- **Decision:** ⚠️ needs fix — TS should add note about preferring defaults + +--- + +#### Timeout Configuration +- **Python:** Code with inline comments explaining each timeout +- **TypeScript:** Code with inline comments explaining each timeout +- **Decision:** all good + +--- + +#### Workflow Failure (Python only) +- **Python:** Code + note about not using non_retryable in workflows +- **TypeScript:** N/A +- **Decision:** TODO — Add TS equivalent with nonRetryable warning + +--- + +#### Cancellation Handling in Activities (TypeScript only) +- **Python:** N/A +- **TypeScript:** Code showing CancelledFailure handling with heartbeat +- **Decision:** DEL from error-handling.md — Move to patterns.md (Python already has Cancellation Handling there) + +--- + +#### Idempotency Patterns (TypeScript only) +- **Python:** N/A (brief mention in Best Practices, references core/patterns.md) +- **TypeScript:** Detailed section with WHY + Using Keys + Granular Activities subsections +- **Decision:** DEL — Remove from TS, replace with reference to core (like Python does) + +--- + +#### Best Practices +- **Python:** Numbered list (6 items), includes reference to core/patterns.md for idempotency +- **TypeScript:** Numbered list (7 items), includes idempotency items +- **Decision:** all good + +--- + +### gotchas.md + +**Note:** These files have very different structures. TypeScript has 11 detailed sections with code examples. Python has 5 sections and references other docs more heavily. + +#### Idempotency (TypeScript only) +- **Python:** N/A (covered in core/patterns.md) +- **TypeScript:** BAD/GOOD code example +- **Decision:** DEL — Remove from TypeScript (Core coverage sufficient) + +--- + +#### Replay Safety (TypeScript only) +- **Python:** N/A +- **TypeScript:** Subsections for Side Effects and Non-Deterministic Operations with code +- **Decision:** DEL — Remove from TypeScript (Core coverage sufficient) + +--- + +#### Query Handlers (TypeScript only) +- **Python:** N/A +- **TypeScript:** Subsections for Modifying State and Blocking in Queries +- **Decision:** DEL — Remove from TypeScript (Core coverage sufficient) + +--- + +#### File Organization (Python only) +- **Python:** Subsections for Importing Activities and Mixing Workflows/Activities +- **TypeScript:** N/A (covered in Activity Imports section) +- **Decision:** all good (different names, similar concepts) + +--- + +#### Activity Imports (TypeScript only) +- **Python:** N/A (covered in File Organization) +- **TypeScript:** Subsections for type-only imports and Node.js module restrictions +- **Decision:** all good (Python covers import issues in File Organization) + +--- + +#### Bundling Issues (TypeScript only) +- **Python:** N/A +- **TypeScript:** Subsections for Missing Dependencies and Package Version Mismatches +- **Decision:** all good (TS-specific concern due to workflow bundling) + +--- + +#### Async vs Sync Activities (Python only) +- **Python:** Subsections for Blocking in Async and Missing Executor +- **TypeScript:** N/A +- **Decision:** all good (Python-specific concern) + +--- + +#### Error Handling (TypeScript only) +- **Python:** N/A (references error-handling.md) +- **TypeScript:** Subsections for Swallowing Errors and Wrong Retry Classification +- **Decision:** all good (Python references separate file) + +--- + +#### Wrong Retry Classification +- **Python:** Brief note referencing error-handling.md +- **TypeScript:** N/A +- **Decision:** TODO — Add brief note + reference to TypeScript (like Python has) + +--- + +#### Retry Policies (TypeScript only) +- **Python:** N/A +- **TypeScript:** "Too Aggressive" subsection with code +- **Decision:** DEL — Remove from TypeScript (Core coverage sufficient) + +--- + +#### Cancellation (TypeScript only) +- **Python:** N/A +- **TypeScript:** "Not Handling Cancellation" with CancellationScope example +- **Decision:** all good (TS-specific due to CancellationScope API) + +--- + +#### Heartbeating +- **Python:** Two subsections: Forgetting to Heartbeat, Timeout Too Short +- **TypeScript:** Two subsections: Forgetting to Heartbeat, Timeout Too Short +- **Decision:** all good (both have equivalent content) + +--- + +#### Testing +- **Python:** Brief note referencing testing.md +- **TypeScript:** Full code examples for failure testing and replay testing +- **Decision:** all good (Python references separate file, TS has inline examples) + +--- + +#### Timers and Sleep (TypeScript only) +- **Python:** N/A +- **TypeScript:** "Using JavaScript setTimeout" BAD/GOOD example +- **Decision:** TODO — Add Python equivalent section for `asyncio.sleep` vs `workflow.sleep` + +--- + +### observability.md + +#### Overview +- **Python:** One-liner mentioning logging, metrics, tracing, visibility +- **TypeScript:** One-liner mentioning replay-aware logging, metrics, OpenTelemetry +- **Decision:** all good + +--- + +#### Logging / Replay-Aware Logging +- **Python:** "Logging" with subsections for Workflow and Activity logging + code examples +- **TypeScript:** "Replay-Aware Logging" with subsections for Workflow and Activity logging + code examples +- **Decision:** all good (different section names, same content structure) + +--- + +#### Customizing the Logger +- **Python:** Subsection under Logging showing basicConfig +- **TypeScript:** Separate section with Basic Configuration + Winston Integration subsections +- **Decision:** all good (TS has more detail for Winston, appropriate) + +--- + +#### OpenTelemetry Integration (TypeScript only) +- **Python:** N/A +- **TypeScript:** Setup + Worker Configuration subsections with full code +- **Decision:** DEL — Remove from TypeScript (too detailed for reference docs) + +--- + +#### Metrics +- **Python:** Enabling SDK Metrics + Key SDK Metrics subsections +- **TypeScript:** Prometheus Metrics + OTLP Metrics subsections +- **Decision:** all good (both cover metrics, different focus) + +--- + +#### Search Attributes (Python only) +- **Python:** Brief reference to data-handling.md +- **TypeScript:** N/A (covered in data-handling.md) +- **Decision:** all good (Python includes as observability concept, TS keeps in data-handling) + +--- + +#### Debugging with Event History (TypeScript only) +- **Python:** N/A +- **TypeScript:** Viewing Event History, Key Events table, Debugging Non-Determinism +- **Decision:** DEL — Remove from TypeScript (too detailed) + +--- + +#### Best Practices +- **Python:** 4 items +- **TypeScript:** 6 items +- **Decision:** all good + +--- + +### testing.md + +#### Overview +- **Python:** Multi-sentence describing WorkflowEnvironment and ActivityEnvironment +- **TypeScript:** One-liner mentioning TestWorkflowEnvironment +- **Decision:** all good (Python more detailed intro) + +--- + +#### Test Environment Setup +- **Python:** "Workflow Test Environment" - detailed pattern explanation + code +- **TypeScript:** "Test Environment Setup" - code example with before/after hooks +- **Decision:** all good (both comprehensive) + +--- + +#### Time Skipping (TypeScript only) +- **Python:** N/A (mentioned inline in environment section) +- **TypeScript:** Separate section with code examples +- **Decision:** DEL — Remove dedicated section from TypeScript (mention inline like Python) + +--- + +#### Activity Mocking +- **Python:** "Mocking Activities" - code with @activity.defn mock +- **TypeScript:** "Activity Mocking" - inline activity object in Worker.create +- **Decision:** all good + +--- + +#### Testing Signals and Queries +- **Python:** Code example with signal/query +- **TypeScript:** Code example with signal/query +- **Decision:** all good + +--- + +#### Testing Failure Cases (Python only) +- **Python:** Code example with pytest.raises +- **TypeScript:** N/A +- **Decision:** TODO — Add TS equivalent section + +--- + +#### Replay Testing +- **Python:** "Workflow Replay Testing" - code with Replayer class +- **TypeScript:** "Replay Testing" - code with Worker.runReplayHistory +- **Decision:** all good + +--- + +#### Activity Testing (Python only) +- **Python:** Code with ActivityEnvironment +- **TypeScript:** N/A +- **Decision:** TODO — Add TS equivalent for isolated activity testing + +--- + +#### Best Practices +- **Python:** 6 items +- **TypeScript:** 5 items +- **Decision:** all good + +--- + +### versioning.md + +#### Overview +- **Core:** Brief intro listing three approaches +- **Python:** Detailed intro covering all three approaches +- **TypeScript:** Brief intro covering all three approaches +- **Decision:** all good + +--- + +#### Why Versioning is Needed +- **Core:** Conceptual explanation with pseudo-code +- **Python:** Detailed explanation with replay context +- **TypeScript:** "Why Versioning Matters" - similar detailed explanation +- **Decision:** all good + +--- + +#### Patching API +- **Core:** Conceptual with Three-Phase Lifecycle, When to Use, When NOT to Use +- **Python:** Full code examples with patched(), Three-Step Process, Multiple Patches, Query Filters +- **TypeScript:** Full code examples with patched(), Three-Step Process, Multiple Patches, Query Filters +- **Decision:** all good (Core conceptual, languages have implementation details) + +--- + +#### Workflow Type Versioning +- **Core:** Conceptual with process steps +- **Python:** Full code examples with Worker registration +- **TypeScript:** Full code examples with Worker registration +- **Decision:** all good + +--- + +#### Worker Versioning +- **Core:** Key Concepts, PINNED/AUTO_UPGRADE guidance +- **Python:** Full code examples, Deployment Strategies, Querying +- **TypeScript:** Full code examples, Deployment Strategies, Rainbow/Blue-Green +- **Decision:** all good + +--- + +#### Choosing a Strategy +- **Core:** Decision table +- **Python:** N/A +- **TypeScript:** Decision table +- **Decision:** TODO — Add decision table to Python + +--- + +#### Best Practices +- **Core:** 5 items +- **Python:** 7 items +- **TypeScript:** 8 items +- **Decision:** all good + +--- + +#### Finding Workflows by Version (Core only) +- **Core:** CLI examples for querying by version +- **Python:** N/A (covered in Query Filters subsection) +- **TypeScript:** N/A (covered in Query Filters subsection) +- **Decision:** all good (languages include as subsections) + +--- + +#### Common Mistakes (Core only) +- **Core:** 4 common mistakes +- **Python:** N/A +- **TypeScript:** N/A +- **Decision:** — (intentional) — Keep Core-only; conceptual coverage sufficient + +--- + +### {language}.md (top-level files) + +#### Overview +- **Python:** One-liner about async/type-safe, Python 3.9+, sandbox +- **TypeScript:** One-liner about async/await, V8 sandbox + version warning +- **Decision:** all good + +--- + +#### How Temporal Works: History Replay (TypeScript only) +- **Python:** N/A (covered in core/determinism.md) +- **TypeScript:** Detailed explanation with Commands/Events table, When Replay Occurs +- **Decision:** DEL — Remove from TypeScript; both languages should reference core/determinism.md + +--- + +#### Quick Start / Quick Demo +- **Python:** Full multi-file example with activities, workflows, worker, starter +- **TypeScript:** Shorter example with activities, workflows, worker +- **Decision:** TODO — Expand TypeScript to match Python (see detailed gaps below) + +--- + +#### Key Concepts +- **Python:** Workflow Definition, Activity Definition (sync vs async), Worker Setup, Determinism subsection +- **TypeScript:** Workflow Definition, Activity Definition, Worker Setup (no Determinism subsection) +- **Decision:** all good (TS has separate Determinism Rules section) + +--- + +#### Determinism Rules (TypeScript only) +- **Python:** N/A (has Determinism subsection in Key Concepts) +- **TypeScript:** Separate section with automatic replacements and safe operations +- **Decision:** all good (different organization, both cover determinism) + +--- + +#### File Organization Best Practice (Python only) +- **Python:** Directory structure + code example for sandbox imports +- **TypeScript:** N/A +- **Decision:** TODO — Add to TypeScript with bundling/import guidance + +--- + +#### Common Pitfalls +- **Python:** 7 items +- **TypeScript:** 5 items +- **Decision:** all good + +--- + +#### Writing Tests +- **Python:** Reference to testing.md +- **TypeScript:** Reference to testing.md +- **Decision:** all good + +--- + +#### Additional Resources +- **Python:** Correct references to python files +- **TypeScript:** ⚠️ BUG — all references point to `references/python/` instead of `references/typescript/` +- **Decision:** FIX REQUIRED — update TypeScript file paths + +--- + +### determinism-protection.md + +**Note:** These files have very different structures due to different sandbox implementations. Python has 12 detailed sections; TypeScript has 3 sections. + +#### Overview +- **Python:** One-liner about sandbox protection +- **TypeScript:** One-liner about V8 sandbox +- **Decision:** all good + +--- + +#### How the Sandbox Works (Python only) +- **Python:** Bullet list of sandbox mechanisms +- **TypeScript:** N/A (covered in Overview) +- **Decision:** all good + +--- + +#### Import Blocking (TypeScript only) +- **Python:** N/A +- **TypeScript:** Code example with ignoreModules for bundler +- **Decision:** all good (TS-specific V8 concern) + +--- + +#### Forbidden Operations (Python only) +- **Python:** List of forbidden operations (I/O, threading, subprocess, etc.) +- **TypeScript:** N/A (covered briefly in determinism.md) +- **Decision:** all good + +--- + +#### Function Replacement (TypeScript only) +- **Python:** N/A +- **TypeScript:** Explains Date/Math.random deterministic replacement with code example +- **Decision:** all good (TS-specific V8 feature) + +--- + +#### Pass-Through Pattern (Python only) +- **Python:** Code example with imports_passed_through(), when to use +- **TypeScript:** N/A +- **Decision:** all good (Python-specific sandbox pattern) + +--- + +#### Importing Activities (Python only) +- **Python:** Full code example for activity imports +- **TypeScript:** N/A (uses type-only imports, covered in gotchas.md) +- **Decision:** all good + +--- + +#### Disabling the Sandbox (Python only) +- **Python:** Code with sandbox_unrestricted(), warnings +- **TypeScript:** N/A +- **Decision:** all good (Python-specific escape hatch) + +--- + +#### Customizing Invalid Module Members (Python only) +- **Python:** Detailed code for SandboxRestrictions customization +- **TypeScript:** N/A +- **Decision:** all good (Python-specific advanced config) + +--- + +#### Import Notification Policy (Python only) +- **Python:** Code for warning/error policies +- **TypeScript:** N/A +- **Decision:** all good (Python-specific) + +--- + +#### File Organization (Python only) +- **Python:** Directory structure example +- **TypeScript:** N/A (covered in gotchas.md) +- **Decision:** all good + +--- + +#### Common Issues (Python only) +- **Python:** Import errors and non-determinism from libraries +- **TypeScript:** N/A +- **Decision:** all good + +--- + +#### Best Practices (Python only) +- **Python:** 5 items +- **TypeScript:** N/A (covered in determinism.md) +- **Decision:** all good + +--- + +### determinism.md + +#### Overview +- **Core:** One-liner about determinism and replay +- **Python:** One-liner about sandbox protection +- **TypeScript:** One-liner about V8 sandbox +- **Decision:** all good + +--- + +#### Why Determinism Matters +- **Core:** Detailed with Replay Mechanism, Commands/Events table, Non-Determinism Example +- **Python:** "Why Determinism Matters: History Replay" - brief explanation +- **TypeScript:** "Why Determinism Matters" - brief explanation +- **Decision:** all good (Core has conceptual depth, languages are brief) + +--- + +#### Sources of Non-Determinism (Core only) +- **Core:** Detailed categories: Time, Random, External State, Iteration, Threading +- **Python:** N/A (covered in Forbidden Operations) +- **TypeScript:** N/A (covered in Forbidden Operations) +- **Decision:** all good (Core conceptual, languages list forbidden operations) + +--- + +#### Central Concept: Activities (Core only) +- **Core:** Explains activities as primary mechanism for non-deterministic code +- **Python:** N/A +- **TypeScript:** N/A +- **Decision:** all good (important conceptual point in Core) + +--- + +#### SDK Protection / Sandbox +- **Core:** Brief mention of both Python and TypeScript sandboxes +- **Python:** "Sandbox Behavior" - describes isolation mechanisms +- **TypeScript:** "Temporal's V8 Sandbox" - code example + explanation +- **Decision:** all good + +--- + +#### Forbidden Operations +- **Core:** N/A (covered in Sources) +- **Python:** Bullet list of forbidden operations +- **TypeScript:** Code example of forbidden imports/operations +- **Decision:** all good + +--- + +#### Safe Builtin Alternatives (Python only) +- **Core:** N/A +- **Python:** Table mapping forbidden → safe alternatives +- **TypeScript:** N/A +- **Decision:** — (intentional) — Keep as Python-only; TS V8 sandbox handles this automatically + +--- + +#### Detecting Non-Determinism (Core only) +- **Core:** During Execution + Testing with Replay subsections +- **Python:** N/A +- **TypeScript:** N/A +- **Decision:** all good (conceptual content in Core) + +--- + +#### Recovery from Non-Determinism (Core only) +- **Core:** Accidental Change + Intentional Change subsections +- **Python:** N/A +- **TypeScript:** N/A +- **Decision:** all good (conceptual content in Core) + +--- + +#### Testing Replay Compatibility +- **Core:** N/A (covered in Detecting subsection) +- **Python:** Reference to testing.md +- **TypeScript:** Reference to testing.md +- **Decision:** all good + +--- + +#### Best Practices +- **Core:** 5 items +- **Python:** 7 items +- **TypeScript:** 5 items +- **Decision:** all good + +--- + +## Summary + +### patterns.md + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Decided to keep as Core-only:** +- Polling Patterns: Core conceptual explanation sufficient +- Idempotency Patterns: Core conceptual explanation sufficient + +**Intentionally missing (`—`):** +- Dynamic handlers, External workflow handles, Wait conditions, Heartbeat details: language-specific implementation, core has concepts only +- Child Workflow Options: TS-specific (Python shows inline) +- Deterministic Asyncio Alternatives: Python-specific (TS doesn't have this issue) +- Cancellation Handling vs Cancellation Scopes: different idioms per language +- Triggers: TS-specific pattern +- Entity Workflow Pattern: conceptual in core, implementation left to user +- Using Pydantic Models: Python-specific + +**Order alignment:** ✓ Aligned — TS# monotonically increases + +**Style alignment issues:** +- ⚠️ **Queries:** TS missing "Important: must NOT modify state" note +- ⚠️ **Saga Pattern:** TS missing idempotency note, missing critical comments about saving compensation BEFORE activity +- ⚠️ **Saga Pattern BUG:** TS uses `console.log` — should use `log` from `@temporalio/workflow` + +### data-handling.md + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Intentionally missing (`—`):** +- Core column: data handling is implementation-specific, no core concepts doc needed +- Pydantic Integration: Python-specific (TS uses plain JSON/types) +- Protobuf Support: TS-specific section (Python handles protobufs via default converter) +- Deterministic APIs for Values: Python-specific (`workflow.uuid4()`, `workflow.random()`) + +**Order alignment:** ⚠️ NOT ALIGNED — TS# column is not monotonic (5, 6, 3, 4...) +- Search Attributes: Py#6, TS#3 +- Workflow Memo: Py#7, TS#4 +- Custom Data Converter: Py#4, TS#5 +- Payload Encryption: Py#5, TS#6 +- **Action:** Reorder TypeScript to match Python order + +**Style alignment:** All TypeScript sections aligned with Python. No changes needed. + +### error-handling.md + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Intentionally missing (`—`):** +- Core column: error handling is implementation-specific, no core concepts doc needed +- Non-Retryable Errors: TS covers inline in Application Failures +- Activity Errors: Python covers in Application Errors +- Workflow Failure: TS-specific section not needed (different SDK design) +- Idempotency Patterns: TS-specific detailed section; Python references core/patterns.md + +**Sections marked DEL:** +- Cancellation Handling in Activities: Move from error-handling.md to patterns.md + +**Order alignment:** ✓ Aligned — TS# monotonically increases + +**Action items:** +- **TypeScript TODO:** Add Workflow Failure section with nonRetryable warning +- **TypeScript DEL:** Move Cancellation Handling in Activities to patterns.md (Python already has it there) +- Idempotency Patterns: Python references core/patterns.md which is appropriate; no change needed + +**Style alignment issues:** +- ⚠️ **Handling Activity Errors BUG:** TS uses `console.log` — should use `log` from `@temporalio/workflow` +- ⚠️ **Retry Configuration:** TS missing note about preferring defaults + +### gotchas.md + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Decided to keep as-is:** +- Multiple Workers with Different Code: Core-only (conceptual explanation sufficient) +- Heartbeating: Py/TS-only (language-specific code examples, no Core conceptual section needed) + +**Intentionally missing (`—`):** +- Idempotency, Replay Safety, Query Handlers, Error Handling, Retry Policies: Core-only (conceptual) +- Multiple Workers with Different Code: Core-only (conceptual) +- File Organization: Core + Python; TS covers similar in Activity Imports +- Activity Imports: TS-specific (bundling/sandbox concerns) +- Bundling Issues: TS-specific (workflow bundling) +- Async vs Sync Activities: Python-specific +- Cancellation: TS-specific (CancellationScope API) +- Timers and Sleep: TS-specific + +**Order alignment:** N/A after cleanup — Core has conceptual sections, language files have implementation-specific sections + +**Action items:** +- **TypeScript DEL:** Remove Idempotency, Replay Safety, Retry Policies, Query Handlers, Error Handling (move to Core only) +- **TypeScript TODO:** Add Wrong Retry Classification section (brief note + reference, like Python) +- **Python TODO:** Add Timers and Sleep section for `asyncio.sleep` vs `workflow.sleep` gotcha + +**Style alignment:** After changes: +- Core: 8 conceptual sections with symptoms/fixes (authoritative for cross-cutting concerns) +- TypeScript: 7 sections (Activity Imports, Bundling, Cancellation, Heartbeating, Testing, Timers, Wrong Retry Classification) +- Python: 5 sections (File Organization, Async vs Sync, Wrong Retry Classification, Heartbeating, Testing) + +### observability.md + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Intentionally missing (`—`):** +- Core column: no core observability.md exists (implementation-specific) +- Search Attributes: Python includes as observability concept; TS keeps in data-handling.md + +**Sections marked DEL:** +- OpenTelemetry Integration: Remove from TypeScript (too detailed) +- Debugging with Event History: Remove from TypeScript (too detailed) + +**Order alignment:** ✓ Aligned — TS# monotonically increases (Py# 2 maps to both TS# 2 and 3, but order preserved) + +**Action items:** +- DEL: Remove OpenTelemetry Integration from TypeScript (too detailed) +- DEL: Remove Debugging with Event History from TypeScript (too detailed) + +**Style alignment:** Mostly aligned. After removing both sections, TS will be more concise like Python. + +### testing.md + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Intentionally missing (`—`):** +- Core column: no core testing.md exists (implementation-specific) +- Testing Failure Cases: Python-specific section (adding to TS) +- Activity Testing: Python-specific (ActivityEnvironment) (adding to TS) + +**Sections marked DEL:** +- Time Skipping: Remove dedicated section from TypeScript (mention inline like Python) + +**Order alignment:** ⚠️ NOT ALIGNED — TS# not monotonic +- Testing Signals and Queries: Py#4 → TS#6 +- Replay Testing: Py#6 → TS#5 +- TS has: 1, 2, 3, 4, 6, 5, 7 (jumps 4→6, then back to 5) + +**Action items:** +- TODO: Add Testing Failure Cases section to TypeScript +- TODO: Add Activity Testing section to TypeScript +- Reorder TS sections to match Python order (Signals/Queries before Replay) + +**Style alignment:** Mostly aligned. Python has more sections (Failure Cases, Activity Testing) — adding to TS. + +### versioning.md + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Intentionally missing (`—`):** +- Choosing a Strategy: Python missing (TS + Core have decision table) +- Finding Workflows by Version: Core-only section (languages cover in Query Filters subsections) +- Common Mistakes: Core-only section + +**Order alignment:** ✓ Aligned — all three files follow same order (Overview, Why, Patching, Type Versioning, Worker Versioning, Best Practices) + +**Action items:** +- TODO: Add Choosing a Strategy decision table to Python versioning.md + +**Decided to keep as-is:** +- Common Mistakes: Core-only (conceptual coverage sufficient) + +**Style alignment:** ✓ Well aligned +- Core: Conceptual explanations with decision guidance +- Python/TypeScript: Full code examples matching Core structure +- All three cover the same three approaches consistently + +### {language}.md (top-level files) + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Intentionally missing (`—`):** +- Core column: no core top-level file (these are language entry points) +- Determinism Rules — TS has separate section; Python has subsection in Key Concepts + +**Sections marked DEL:** +- How Temporal Works: History Replay — Remove from TS (reference core/determinism.md instead) + +**Sections marked TODO:** +- File Organization Best Practice — Add to TypeScript + +**Order alignment:** ⚠️ NOT ALIGNED — different structures +- TS has "How Temporal Works" section that Python doesn't have +- TS has separate "Determinism Rules" section; Python has it as Key Concepts subsection +- Python has "File Organization" section that TS doesn't have + +**Action items:** +- **FIX BUG:** TypeScript Additional Resources section has wrong paths (all say `references/python/` instead of `references/typescript/`) +- **TypeScript DEL:** Remove "How Temporal Works: History Replay" section (reference core/determinism.md instead) +- **TypeScript TODO:** Add "File Organization Best Practice" section + +**Detailed gaps in TypeScript Quick Start (vs Python Quick Demo):** +- ⚠️ Missing: "Add Dependency" instruction (`npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity`) +- ⚠️ Missing: File descriptions explaining purpose (e.g., "separate file for performance") +- ⚠️ Missing: "Start the dev server" instruction (`temporal server start-dev`) +- ⚠️ Missing: "Start the worker" instruction (`npx ts-node worker.ts`) +- ⚠️ Missing: starter.ts file showing how to execute a workflow from client +- ⚠️ Missing: "Run the workflow" instruction with expected output + +**Detailed gaps in TypeScript Key Concepts (vs Python):** +- Activity Definition: Python has 5 bullets, TS has 3 bullets + - ⚠️ Missing: `heartbeat()` mention for long operations + - N/A: sync vs async guidance (TS activities are always async) + +**Detailed gaps in TypeScript Common Pitfalls (vs Python):** +- Python has 7 items, TypeScript has 5 items +- ⚠️ Missing: "Forgetting to heartbeat" — important for long-running activities +- ⚠️ Missing: "Using console.log in workflows" — should use `log` from `@temporalio/workflow` for replay-safe logging + +**Style alignment:** ⚠️ TypeScript significantly less comprehensive +- Python Quick Demo: Full tutorial with dependency install, 4 files, run instructions, expected output +- TypeScript Quick Start: Just 3 code blocks with no context or instructions +- Python Key Concepts: More detailed Activity Definition guidance +- Python Common Pitfalls: More items including heartbeat and logging + +### determinism-protection.md + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Intentionally missing (`—`):** +- Core column: no core file (sandbox implementation is language-specific) +- Most sections are language-specific due to different sandbox architectures: + - Python: Pass-through pattern, customization APIs, notification policies + - TypeScript: Import blocking, function replacement (V8-specific) + +**Order alignment:** N/A — files have completely different structures (Python: 12 sections, TS: 3 sections) + +**Action items:** +- None — different sandboxes require different documentation + +**Style alignment:** ⚠️ Very different structures (intentional) +- Python: Comprehensive (12 sections) — complex sandbox with many customization options +- TypeScript: Minimal (3 sections) — V8 sandbox is mostly automatic +- This is appropriate given the different sandbox architectures + +### determinism.md + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Intentionally missing (`—`):** +- Sources of Non-Determinism: Core-only (conceptual categories) +- Central Concept: Activities: Core-only (conceptual) +- Forbidden Operations: Language-specific (Core covers in Sources) +- Safe Builtin Alternatives: Python-only (table format) +- Detecting Non-Determinism: Core-only +- Recovery from Non-Determinism: Core-only +- Testing Replay Compatibility: Language-specific (Core covers in Detecting) + +**Order alignment:** ⚠️ NOT ALIGNED — different structures +- Core#5 (SDK Protection) → Py#6, TS#3 +- Languages have Forbidden Operations (Py#3, TS#4) which Core doesn't have as separate section +- Each file follows own logical structure + +**Action items:** +- None — Safe Builtin Alternatives intentionally Python-only (TS V8 sandbox handles automatically) + +**Style alignment:** ✓ Well aligned +- Core: Deep conceptual content (replay mechanism, commands/events, recovery) +- Python: Practical focus (forbidden operations, safe alternatives table, sandbox) +- TypeScript: Practical focus (V8 sandbox, forbidden operations) +- Good division: Core explains "why", languages explain "how" + +### advanced-features.md + +| Section | Core | Python | Py# | TypeScript | TS# | Go | +|---------|------|--------|-----|------------|-----|-----| +| Schedules | — | ✓ | 1 | ✓ | 5 | | +| Async Activity Completion | — | ✓ | 2 | TODO | | | +| Sandbox Customization | — | ✓ | 3 | — | — | | +| Gevent Compatibility Warning | — | ✓ | 4 | — | — | | +| Worker Tuning | — | ✓ | 5 | TODO | | | +| Workflow Init Decorator | — | ✓ | 6 | — | — | | +| Workflow Failure Exception Types | — | ✓ | 7 | — | — | | +| Continue-as-New | — | — | — | DEL | 1 | | +| Workflow Updates | — | — | — | DEL | 2 | | +| Nexus Operations | — | — | — | DEL | 3 | | +| Activity Cancellation and Heartbeating | — | — | — | DEL | 4 | | +| Sinks | — | — | — | ✓ | 6 | | +| CancellationScope Patterns | — | — | — | DEL | 7 | | +| Best Practices | — | — | — | DEL | 8 | | + +--- + +## Style Alignment: advanced-features.md + +#### Schedules +- **Python:** Code example with create_schedule, manage schedules +- **TypeScript:** Code example with schedule.create, manage schedules +- **Decision:** all good + +--- + +#### Async Activity Completion +- **Python:** Detailed section with task_token pattern, external completion code +- **TypeScript:** N/A (not in advanced-features.md) +- **Decision:** TODO — Add to TypeScript + +--- + +#### Sandbox Customization (Python only) +- **Python:** Brief reference to determinism-protection.md +- **TypeScript:** N/A (has determinism-protection.md directly) +- **Decision:** all good (Python provides helpful cross-reference) + +--- + +#### Gevent Compatibility Warning (Python only) +- **Python:** Warning about gevent incompatibility with workarounds +- **TypeScript:** N/A +- **Decision:** all good (Python-specific concern) + +--- + +#### Worker Tuning +- **Python:** Code example with max_concurrent_*, activity_executor, graceful_shutdown_timeout +- **TypeScript:** N/A (not in advanced-features.md) +- **Decision:** TODO — Add to TypeScript + +--- + +#### Workflow Init Decorator (Python only) +- **Python:** Code example with @workflow.init +- **TypeScript:** N/A +- **Decision:** all good (Python-specific feature) + +--- + +#### Workflow Failure Exception Types (Python only) +- **Python:** Code examples for per-workflow and worker-level configuration +- **TypeScript:** N/A +- **Decision:** all good (Python-specific configuration) + +--- + +#### Continue-as-New (TypeScript - DUPLICATE) +- **Python:** N/A (in patterns.md) +- **TypeScript:** Full code example in advanced-features.md +- **Decision:** DEL — Already in patterns.md (TS#10). Remove from advanced-features.md + +--- + +#### Workflow Updates (TypeScript - DUPLICATE) +- **Python:** N/A (in patterns.md) +- **TypeScript:** Full code example with validators, client calling +- **Decision:** DEL — Already in patterns.md (TS#5). Remove from advanced-features.md + +--- + +#### Nexus Operations (TypeScript only) +- **Python:** N/A +- **TypeScript:** WHY/WHEN + service definition + handlers + workflow calling +- **Decision:** DEL — Remove from TypeScript (too advanced for reference docs) + +--- + +#### Activity Cancellation and Heartbeating (TypeScript only) +- **Python:** N/A (Heartbeat Details in patterns.md) +- **TypeScript:** ActivityCancellationType + Heartbeat Details for resumption +- **Decision:** DEL — Remove from TypeScript (Heartbeat Details already in patterns.md; ActivityCancellationType not needed) + +--- + +#### Sinks (TypeScript only) +- **Python:** N/A +- **TypeScript:** Full example with proxySinks, worker implementation +- **Decision:** all good (TS-specific feature for workflow logging) + +--- + +#### CancellationScope Patterns (TypeScript - DUPLICATE) +- **Python:** N/A (has Cancellation Handling in patterns.md) +- **TypeScript:** nonCancellable + cancellable scope patterns +- **Decision:** DEL — Already in patterns.md as "Cancellation Scopes" (TS#12). Remove from advanced-features.md + +--- + +#### Best Practices (TypeScript only) +- **Python:** N/A +- **TypeScript:** 7 items covering continue-as-new, updates, sinks, cancellation +- **Decision:** DEL — Remove from TypeScript (best practices covered in individual sections) + +--- + +## Summary: advanced-features.md + +**Sections marked TODO:** +- Async Activity Completion: Add to TypeScript +- Worker Tuning: Add to TypeScript + +**Sections needing review (empty cells):** +- Go column: all empty — Go files not yet created + +**Intentionally missing (`—`):** +- Core column: advanced features are implementation-specific +- Sandbox Customization: TS has determinism-protection.md directly +- Gevent Compatibility Warning: Python-specific +- Workflow Init Decorator: Python-specific (@workflow.init) +- Workflow Failure Exception Types: Python-specific +- Continue-as-New, Workflow Updates: Python has in patterns.md (appropriate location) +- Nexus Operations: Removed (too advanced) +- Sinks: TS-specific feature + +**Sections marked DEL (duplicates/remove in TypeScript):** +- Continue-as-New: Already in patterns.md TS#10 +- Workflow Updates: Already in patterns.md TS#5 +- Nexus Operations: Too advanced for reference docs +- CancellationScope Patterns: Already in patterns.md TS#12 as "Cancellation Scopes" +- Activity Cancellation and Heartbeating: Heartbeat Details already in patterns.md; ActivityCancellationType not needed +- Best Practices: Remove (covered in individual sections) + +**Order alignment:** N/A — files have very different structures; TS has many duplicates that should be removed + +**Action items:** +1. **TypeScript DEL:** Remove Continue-as-New, Workflow Updates, CancellationScope Patterns (duplicates from patterns.md) +2. **TypeScript DEL:** Remove Nexus Operations (too advanced) +3. **TypeScript DEL:** Remove Activity Cancellation and Heartbeating (Heartbeat Details in patterns.md; ActivityCancellationType not needed) +4. **TypeScript DEL:** Remove Best Practices (covered in individual sections) +5. **TypeScript TODO:** Add Async Activity Completion +6. **TypeScript TODO:** Add Worker Tuning + +**Style alignment:** After changes: +- Python: 7 sections (Schedules, Async Activity Completion, Sandbox Customization, Gevent Warning, Worker Tuning, Workflow Init, Failure Exception Types) +- TypeScript: 4 sections (Schedules, Async Activity Completion, Worker Tuning, Sinks) +- Both serve as "miscellaneous advanced topics" not covered elsewhere + +--- + +### Other files + +Not yet inventoried. Add sections as files are reviewed. diff --git a/references/typescript/correctness_checking.md b/references/typescript/correctness_checking.md new file mode 100644 index 0000000..a887722 --- /dev/null +++ b/references/typescript/correctness_checking.md @@ -0,0 +1,312 @@ +# TypeScript References Correctness Check + +## Task Prompt (for session recovery) + +**Goal:** Verify correctness of every factual statement and code example in TypeScript reference files. + +**Workflow for each section:** + +1. Read the section from the reference file +2. Query documentation sources to verify: + - Use `mcp__context7__query-docs` with `libraryId: "/temporalio/sdk-typescript"` for SDK-specific API verification + - Use `mcp__temporal-docs__search_temporal_knowledge_sources` for conceptual/pattern verification +3. Compare the code example against official documentation +4. Update this tracking file: + - Update the table row with status and sources consulted + - Update the detailed notes section with verification details and any needed edits +5. If edits are needed, apply them to the source file after documenting here + +**Status values:** +- `unchecked` - Not yet verified +- `all good` - Verified correct, no changes needed +- `FIXED` - Issues found and corrected + +**Resume instructions:** Find the first `unchecked` section in any table and continue from there. + +--- + +## patterns.md + +**File:** `references/typescript/patterns.md` + +### Tracking + +| # | Section | Status | Fix Applied | Sources | +|---|---------|--------|-------------|---------| +| 1 | Signals | all good | | context7 sdk-typescript, temporal-docs | +| 2 | Dynamic Signal Handlers | FIXED | Used `setDefaultSignalHandler` | context7 sdk-typescript, temporal-docs | +| 3 | Queries | all good | | context7 sdk-typescript | +| 4 | Dynamic Query Handlers | FIXED | Used `setDefaultQueryHandler` | temporal-docs | +| 5 | Updates | all good | | context7 sdk-typescript, temporal-docs | +| 6 | Child Workflows | all good | | context7 sdk-typescript, temporal-docs | +| 7 | Child Workflow Options | all good | | context7 sdk-typescript, temporal-docs | +| 8 | Handles to External Workflows | all good | | context7 sdk-typescript | +| 9 | Parallel Execution | all good | | context7 sdk-typescript | +| 10 | Continue-as-New | all good | | context7 sdk-typescript | +| 11 | Saga Pattern | all good | | temporal-docs, samples-typescript | +| 12 | Cancellation Scopes | all good | | context7 sdk-typescript, temporal-docs | +| 13 | Triggers (Promise-like Signals) | all good | | temporal-docs api reference | +| 14 | Wait Condition with Timeout | all good | | context7 sdk-typescript, temporal-docs | +| 15 | Waiting for All Handlers to Finish | FIXED | Simplified to `await condition(allHandlersFinished)` | temporal-docs api reference | +| 16 | Activity Heartbeat Details | all good | | context7 sdk-typescript, temporal-docs | +| 17 | Timers | all good | | context7 sdk-typescript | +| 18 | Local Activities | FIXED | Wrong import: `executeLocalActivity` → `proxyLocalActivities` | context7 sdk-typescript | + +### Detailed Notes + +#### 1. Signals +**Status:** all good + +**Verified:** +- `defineSignal`, `setHandler`, `condition` imports from `@temporalio/workflow` ✓ +- `defineSignal<[boolean]>('approve')` syntax with type parameter as array of arg types ✓ +- `setHandler(signal, handler)` pattern ✓ +- `await condition(() => approved)` for waiting on state ✓ + +--- + +#### 2. Dynamic Signal Handlers +**Status:** FIXED + +**Issue:** The current code uses a non-existent predicate-based `setHandler` API. The TypeScript SDK uses `setDefaultSignalHandler` for handling signals with unknown names. + +**Before:** +```typescript +setHandler( + (signalName: string) => true, // This API doesn't exist + (signalName: string, ...args: unknown[]) => { ... } +); +``` + +**After:** +```typescript +import { setDefaultSignalHandler, condition } from '@temporalio/workflow'; + +export async function dynamicSignalWorkflow(): Promise> { + const signals: Record = {}; + + setDefaultSignalHandler((signalName: string, ...args: unknown[]) => { + if (!signals[signalName]) { + signals[signalName] = []; + } + signals[signalName].push(args); + }); + + await condition(() => signals['done'] !== undefined); + return signals; +} +``` + +**Source:** https://typescript.temporal.io/api/namespaces/workflow#setdefaultsignalhandler + +--- + +#### 3. Queries +**Status:** all good + +**Verified:** +- `defineQuery`, `setHandler` imports from `@temporalio/workflow` ✓ +- `defineQuery('status')` - return type as type parameter ✓ +- `setHandler(query, () => value)` - synchronous handler returning value ✓ +- Query handlers must be synchronous (not async) ✓ + +--- + +#### 4. Dynamic Query Handlers +**Status:** FIXED + +**Issue:** Same as Dynamic Signal Handlers - uses non-existent predicate-based API. + +**Correct API:** `setDefaultQueryHandler` + +```typescript +setDefaultQueryHandler((queryName: string, ...args: any[]) => { + // return value +}); +``` + +**Source:** https://typescript.temporal.io/api/namespaces/workflow#setdefaultqueryhandler + +--- + +#### 5. Updates +**Status:** all good + +**Verified:** +- `defineUpdate('name')` syntax - return type first, then args as tuple type ✓ +- `setHandler(update, handler, { validator })` pattern matches official docs ✓ +- Validator is synchronous, throws error to reject ✓ +- Handler can be sync or async, returns a value ✓ +- Imports `defineUpdate`, `setHandler`, `condition` from `@temporalio/workflow` ✓ + +--- + +#### 6. Child Workflows +**Status:** all good + +**Verified:** +- `executeChild` import from `@temporalio/workflow` ✓ +- `executeChild(workflowFunc, { args, workflowId })` syntax correct ✓ +- Child scheduled on same task queue as parent by default ✓ + +--- + +#### 7. Child Workflow Options +**Status:** all good + +**Verified:** +- `ParentClosePolicy` values: `TERMINATE` (default), `ABANDON`, `REQUEST_CANCEL` ✓ +- `ChildWorkflowCancellationType` values: `WAIT_CANCELLATION_COMPLETED` (default), `WAIT_CANCELLATION_REQUESTED`, `TRY_CANCEL`, `ABANDON` ✓ +- Both imported from `@temporalio/workflow` ✓ + +--- + +#### 8. Handles to External Workflows +**Status:** all good + +**Verified:** +- `getExternalWorkflowHandle(workflowId)` from `@temporalio/workflow` ✓ +- Synchronous function (not async) ✓ +- `handle.signal()` and `handle.cancel()` methods exist ✓ + +--- + +#### 9. Parallel Execution +**Status:** all good + +**Verified:** +- `Promise.all` for parallel execution is standard pattern ✓ +- Used in official examples for parallel child workflows ✓ + +--- + +#### 10. Continue-as-New +**Status:** all good + +**Verified:** +- `continueAsNew`, `workflowInfo` imports from `@temporalio/workflow` ✓ +- `await continueAsNew(args)` syntax correct ✓ +- `workflowInfo().continueAsNewSuggested` property exists ✓ +- Checking history length threshold is standard pattern ✓ + +--- + +#### 11. Saga Pattern +**Status:** all good + +**Verified:** +- Array of compensation functions pattern ✓ +- Try/catch with compensations in reverse order ✓ +- Official samples-typescript/saga uses same pattern ✓ +- No built-in Saga class in TS SDK (unlike Java), manual implementation correct ✓ + +--- + +#### 12. Cancellation Scopes +**Status:** all good + +**Verified:** +- `CancellationScope` from `@temporalio/workflow` ✓ +- `CancellationScope.nonCancellable(fn)` - prevents cancellation propagation ✓ +- `CancellationScope.withTimeout(timeout, fn)` - auto-cancels after timeout ✓ +- `new CancellationScope()` + `scope.run(fn)` + `scope.cancel()` pattern ✓ + +--- + +#### 13. Triggers (Promise-like Signals) +**Status:** all good + +**Verified:** +- `Trigger` class from `@temporalio/workflow` ✓ +- `new Trigger()` creates a PromiseLike that exposes resolve/reject ✓ +- `trigger.resolve(value)` to resolve from signal handler ✓ +- `await trigger` works because Trigger implements PromiseLike ✓ +- CancellationScope-aware (throws when scope cancelled) ✓ + +--- + +#### 14. Wait Condition with Timeout +**Status:** all good + +**Verified:** +- `condition(fn, timeout)` with timeout returns `Promise` ✓ +- Returns `true` if condition met, `false` if timeout expires ✓ +- String duration format `'24 hours'` supported (ms-formatted string) ✓ +- Import of `CancelledFailure` unused in example but harmless ✓ + +--- + +#### 15. Waiting for All Handlers to Finish +**Status:** FIXED + +**Issue:** Current code used overly complex condition with `workflowInfo().unsafe.isReplaying` and was missing import of `allHandlersFinished`. + +**Before:** +```typescript +import { condition, workflowInfo } from '@temporalio/workflow'; +// ... +await condition(() => workflowInfo().unsafe.isReplaying || allHandlersFinished()); +``` + +**After:** +```typescript +import { condition, allHandlersFinished } from '@temporalio/workflow'; +// ... +await condition(allHandlersFinished); +``` + +**Source:** https://typescript.temporal.io/api/namespaces/workflow#allhandlersfinished + +**Notes:** +- `allHandlersFinished` is a function that returns `boolean` +- Pass it directly to `condition()` (not wrapped in a lambda) +- Official pattern: `await wf.condition(wf.allHandlersFinished)` + +--- + +#### 16. Activity Heartbeat Details +**Status:** all good + +**Verified:** +- `heartbeat`, `activityInfo` imports from `@temporalio/activity` ✓ +- `activityInfo().heartbeatDetails` gets heartbeat from previous failed attempt ✓ +- `heartbeat(details)` records checkpoint for resume ✓ +- Pattern matches official samples-typescript/activities-cancellation-heartbeating ✓ + +**Notes:** +- `activityInfo()` is convenience function for `Context.current().info` +- heartbeatDetails can be any serializable value (number, object, etc.) + +--- + +#### 17. Timers +**Status:** all good + +**Verified:** +- `sleep` from `@temporalio/workflow` accepts duration strings ✓ +- `CancellationScope` for cancellable timers ✓ +- `scope.run()` and `scope.cancel()` pattern ✓ + +**Notes:** +- Example uses `setHandler` and `cancelSignal` without imports (pattern demonstration) + +--- + +#### 18. Local Activities +**Status:** FIXED + +**Issue:** Wrong import - code imported `executeLocalActivity` but used `proxyLocalActivities`. + +**Before:** +```typescript +import { executeLocalActivity } from '@temporalio/workflow'; +``` + +**After:** +```typescript +import { proxyLocalActivities } from '@temporalio/workflow'; +``` + +**Source:** context7 sdk-typescript documentation + +--- diff --git a/references/typescript/data-handling.edits.md b/references/typescript/data-handling.edits.md new file mode 100644 index 0000000..3c0777d --- /dev/null +++ b/references/typescript/data-handling.edits.md @@ -0,0 +1,55 @@ +# TypeScript data-handling.md Edits + +## Status: PENDING + +--- + +## Order Changes + +**Issue:** TypeScript section order does not match Python order. + +**Current order (by section):** +1. Overview +2. Default Data Converter +3. Search Attributes (Py#6) +4. Workflow Memo (Py#7) +5. Custom Data Converter (Py#4) +6. Payload Encryption (Py#5) +7. Protobuf Support +8. Large Payloads +9. Best Practices + +**Target order (matching Python):** +1. Overview +2. Default Data Converter +3. Custom Data Converter +4. Payload Encryption +5. Search Attributes +6. Workflow Memo +7. Protobuf Support (TS-specific, keep at end) +8. Large Payloads +9. Best Practices + +**Action:** Reorder sections to match Python order: +- Move "Custom Data Converter" up to position 3 +- Move "Payload Encryption" up to position 4 +- Move "Search Attributes" down to position 5 +- Move "Workflow Memo" down to position 6 + +--- + +## Content to FIX + +None. + +--- + +## Content to DELETE + +None. + +--- + +## Content to ADD + +None. diff --git a/references/typescript/determinism.edits.md b/references/typescript/determinism.edits.md new file mode 100644 index 0000000..424c6ae --- /dev/null +++ b/references/typescript/determinism.edits.md @@ -0,0 +1,53 @@ +# TypeScript determinism.md Edits + +## Status: PENDING + +--- + +## Content to ADD + +### 1. uuid4() Utility + +**Location:** After "Temporal's V8 Sandbox" section, before "Forbidden Operations" + +**Add this section:** +```markdown +## Deterministic UUID Generation + +Generate deterministic UUIDs safe to use in workflows. Uses the workflow seeded PRNG, so the same UUID is generated during replay. + +```typescript +import { uuid4 } from '@temporalio/workflow'; + +export async function workflowWithIds(): Promise { + const childWorkflowId = uuid4(); + await executeChild(childWorkflow, { + workflowId: childWorkflowId, + args: [input], + }); +} +``` + +**When to use:** +- Generating unique IDs for child workflows +- Creating idempotency keys +- Any situation requiring unique identifiers in workflow code +``` + +--- + +## Content to DELETE + +None. + +--- + +## Content to FIX + +None. + +--- + +## Order Changes + +None - order is aligned. diff --git a/references/typescript/error-handling.edits.md b/references/typescript/error-handling.edits.md new file mode 100644 index 0000000..8dbadf0 --- /dev/null +++ b/references/typescript/error-handling.edits.md @@ -0,0 +1,113 @@ +# TypeScript error-handling.md Edits + +## Status: PENDING + +--- + +## BUGS to FIX + +### 1. Handling Activity Errors - Replace console.log with workflow logger + +**Location:** Inside the catch block of the activity error handling example + +**Current code:** +```typescript +} catch (err) { + console.log('Activity failed', err); + // ... +} +``` + +**Change to:** +```typescript +import { log } from '@temporalio/workflow'; + +// ... in catch block: +} catch (err) { + log.warn('Activity failed', { error: err }); + // ... +} +``` + +**WHY:** `console.log` is not replay-safe. Use `log` from `@temporalio/workflow` for replay-aware logging in workflows. + +--- + +## Content to FIX + +### 2. Retry Configuration - Add note about preferring defaults + +**Location:** After the Retry Configuration code example + +**Add this note:** +```markdown +**Note:** Only set retry options if you have a domain-specific reason to. The defaults are suitable for most use cases. +``` + +--- + +## Content to DELETE + +### 3. Cancellation Handling in Activities + +**Location:** Entire "Cancellation Handling in Activities" section + +**Action:** DELETE this entire section. + +**Reason:** Move to patterns.md. Python already has Cancellation Handling in patterns.md. Content should be consolidated there. + +**Note:** When adding to patterns.md, it should complement the existing "Cancellation Scopes" section. + +--- + +### 4. Idempotency Patterns + +**Location:** Entire "Idempotency Patterns" section (includes WHY, Using Keys, Granular Activities subsections) + +**Action:** DELETE this entire section. + +**Reason:** Too detailed for error-handling.md. Replace with brief reference to core/patterns.md like Python does. + +**Replace with:** +```markdown +## Idempotency + +For idempotency patterns (using keys, making activities granular), see `core/patterns.md`. +``` + +--- + +## Content to ADD + +### 5. Workflow Failure section + +**Location:** After "Timeout Configuration" section, before "Best Practices" + +**Add this section:** +```markdown +## Workflow Failure + +Workflows can throw errors to indicate failure: + +```typescript +import { ApplicationFailure } from '@temporalio/workflow'; + +export async function myWorkflow(): Promise { + if (someCondition) { + throw ApplicationFailure.create({ + message: 'Workflow failed due to invalid state', + type: 'InvalidStateError', + }); + } + return 'success'; +} +``` + +**Warning:** Do NOT use `nonRetryable: true` for workflow failures in most cases. Unlike activities, workflow retries are controlled by the caller, not retry policies. Use `nonRetryable` only for errors that are truly unrecoverable (e.g., invalid input that will never be valid). +``` + +--- + +## Order Changes + +None - order is aligned after deletions. diff --git a/references/typescript/gotchas.edits.md b/references/typescript/gotchas.edits.md new file mode 100644 index 0000000..805c629 --- /dev/null +++ b/references/typescript/gotchas.edits.md @@ -0,0 +1,105 @@ +# TypeScript gotchas.md Edits + +## Status: PENDING + +--- + +## Content to DELETE + +### 1. Idempotency section + +**Location:** Entire "Idempotency" section with BAD/GOOD code example + +**Action:** DELETE this entire section. + +**Reason:** Core coverage in core/gotchas.md is sufficient. No need to duplicate. + +--- + +### 2. Replay Safety section + +**Location:** Entire "Replay Safety" section (includes "Side Effects" and "Non-Deterministic Operations" subsections) + +**Action:** DELETE this entire section. + +**Reason:** Core coverage in core/gotchas.md is sufficient. No need to duplicate. + +--- + +### 3. Query Handlers section + +**Location:** Entire "Query Handlers" section (includes "Modifying State" and "Blocking in Queries" subsections) + +**Action:** DELETE this entire section. + +**Reason:** Core coverage in core/gotchas.md is sufficient. No need to duplicate. + +--- + +### 4. Error Handling section + +**Location:** Entire "Error Handling" section + +**Action:** DELETE this entire section. + +**Reason:** Core coverage in core/gotchas.md is sufficient. TypeScript-specific error handling is covered in error-handling.md. + +--- + +### 5. Retry Policies section + +**Location:** Entire "Retry Policies" section (includes "Too Aggressive" subsection) + +**Action:** DELETE this entire section. + +**Reason:** Core coverage in core/gotchas.md is sufficient. No need to duplicate. + +--- + +## Content to ADD + +### 6. Wrong Retry Classification section + +**Location:** After existing sections, before Testing section + +**Add this section:** +```markdown +## Wrong Retry Classification + +A common mistake is treating transient errors as permanent (or vice versa): + +- **Transient errors** (retry): network timeouts, temporary service unavailability, rate limits +- **Permanent errors** (don't retry): invalid input, authentication failure, resource not found + +```typescript +// BAD: Retrying a permanent error +throw ApplicationFailure.create({ message: 'User not found' }); +// This will retry indefinitely! + +// GOOD: Mark permanent errors as non-retryable +throw ApplicationFailure.nonRetryable('User not found'); +``` + +For detailed guidance on error classification and retry policies, see `error-handling.md`. +``` + +--- + +## Content to FIX + +None. + +--- + +## Order Changes + +After deletions, the remaining sections should be: +1. Activity Imports (TS-specific) +2. Bundling Issues (TS-specific) +3. Wrong Retry Classification (new) +4. Cancellation (TS-specific) +5. Heartbeating +6. Testing +7. Timers and Sleep (TS-specific) + +This provides a focused TypeScript-specific gotchas file that complements (rather than duplicates) the Core gotchas. diff --git a/references/typescript/observability.edits.md b/references/typescript/observability.edits.md new file mode 100644 index 0000000..a0f8a48 --- /dev/null +++ b/references/typescript/observability.edits.md @@ -0,0 +1,50 @@ +# TypeScript observability.md Edits + +## Status: PENDING + +--- + +## Content to DELETE + +### 1. OpenTelemetry Integration section + +**Location:** Entire "OpenTelemetry Integration" section (includes "Setup" and "Worker Configuration" subsections) + +**Action:** DELETE this entire section. + +**Reason:** Too detailed for reference docs. Users needing OpenTelemetry should consult the official Temporal documentation. + +--- + +### 2. Debugging with Event History section + +**Location:** Entire "Debugging with Event History" section (includes "Viewing Event History", "Key Events" table, "Debugging Non-Determinism" subsections) + +**Action:** DELETE this entire section. + +**Reason:** Too detailed for reference docs. This is operational knowledge better suited for official documentation or tutorials. + +--- + +## Content to FIX + +None. + +--- + +## Content to ADD + +None. + +--- + +## Order Changes + +After deletions, the remaining sections should be: +1. Overview +2. Replay-Aware Logging +3. Customizing the Logger +4. Metrics +5. Best Practices + +This provides a focused observability reference matching Python's structure. diff --git a/references/typescript/patterns.edits.md b/references/typescript/patterns.edits.md new file mode 100644 index 0000000..2504007 --- /dev/null +++ b/references/typescript/patterns.edits.md @@ -0,0 +1,93 @@ +# TypeScript patterns.md Edits + +## Status: PENDING + +--- + +## Content to FIX + +### 1. Queries - Add "Important" note + +**Location:** After the `## Queries` header, before the code block + +**Add this note:** +```markdown +**Important:** Queries must NOT modify workflow state or have side effects. +``` + +--- + +### 2. Saga Pattern - Add idempotency note + +**Location:** After the `## Saga Pattern` header, before the code block + +**Add this note:** +```markdown +**Important:** Compensation activities should be idempotent. +``` + +--- + +### 3. Saga Pattern - Add compensation comments + +**Location:** Inside the saga workflow code example + +**Current code (simplified):** +```typescript +await reserveInventory(order); +compensations.push(() => releaseInventory(order)); +``` + +**Change to:** +```typescript +// IMPORTANT: Save compensation BEFORE calling the activity +// If activity fails after completing but before returning, +// compensation must still be registered +await reserveInventory(order); +compensations.push(() => releaseInventory(order)); +``` + +--- + +## BUGS to FIX + +### 4. Saga Pattern - Replace console.log with workflow logger + +**Location:** Inside the catch block of the saga workflow + +**Current code:** +```typescript +} catch (compErr) { + console.log('Compensation failed', compErr); +} +``` + +**Change to:** +```typescript +import { log } from '@temporalio/workflow'; + +// ... in catch block: +} catch (compErr) { + log.warn('Compensation failed', { error: compErr }); +} +``` + +**WHY:** `console.log` is not replay-safe. Use `log` from `@temporalio/workflow` for replay-aware logging in workflows. + +--- + +## Content to DELETE + +None. + +--- + +## Content to ADD + +None. + +--- + +## Order Changes + +None - order is already aligned. diff --git a/references/typescript/patterns.md b/references/typescript/patterns.md index 760f112..3e36c13 100644 --- a/references/typescript/patterns.md +++ b/references/typescript/patterns.md @@ -30,20 +30,17 @@ export async function orderWorkflow(): Promise { For handling signals with names not known at compile time: ```typescript -import { setHandler, condition } from '@temporalio/workflow'; +import { setDefaultSignalHandler, condition } from '@temporalio/workflow'; export async function dynamicSignalWorkflow(): Promise> { const signals: Record = {}; - setHandler( - (signalName: string) => true, // Accept all signal names - (signalName: string, ...args: unknown[]) => { - if (!signals[signalName]) { - signals[signalName] = []; - } - signals[signalName].push(args); + setDefaultSignalHandler((signalName: string, ...args: unknown[]) => { + if (!signals[signalName]) { + signals[signalName] = []; } - ); + signals[signalName].push(args); + }); await condition(() => signals['done'] !== undefined); return signals; @@ -78,7 +75,7 @@ export async function progressWorkflow(): Promise { For handling queries with names not known at compile time: ```typescript -import { setHandler } from '@temporalio/workflow'; +import { setDefaultQueryHandler } from '@temporalio/workflow'; export async function dynamicQueryWorkflow(): Promise { const state: Record = { @@ -86,12 +83,9 @@ export async function dynamicQueryWorkflow(): Promise { progress: 0, }; - setHandler( - (queryName: string) => true, // Accept all query names - (queryName: string) => { - return state[queryName]; - } - ); + setDefaultQueryHandler((queryName: string) => { + return state[queryName]; + }); // ... workflow logic } @@ -330,13 +324,13 @@ export async function approvalWorkflow(): Promise { - **Before continue-as-new** - Ensure handlers complete before resetting ```typescript -import { condition, workflowInfo } from '@temporalio/workflow'; +import { condition, allHandlersFinished } from '@temporalio/workflow'; export async function handlerAwareWorkflow(): Promise { // ... main workflow logic ... // Before exiting, wait for all handlers to finish - await condition(() => workflowInfo().unsafe.isReplaying || allHandlersFinished()); + await condition(allHandlersFinished); return 'done'; } ``` @@ -397,7 +391,7 @@ export async function timerWorkflow(): Promise { **Purpose**: Reduce latency for short, lightweight operations by skipping the task queue. ONLY use these when necessary for performance. Do NOT use these by default, as they are not durable and distributed. ```typescript -import { executeLocalActivity } from '@temporalio/workflow'; +import { proxyLocalActivities } from '@temporalio/workflow'; import type * as activities from './activities'; const { quickLookup } = proxyLocalActivities({ diff --git a/references/typescript/testing.edits.md b/references/typescript/testing.edits.md new file mode 100644 index 0000000..b4fe6b3 --- /dev/null +++ b/references/typescript/testing.edits.md @@ -0,0 +1,151 @@ +# TypeScript testing.md Edits + +## Status: PENDING + +--- + +## Content to DELETE + +### 1. Time Skipping section (as dedicated section) + +**Location:** Entire "Time Skipping" section + +**Action:** DELETE as a dedicated section. + +**Reason:** Python mentions time skipping inline in the Test Environment Setup section. TypeScript should do the same for consistency. + +**Alternative:** Add a brief mention in Test Environment Setup: +```markdown +The test environment automatically skips time when the workflow is waiting on timers, making tests fast. +``` + +--- + +## Content to ADD + +### 2. Testing Failure Cases section + +**Location:** After "Activity Mocking" section, before "Replay Testing" + +**Add this section:** +```markdown +## Testing Failure Cases + +Test that workflows handle errors correctly: + +```typescript +import { TestWorkflowEnvironment } from '@temporalio/testing'; +import { Worker } from '@temporalio/worker'; +import assert from 'assert'; + +describe('Failure handling', () => { + let testEnv: TestWorkflowEnvironment; + + before(async () => { + testEnv = await TestWorkflowEnvironment.createLocal(); + }); + + after(async () => { + await testEnv?.teardown(); + }); + + it('handles activity failure', async () => { + const { client, nativeConnection } = testEnv; + + const worker = await Worker.create({ + connection: nativeConnection, + taskQueue: 'test', + workflowsPath: require.resolve('./workflows'), + activities: { + // Mock activity that always fails + myActivity: async () => { + throw new Error('Activity failed'); + }, + }, + }); + + await worker.runUntil(async () => { + try { + await client.workflow.execute(myWorkflow, { + workflowId: 'test-failure', + taskQueue: 'test', + }); + assert.fail('Expected workflow to fail'); + } catch (err) { + assert(err instanceof WorkflowFailedError); + } + }); + }); +}); +``` +``` + +--- + +### 3. Activity Testing section + +**Location:** After "Replay Testing" section, before "Best Practices" + +**Add this section:** +```markdown +## Activity Testing + +Test activities in isolation without running a workflow: + +```typescript +import { MockActivityEnvironment } from '@temporalio/testing'; +import { myActivity } from './activities'; +import assert from 'assert'; + +describe('Activity tests', () => { + it('completes successfully', async () => { + const env = new MockActivityEnvironment(); + const result = await env.run(myActivity, 'input'); + assert.equal(result, 'expected output'); + }); + + it('handles cancellation', async () => { + const env = new MockActivityEnvironment({ cancelled: true }); + try { + await env.run(longRunningActivity, 'input'); + assert.fail('Expected cancellation'); + } catch (err) { + assert(err instanceof CancelledFailure); + } + }); +}); +``` + +**Note:** `MockActivityEnvironment` provides `heartbeat()` and cancellation support for testing activity behavior. +``` + +--- + +## Order Changes + +**Current order:** +1. Overview +2. Test Environment Setup +3. Time Skipping +4. Activity Mocking +5. Testing Signals and Queries (TS#6) +6. Replay Testing (TS#5) +7. Best Practices + +**Target order (matching Python, after edits):** +1. Overview +2. Test Environment Setup (with inline time skipping mention) +3. Activity Mocking +4. Testing Signals and Queries +5. Testing Failure Cases (new) +6. Replay Testing +7. Activity Testing (new) +8. Best Practices + +**Action:** Reorder "Testing Signals and Queries" to come before "Replay Testing" (matching Python order). + +--- + +## Content to FIX + +None. diff --git a/references/typescript/typescript.edits.md b/references/typescript/typescript.edits.md new file mode 100644 index 0000000..b8dd433 --- /dev/null +++ b/references/typescript/typescript.edits.md @@ -0,0 +1,204 @@ +# TypeScript typescript.md (top-level) Edits + +## Status: PENDING + +--- + +## BUGS to FIX + +### 1. Additional Resources - Wrong file paths + +**Location:** "Additional Resources" section at the end of the file + +**Current paths (WRONG):** +```markdown +- **`references/python/patterns.md`** - ... +- **`references/python/determinism.md`** - ... +- **`references/python/gotchas.md`** - ... +- **`references/python/error-handling.md`** - ... +- **`references/python/observability.md`** - ... +- **`references/python/testing.md`** - ... +- **`references/python/advanced-features.md`** - ... +- **`references/python/data-handling.md`** - ... +- **`references/python/versioning.md`** - ... +- **`references/python/determinism-protection.md`** - ... +``` + +**Change to (CORRECT):** +```markdown +- **`references/typescript/patterns.md`** - Signals, queries, child workflows, saga pattern, etc. +- **`references/typescript/determinism.md`** - Essentials of determinism in TypeScript +- **`references/typescript/gotchas.md`** - TypeScript-specific mistakes and anti-patterns +- **`references/typescript/error-handling.md`** - ApplicationFailure, retry policies, non-retryable errors +- **`references/typescript/observability.md`** - Logging, metrics, tracing +- **`references/typescript/testing.md`** - TestWorkflowEnvironment, time-skipping, activity mocking +- **`references/typescript/advanced-features.md`** - Schedules, worker tuning, and more +- **`references/typescript/data-handling.md`** - Data converters, payload encryption, etc. +- **`references/typescript/versioning.md`** - Patching API, workflow type versioning, Worker Versioning +- **`references/typescript/determinism-protection.md`** - V8 sandbox and bundling +``` + +--- + +## Content to DELETE + +### 2. How Temporal Works: History Replay section + +**Location:** Entire "How Temporal Works: History Replay" section (includes subsections: The Replay Mechanism, Commands and Events table, When Replay Occurs) + +**Action:** DELETE this entire section. + +**Reason:** This content belongs in core/determinism.md, not in the language-specific top-level file. Both Python and TypeScript should reference the Core conceptual content. + +**Replace with a brief reference (optional):** +```markdown +## Understanding Replay + +Temporal workflows are durable through history replay. For details on how this works, see `core/determinism.md`. +``` + +--- + +## Content to ADD + +### 3. File Organization Best Practice section + +**Location:** After "Key Concepts" section, before "Determinism Rules" + +**Add this section:** +```markdown +## File Organization Best Practice + +**Keep Workflow definitions in separate files from Activity definitions.** The TypeScript SDK bundles workflow files separately. Minimizing workflow file contents improves Worker startup time. + +``` +my_temporal_app/ +├── workflows/ +│ └── greeting.ts # Only Workflow functions +├── activities/ +│ └── translate.ts # Only Activity functions +├── worker.ts # Worker setup, imports both +└── client.ts # Client code to start workflows +``` + +**In the Workflow file, use type-only imports for activities:** +```typescript +// workflows/greeting.ts +import { proxyActivities } from '@temporalio/workflow'; +import type * as activities from '../activities/translate'; + +const { translate } = proxyActivities({ + startToCloseTimeout: '1 minute', +}); +``` +``` + +--- + +### 4. Expand Quick Start section + +**Location:** "Quick Start" section + +**Current state:** Just 3 code blocks with minimal context. + +**Target state (matching Python "Quick Demo"):** + +```markdown +## Quick Start + +**Add Dependencies:** Install the Temporal SDK packages: +```bash +npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity +``` + +**activities.ts** - Activity definitions (separate file for bundling performance): +```typescript +export async function greet(name: string): Promise { + return `Hello, ${name}!`; +} +``` + +**workflows.ts** - Workflow definition (use type-only imports for activities): +```typescript +import { proxyActivities } from '@temporalio/workflow'; +import type * as activities from './activities'; + +const { greet } = proxyActivities({ + startToCloseTimeout: '1 minute', +}); + +export async function greetingWorkflow(name: string): Promise { + return await greet(name); +} +``` + +**worker.ts** - Worker setup (imports activities and workflows, runs indefinitely): +```typescript +import { Worker } from '@temporalio/worker'; +import * as activities from './activities'; + +async function run() { + const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + activities, + taskQueue: 'greeting-queue', + }); + await worker.run(); +} + +run().catch(console.error); +``` + +**Start the dev server:** Start `temporal server start-dev` in the background. + +**Start the worker:** Run `npx ts-node worker.ts` in the background. + +**client.ts** - Start a workflow execution: +```typescript +import { Client } from '@temporalio/client'; +import { greetingWorkflow } from './workflows'; +import { v4 as uuid } from 'uuid'; + +async function run() { + const client = new Client(); + + const result = await client.workflow.execute(greetingWorkflow, { + workflowId: uuid(), + taskQueue: 'greeting-queue', + args: ['my name'], + }); + + console.log(`Result: ${result}`); +} + +run().catch(console.error); +``` + +**Run the workflow:** Run `npx ts-node client.ts`. Should output: `Result: Hello, my name!`. +``` + +--- + +### 5. Common Pitfalls - Add missing items + +**Location:** "Common Pitfalls" section + +**Add these items:** +```markdown +6. **Forgetting to heartbeat** - Long-running activities need `heartbeat()` calls +7. **Using console.log in workflows** - Use `log` from `@temporalio/workflow` for replay-safe logging +``` + +--- + +## Order Changes + +After edits, the order should be: +1. Overview +2. Quick Start (expanded) +3. Key Concepts +4. File Organization Best Practice (new) +5. Determinism Rules +6. Common Pitfalls (expanded) +7. Writing Tests +8. Additional Resources (fixed paths) From 41556eddffa5a0abcd82ef547c59e13dd37dc701 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 25 Feb 2026 18:51:28 -0500 Subject: [PATCH 07/50] bulk apply edits --- references/python/gotchas.edits.md | 2 +- references/python/versioning.edits.md | 2 +- references/python/versioning.md | 15 + .../typescript/advanced-features.edits.md | 2 +- references/typescript/advanced-features.md | 355 ++++-------------- references/typescript/data-handling.edits.md | 2 +- references/typescript/data-handling.md | 190 +++++----- references/typescript/determinism.edits.md | 2 +- references/typescript/determinism.md | 21 ++ references/typescript/editing_prompt.md | 21 ++ references/typescript/error-handling.edits.md | 2 +- references/typescript/error-handling.md | 106 ++---- references/typescript/gotchas.edits.md | 2 +- references/typescript/gotchas.md | 183 +-------- references/typescript/observability.edits.md | 2 +- references/typescript/observability.md | 108 ------ references/typescript/patterns.edits.md | 2 +- references/typescript/patterns.md | 11 +- references/typescript/testing.edits.md | 2 +- references/typescript/testing.md | 133 +++++-- references/typescript/typescript.edits.md | 2 +- references/typescript/typescript.md | 119 ++++-- 22 files changed, 451 insertions(+), 833 deletions(-) create mode 100644 references/typescript/editing_prompt.md diff --git a/references/python/gotchas.edits.md b/references/python/gotchas.edits.md index 856a457..33cd1c0 100644 --- a/references/python/gotchas.edits.md +++ b/references/python/gotchas.edits.md @@ -1,6 +1,6 @@ # Python gotchas.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/python/versioning.edits.md b/references/python/versioning.edits.md index 3d8f867..73c8d06 100644 --- a/references/python/versioning.edits.md +++ b/references/python/versioning.edits.md @@ -1,6 +1,6 @@ # Python versioning.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/python/versioning.md b/references/python/versioning.md index 029eaa5..91f6406 100644 --- a/references/python/versioning.md +++ b/references/python/versioning.md @@ -297,6 +297,21 @@ temporal workflow list --query \ 'TemporalWorkerDeploymentVersion = "my-service:v1.0.0" AND ExecutionStatus = "Running"' ``` +## Choosing a Strategy + +| Scenario | Recommended Approach | +|----------|---------------------| +| Minor bug fix, compatible change | Patching API (`patched()`) | +| Major logic change, incompatible | Workflow Type Versioning (new workflow name) | +| Infrastructure change, gradual rollout | Worker Versioning (Build ID) | +| Need to query/signal old workflows | Patching (keeps same workflow type) | +| Clean break, no backward compatibility | Workflow Type Versioning | + +**Decision factors:** +- **Patching API**: Best for incremental changes where you need to maintain the same workflow type and can gradually migrate +- **Workflow Type Versioning**: Best for major changes where a clean break is acceptable +- **Worker Versioning**: Best for infrastructure-level changes or when you need fine-grained deployment control + ## Best Practices 1. **Check for open executions** before removing old code paths diff --git a/references/typescript/advanced-features.edits.md b/references/typescript/advanced-features.edits.md index 07ee3e2..8e53696 100644 --- a/references/typescript/advanced-features.edits.md +++ b/references/typescript/advanced-features.edits.md @@ -1,6 +1,6 @@ # TypeScript advanced-features.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/typescript/advanced-features.md b/references/typescript/advanced-features.md index 188d692..12df0c4 100644 --- a/references/typescript/advanced-features.md +++ b/references/typescript/advanced-features.md @@ -1,277 +1,109 @@ # TypeScript SDK Advanced Features -## Continue-as-New - -Use continue-as-new to prevent unbounded history growth in long-running workflows. - -```typescript -import { continueAsNew, workflowInfo } from '@temporalio/workflow'; - -export async function batchProcessingWorkflow(state: ProcessingState): Promise { - while (!state.isComplete) { - // Process next batch - state = await processNextBatch(state); - - // Check history size and continue-as-new if needed - const info = workflowInfo(); - if (info.historyLength > 10000) { - await continueAsNew(state); - } - } - - return 'completed'; -} -``` - -### Continue-as-New with Options - -```typescript -import { continueAsNew } from '@temporalio/workflow'; - -// Continue with modified options -await continueAsNew(newState, { - memo: { lastProcessed: itemId }, - searchAttributes: { BatchNumber: [state.batch + 1] }, -}); -``` - -## Workflow Updates - -Updates allow synchronous interaction with running workflows. - -### Defining Update Handlers - -```typescript -import { defineUpdate, setHandler, condition } from '@temporalio/workflow'; - -// Define the update -export const addItemUpdate = defineUpdate('addItem'); -export const addItemValidatedUpdate = defineUpdate('addItemValidated'); - -export async function orderWorkflow(): Promise { - const items: string[] = []; - let completed = false; - - // Simple update handler - setHandler(addItemUpdate, (item: string) => { - items.push(item); - return items.length; - }); - - // Update handler with validator - setHandler( - addItemValidatedUpdate, - (item: string) => { - items.push(item); - return items.length; - }, - { - validator: (item: string) => { - if (!item) throw new Error('Item cannot be empty'); - if (items.length >= 100) throw new Error('Order is full'); - }, - } - ); - - // Wait for completion signal - await condition(() => completed); - return `Order with ${items.length} items completed`; -} -``` +## Schedules -### Calling Updates from Client +Create recurring workflow executions. ```typescript -import { Client } from '@temporalio/client'; -import { addItemUpdate } from './workflows'; +import { Client, ScheduleOverlapPolicy } from '@temporalio/client'; const client = new Client(); -const handle = client.workflow.getHandle('order-123'); - -// Execute update and wait for result -const count = await handle.executeUpdate(addItemUpdate, { args: ['new-item'] }); -console.log(`Order now has ${count} items`); -``` - -## Nexus Operations - -### WHY: Cross-namespace and cross-cluster service communication -### WHEN: -- **Multi-namespace architectures** - Call operations across Temporal namespaces -- **Service-oriented design** - Expose workflow capabilities as reusable services -- **Cross-cluster communication** - Interact with workflows in different Temporal clusters - -### Defining a Nexus Service -Define the service interface shared between caller and handler: - -```typescript -// api.ts - shared service definition -import * as nexus from 'nexus-rpc'; - -export const helloService = nexus.service('hello', { - // Synchronous operation - echo: nexus.operation(), - // Workflow-backed operation - hello: nexus.operation(), +// Create a schedule +const schedule = await client.schedule.create({ + scheduleId: 'daily-report', + spec: { + intervals: [{ every: '1 day' }], + }, + action: { + type: 'startWorkflow', + workflowType: 'dailyReportWorkflow', + taskQueue: 'reports', + args: [], + }, + policies: { + overlap: ScheduleOverlapPolicy.SKIP, + }, }); -export interface EchoInput { message: string; } -export interface EchoOutput { message: string; } -export interface HelloInput { name: string; language: string; } -export interface HelloOutput { message: string; } +// Manage schedules +const handle = client.schedule.getHandle('daily-report'); +await handle.pause('Maintenance window'); +await handle.unpause(); +await handle.trigger(); // Run immediately +await handle.delete(); ``` -### Implementing Nexus Service Handlers +## Async Activity Completion -```typescript -// service/handler.ts -import * as nexus from 'nexus-rpc'; -import * as temporalNexus from '@temporalio/nexus'; -import { helloService, EchoInput, EchoOutput, HelloInput, HelloOutput } from '../api'; -import { helloWorkflow } from './workflows'; - -export const helloServiceHandler = nexus.serviceHandler(helloService, { - // Synchronous operation - simple async function - echo: async (ctx, input: EchoInput): Promise => { - // Can access Temporal client via temporalNexus.getClient() - return input; - }, - - // Workflow-backed operation - hello: new temporalNexus.WorkflowRunOperationHandler( - async (ctx, input: HelloInput) => { - return await temporalNexus.startWorkflow(ctx, helloWorkflow, { - args: [input], - workflowId: ctx.requestId ?? crypto.randomUUID(), - }); - }, - ), -}); -``` - -### Calling Nexus Operations from Workflows +Complete an activity asynchronously from outside the activity function. Useful when the activity needs to wait for an external event. +**In the activity - return the task token:** ```typescript -// caller/workflows.ts -import * as wf from '@temporalio/workflow'; -import { helloService } from '../api'; - -const HELLO_SERVICE_ENDPOINT = 'my-nexus-endpoint-name'; +import { CompleteAsyncError, activityInfo } from '@temporalio/activity'; -export async function callerWorkflow(name: string): Promise { - const nexusClient = wf.createNexusClient({ - service: helloService, - endpoint: HELLO_SERVICE_ENDPOINT, - }); +export async function asyncActivity(): Promise { + const taskToken = activityInfo().taskToken; - const result = await nexusClient.executeOperation( - 'hello', - { name, language: 'en' }, - { scheduleToCloseTimeout: '10s' }, - ); + // Store taskToken somewhere (database, queue, etc.) + await saveTaskToken(taskToken); - return result.message; + // Throw to indicate async completion + throw new CompleteAsyncError(); } ``` -## Activity Cancellation and Heartbeating - -### ActivityCancellationType - -Control how activities respond to workflow cancellation: - +**External completion (from another process):** ```typescript -import { proxyActivities, ActivityCancellationType, isCancellation, log } from '@temporalio/workflow'; -import type * as activities from './activities'; - -const { longRunningActivity } = proxyActivities({ - startToCloseTimeout: '60s', - heartbeatTimeout: '3s', - // TRY_CANCEL (default): Request cancellation, resolve/reject immediately - // WAIT_CANCELLATION_COMPLETED: Wait for activity to acknowledge cancellation - // WAIT_CANCELLATION_REQUESTED: Wait for cancellation request to be delivered - // ABANDON: Don't request cancellation - cancellationType: ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, -}); +import { AsyncCompletionClient } from '@temporalio/client'; -export async function workflowWithCancellation(): Promise { - try { - await longRunningActivity(); - } catch (err) { - if (isCancellation(err)) { - log.info('Workflow cancelled along with its activity'); - // Use CancellationScope.nonCancellable for cleanup - } - throw err; - } +async function completeActivity(taskToken: Uint8Array, result: string) { + const client = new AsyncCompletionClient(); + + await client.complete(taskToken, result); + // Or for failure: + // await client.fail(taskToken, new Error('Failed')); } ``` -### Activity Heartbeat Details for Resumption +**When to use:** +- Waiting for human approval +- Waiting for external webhook callback +- Long-polling external systems + +## Worker Tuning -Use heartbeat details to resume long-running activities from where they left off: +Configure worker capacity for production workloads: ```typescript -// activities.ts -import { activityInfo, log, sleep, CancelledFailure, heartbeat } from '@temporalio/activity'; - -export async function processWithProgress(sleepIntervalMs = 1000): Promise { - try { - // Resume from last heartbeat on retry - const startingPoint = activityInfo().heartbeatDetails || 1; - log.info('Starting activity at progress', { startingPoint }); - - for (let progress = startingPoint; progress <= 100; ++progress) { - log.info('Progress', { progress }); - await sleep(sleepIntervalMs); - // Heartbeat with progress - allows resuming on retry - heartbeat(progress); - } - } catch (err) { - if (err instanceof CancelledFailure) { - log.warn('Activity cancelled', { message: err.message }); - // Cleanup code here - } - throw err; - } -} -``` +import { Worker, NativeConnection } from '@temporalio/worker'; -## Schedules +const worker = await Worker.create({ + connection: await NativeConnection.connect({ address: 'temporal:7233' }), + taskQueue: 'my-queue', + workflowsPath: require.resolve('./workflows'), + activities, -Create recurring workflow executions. + // Workflow execution concurrency (default: 40) + maxConcurrentWorkflowTaskExecutions: 100, -```typescript -import { Client, ScheduleOverlapPolicy } from '@temporalio/client'; + // Activity execution concurrency (default: 100) + maxConcurrentActivityTaskExecutions: 100, -const client = new Client(); + // Graceful shutdown timeout (default: 0) + shutdownGraceTime: '30 seconds', -// Create a schedule -const schedule = await client.schedule.create({ - scheduleId: 'daily-report', - spec: { - intervals: [{ every: '1 day' }], - }, - action: { - type: 'startWorkflow', - workflowType: 'dailyReportWorkflow', - taskQueue: 'reports', - args: [], - }, - policies: { - overlap: ScheduleOverlapPolicy.SKIP, - }, + // Max cached workflows (memory vs latency tradeoff) + maxCachedWorkflows: 1000, }); - -// Manage schedules -const handle = client.schedule.getHandle('daily-report'); -await handle.pause('Maintenance window'); -await handle.unpause(); -await handle.trigger(); // Run immediately -await handle.delete(); ``` +**Key settings:** +- `maxConcurrentWorkflowTaskExecutions`: Max workflows running simultaneously (default: 40) +- `maxConcurrentActivityTaskExecutions`: Max activities running simultaneously (default: 100) +- `shutdownGraceTime`: Time to wait for in-progress work before forced shutdown +- `maxCachedWorkflows`: Number of workflows to keep in cache (reduces replay on cache hit) + ## Sinks Sinks allow workflows to emit events for side effects (logging, metrics). @@ -322,52 +154,3 @@ const worker = await Worker.create({ }, }); ``` - -## CancellationScope Patterns - -Advanced cancellation control within workflows. - -```typescript -import { - CancellationScope, - CancelledFailure, - sleep, -} from '@temporalio/workflow'; - -export async function workflowWithCancellation(): Promise { - // Non-cancellable scope - runs to completion even if workflow cancelled - const criticalResult = await CancellationScope.nonCancellable(async () => { - return await criticalActivity(); - }); - - // Cancellable scope with timeout - try { - await CancellationScope.cancellable(async () => { - await Promise.race([ - longRunningActivity(), - sleep('5 minutes').then(() => { - CancellationScope.current().cancel(); - }), - ]); - }); - } catch (err) { - if (err instanceof CancelledFailure) { - // Handle cancellation - await cleanupActivity(); - } - throw err; - } - - return criticalResult; -} -``` - -## Best Practices - -1. Use continue-as-new for long-running workflows to prevent history growth -2. Prefer updates over signals when you need a response -3. Use sinks with `callDuringReplay: false` for logging -4. Use CancellationScope.nonCancellable for critical cleanup operations -5. Use `ActivityCancellationType.WAIT_CANCELLATION_COMPLETED` when cleanup is important -6. Store progress in heartbeat details for resumable long-running activities -7. Use Nexus for cross-namespace service communication diff --git a/references/typescript/data-handling.edits.md b/references/typescript/data-handling.edits.md index 3c0777d..b12cbf8 100644 --- a/references/typescript/data-handling.edits.md +++ b/references/typescript/data-handling.edits.md @@ -1,6 +1,6 @@ # TypeScript data-handling.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/typescript/data-handling.md b/references/typescript/data-handling.md index 88aad84..d63f2db 100644 --- a/references/typescript/data-handling.md +++ b/references/typescript/data-handling.md @@ -12,6 +12,101 @@ The default converter handles: - Protobuf messages (if configured) - JSON-serializable types +## Custom Data Converter + +Create custom converters for special serialization needs. + +```typescript +import { + DataConverter, + PayloadConverter, + defaultPayloadConverter, +} from '@temporalio/common'; + +class CustomPayloadConverter implements PayloadConverter { + toPayload(value: unknown): Payload | undefined { + // Custom serialization logic + return defaultPayloadConverter.toPayload(value); + } + + fromPayload(payload: Payload): T { + // Custom deserialization logic + return defaultPayloadConverter.fromPayload(payload); + } +} + +const dataConverter: DataConverter = { + payloadConverter: new CustomPayloadConverter(), +}; + +// Apply to client +const client = new Client({ + dataConverter, +}); + +// Apply to worker +const worker = await Worker.create({ + dataConverter, + // ... +}); +``` + +## Payload Codec (Encryption) + +Encrypt sensitive workflow data. + +```typescript +import { PayloadCodec, Payload } from '@temporalio/common'; + +class EncryptionCodec implements PayloadCodec { + private readonly encryptionKey: Uint8Array; + + constructor(key: Uint8Array) { + this.encryptionKey = key; + } + + async encode(payloads: Payload[]): Promise { + return Promise.all( + payloads.map(async (payload) => ({ + metadata: { + encoding: 'binary/encrypted', + }, + data: await this.encrypt(payload.data ?? new Uint8Array()), + })) + ); + } + + async decode(payloads: Payload[]): Promise { + return Promise.all( + payloads.map(async (payload) => { + if (payload.metadata?.encoding === 'binary/encrypted') { + return { + ...payload, + data: await this.decrypt(payload.data ?? new Uint8Array()), + }; + } + return payload; + }) + ); + } + + private async encrypt(data: Uint8Array): Promise { + // Implement encryption (e.g., using Web Crypto API) + return data; + } + + private async decrypt(data: Uint8Array): Promise { + // Implement decryption + return data; + } +} + +// Apply codec +const dataConverter: DataConverter = { + payloadCodec: new EncryptionCodec(encryptionKey), +}; +``` + ## Search Attributes Custom searchable fields for workflow visibility. @@ -109,101 +204,6 @@ export async function orderWorkflow(): Promise { } ``` -## Custom Data Converter - -Create custom converters for special serialization needs. - -```typescript -import { - DataConverter, - PayloadConverter, - defaultPayloadConverter, -} from '@temporalio/common'; - -class CustomPayloadConverter implements PayloadConverter { - toPayload(value: unknown): Payload | undefined { - // Custom serialization logic - return defaultPayloadConverter.toPayload(value); - } - - fromPayload(payload: Payload): T { - // Custom deserialization logic - return defaultPayloadConverter.fromPayload(payload); - } -} - -const dataConverter: DataConverter = { - payloadConverter: new CustomPayloadConverter(), -}; - -// Apply to client -const client = new Client({ - dataConverter, -}); - -// Apply to worker -const worker = await Worker.create({ - dataConverter, - // ... -}); -``` - -## Payload Codec (Encryption) - -Encrypt sensitive workflow data. - -```typescript -import { PayloadCodec, Payload } from '@temporalio/common'; - -class EncryptionCodec implements PayloadCodec { - private readonly encryptionKey: Uint8Array; - - constructor(key: Uint8Array) { - this.encryptionKey = key; - } - - async encode(payloads: Payload[]): Promise { - return Promise.all( - payloads.map(async (payload) => ({ - metadata: { - encoding: 'binary/encrypted', - }, - data: await this.encrypt(payload.data ?? new Uint8Array()), - })) - ); - } - - async decode(payloads: Payload[]): Promise { - return Promise.all( - payloads.map(async (payload) => { - if (payload.metadata?.encoding === 'binary/encrypted') { - return { - ...payload, - data: await this.decrypt(payload.data ?? new Uint8Array()), - }; - } - return payload; - }) - ); - } - - private async encrypt(data: Uint8Array): Promise { - // Implement encryption (e.g., using Web Crypto API) - return data; - } - - private async decrypt(data: Uint8Array): Promise { - // Implement decryption - return data; - } -} - -// Apply codec -const dataConverter: DataConverter = { - payloadCodec: new EncryptionCodec(encryptionKey), -}; -``` - ## Protobuf Support Using Protocol Buffers for type-safe serialization. diff --git a/references/typescript/determinism.edits.md b/references/typescript/determinism.edits.md index 424c6ae..8b05274 100644 --- a/references/typescript/determinism.edits.md +++ b/references/typescript/determinism.edits.md @@ -1,6 +1,6 @@ # TypeScript determinism.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/typescript/determinism.md b/references/typescript/determinism.md index 4778fd0..5c5e1c3 100644 --- a/references/typescript/determinism.md +++ b/references/typescript/determinism.md @@ -28,6 +28,27 @@ The Temporal workflow sandbox will use the same random seed when replaying a wor See `references/typescript/determinism-protection.md` for more information about the sandbox. +## Deterministic UUID Generation + +Generate deterministic UUIDs safe to use in workflows. Uses the workflow seeded PRNG, so the same UUID is generated during replay. + +```typescript +import { uuid4 } from '@temporalio/workflow'; + +export async function workflowWithIds(): Promise { + const childWorkflowId = uuid4(); + await executeChild(childWorkflow, { + workflowId: childWorkflowId, + args: [input], + }); +} +``` + +**When to use:** +- Generating unique IDs for child workflows +- Creating idempotency keys +- Any situation requiring unique identifiers in workflow code + ## Forbidden Operations ```typescript diff --git a/references/typescript/editing_prompt.md b/references/typescript/editing_prompt.md new file mode 100644 index 0000000..807fb23 --- /dev/null +++ b/references/typescript/editing_prompt.md @@ -0,0 +1,21 @@ +I'm working on building a Skill for developing temporal code. The skill is rooted at plugins/temporal-developer/skills/temporal-developer/. + +It is broken down into 4 components: +- SKILL.md: the top-level skill info, getting started +- references/core/: All the language-agnostic documentation +- references/python/: the python specific documentation +- references/typescript/: the typescript specific documentation + +Generally, core describes concepts, such as conceptual patterns, common gotchas, etc. The language-specific directories then show concrete examples of those concepts, in the language. + +Currently, SKILL.md, core, and python are complete and in a good state. I'm working on the typescript specific documentation now. + +**I want you to help me edit references/typescript/patterns.md**. This should be parallel structure to references/core/patterns.md and references/python/patterns.md, but with typescript specific examples. + +Right now it already has a lot of content, but it has not yet been reviewed or polished. Please take these steps: +1. Review it, in comparison to references/core/patterns.md and references/python/patterns.md for overall structure and content. Note any gaps that it has relative to those. Also note content it has that is not in Python. +2. Based on that review, in references/typescript/patterns.edits.md, create a list of content that should be added and/or removed. Note that there *may* be some content that is only relevant for TypeScript, and it may thus be appropriate to have it even if it doesn't correspond to Python or core. Conversely, some content in Python may not be applicable to TypeScript. +3. Consult with me on the list of content to add and/or remove. +4. Once we agree on the list, now you should edit references/typescript/patterns.md to add and/or remove the content. +5. Consult with me again. We may loop some here on additional edits. +6. Finally, you will need to do a pass for **correctness** in the content. At this point, you should use the context7 mcp server with /temporalio/sdk-typescript and temporal-docs mcp server to verify correctness of every bit of content. \ No newline at end of file diff --git a/references/typescript/error-handling.edits.md b/references/typescript/error-handling.edits.md index 8dbadf0..b683dc8 100644 --- a/references/typescript/error-handling.edits.md +++ b/references/typescript/error-handling.edits.md @@ -1,6 +1,6 @@ # TypeScript error-handling.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/typescript/error-handling.md b/references/typescript/error-handling.md index 4057043..608c93b 100644 --- a/references/typescript/error-handling.md +++ b/references/typescript/error-handling.md @@ -37,7 +37,7 @@ export async function validateActivity(input: string): Promise { ## Handling Errors in Workflows ```typescript -import { proxyActivities, ApplicationFailure } from '@temporalio/workflow'; +import { proxyActivities, ApplicationFailure, log } from '@temporalio/workflow'; import type * as activities from './activities'; const { riskyActivity } = proxyActivities({ @@ -49,7 +49,7 @@ export async function workflowWithErrorHandling(): Promise { return await riskyActivity(); } catch (err) { if (err instanceof ApplicationFailure) { - console.log(`Activity failed: ${err.type} - ${err.message}`); + log.warn('Activity failed', { type: err.type, message: err.message }); } throw err; } @@ -71,6 +71,8 @@ const { myActivity } = proxyActivities({ }); ``` +**Note:** Only set retry options if you have a domain-specific reason to. The defaults are suitable for most use cases. + ## Timeout Configuration ```typescript @@ -81,100 +83,34 @@ const { myActivity } = proxyActivities({ }); ``` -## Cancellation Handling in Activities - -```typescript -import { CancelledFailure, heartbeat } from '@temporalio/activity'; - -export async function cancellableActivity(): Promise { - try { - while (true) { - heartbeat(); - await doWork(); - } - } catch (err) { - if (err instanceof CancelledFailure) { - await cleanup(); - } - throw err; - } -} -``` - -## Idempotency Patterns - -Activities may be executed more than once due to retries. Design activities to be idempotent to prevent duplicate side effects. - -### Why Activities Need Idempotency - -Consider this scenario: -1. Worker polls and accepts an Activity Task -2. Activity function completes successfully -3. Worker crashes before notifying the Cluster -4. Cluster retries the Activity (doesn't know it completed) - -If the Activity charged a credit card, the customer would be charged twice. +## Workflow Failure -### Using Idempotency Keys - -Use the Workflow Run ID + Activity ID as an idempotency key - this is constant across retries but unique across workflow executions: +Workflows can throw errors to indicate failure: ```typescript -import { info } from '@temporalio/activity'; - -export async function chargePayment( - customerId: string, - amount: number -): Promise { - // Create idempotency key from workflow context - const idempotencyKey = `${info().workflowRunId}-${info().activityId}`; - - // Pass to external service (e.g., Stripe, payment processor) - const result = await paymentService.charge({ - customerId, - amount, - idempotencyKey, // Service ignores duplicate requests with same key - }); +import { ApplicationFailure } from '@temporalio/workflow'; - return result.transactionId; +export async function myWorkflow(): Promise { + if (someCondition) { + throw ApplicationFailure.create({ + message: 'Workflow failed due to invalid state', + type: 'InvalidStateError', + }); + } + return 'success'; } ``` -**Important**: Use `workflowRunId` (not `workflowId`) because workflow IDs can be reused. - -### Granular Activities - -Make activities more granular to reduce the scope of potential retries: +**Warning:** Do NOT use `nonRetryable: true` for workflow failures in most cases. Unlike activities, workflow retries are controlled by the caller, not retry policies. Use `nonRetryable` only for errors that are truly unrecoverable (e.g., invalid input that will never be valid). -```typescript -// BETTER - Three small activities -export async function lookupCustomer(customerId: string): Promise { - return await db.findCustomer(customerId); -} - -export async function processPayment(paymentInfo: PaymentInfo): Promise { - const idempotencyKey = `${info().workflowRunId}-${info().activityId}`; - return await paymentService.process(paymentInfo, idempotencyKey); -} +## Idempotency -export async function sendReceipt(transactionId: string): Promise { - await emailService.sendReceipt(transactionId); -} - -// WORSE - One large activity doing multiple things -export async function processOrder(order: Order): Promise { - const customer = await db.findCustomer(order.customerId); - await paymentService.process(order.payment); // If this fails here... - await emailService.sendReceipt(order.id); // ...all three retry -} -``` +For idempotency patterns (using keys, making activities granular), see `core/patterns.md`. ## Best Practices 1. Use specific error types for different failure modes -2. Set `nonRetryable: true` for permanent failures +2. Set `nonRetryable: true` for permanent failures in activities 3. Configure `nonRetryableErrorTypes` in retry policy -4. Handle `CancelledFailure` in activities that need cleanup -5. Always re-throw errors after handling -6. Use idempotency keys for activities with external side effects -7. Make activities granular to minimize retry scope +4. Always re-throw errors after handling in workflows +5. Use `log` from `@temporalio/workflow` for replay-safe logging diff --git a/references/typescript/gotchas.edits.md b/references/typescript/gotchas.edits.md index 805c629..0d41a66 100644 --- a/references/typescript/gotchas.edits.md +++ b/references/typescript/gotchas.edits.md @@ -1,6 +1,6 @@ # TypeScript gotchas.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/typescript/gotchas.md b/references/typescript/gotchas.md index f648a83..4e213e8 100644 --- a/references/typescript/gotchas.md +++ b/references/typescript/gotchas.md @@ -2,112 +2,6 @@ TypeScript-specific mistakes and anti-patterns. See also [Common Gotchas](../core/gotchas.md) for language-agnostic concepts. -## Idempotency - -```typescript -// BAD - May charge customer multiple times on retry -export async function chargePayment(orderId: string, amount: number): Promise { - return await paymentApi.charge(customerId, amount); -} - -// GOOD - Safe for retries -export async function chargePayment(orderId: string, amount: number): Promise { - return await paymentApi.charge(customerId, amount, { - idempotencyKey: `order-${orderId}`, - }); -} -``` - -## Replay Safety - -### Side Effects in Workflows - -```typescript -// BAD - console.log runs on every replay -export async function notificationWorkflow(): Promise { - console.log('Starting workflow'); // Runs on replay too - await sendSlackNotification('Started'); // Side effect in workflow! - await activities.doWork(); -} - -// GOOD - Use workflow logger and activities for side effects -import { log } from '@temporalio/workflow'; - -export async function notificationWorkflow(): Promise { - log.info('Starting workflow'); // Only logs on first execution - await activities.sendNotification('Started'); -} -``` - -### Non-Deterministic Operations - -The TypeScript SDK automatically replaces some non-deterministic operations: - -```typescript -// These are SAFE - automatically replaced by SDK -const now = Date.now(); // Deterministic -const random = Math.random(); // Deterministic -const id = crypto.randomUUID(); // Deterministic (if using workflow's crypto) - -// For explicit deterministic UUID, use: -import { uuid4 } from '@temporalio/workflow'; -const id = uuid4(); -``` - -## Query Handlers - -### Modifying State - -```typescript -// BAD - Query modifies state -const queues = new Map(); - -export const getNextItemQuery = defineQuery('getNextItem'); - -export async function queueWorkflow(queueId: string): Promise { - const queue: string[] = []; - - setHandler(getNextItemQuery, () => { - return queue.shift(); // Mutates state! - }); - - await condition(() => false); -} - -// GOOD - Query reads, Update modifies -export const peekQuery = defineQuery('peek'); -export const dequeueUpdate = defineUpdate('dequeue'); - -export async function queueWorkflow(): Promise { - const queue: string[] = []; - - setHandler(peekQuery, () => queue[0]); - - setHandler(dequeueUpdate, () => queue.shift()); - - await condition(() => false); -} -``` - -### Blocking in Queries - -```typescript -// BAD - Queries cannot await -setHandler(getDataQuery, async () => { - if (!data) { - data = await activities.fetchData(); // Cannot await in query! - } - return data; -}); - -// GOOD - Query returns state, signal triggers refresh -setHandler(refreshSignal, async () => { - data = await activities.fetchData(); -}); - -setHandler(getDataQuery, () => data); -``` - ## Activity Imports ### Importing Implementations Instead of Types @@ -187,80 +81,23 @@ All `@temporalio/*` packages must have the same version: } ``` -## Error Handling +## Wrong Retry Classification -### Swallowing Errors +A common mistake is treating transient errors as permanent (or vice versa): -```typescript -// BAD - Error is hidden -export async function riskyWorkflow(): Promise { - try { - await activities.riskyOperation(); - } catch { - // Error is lost! - } -} - -// GOOD - Handle appropriately -import { log } from '@temporalio/workflow'; - -export async function riskyWorkflow(): Promise { - try { - await activities.riskyOperation(); - } catch (err) { - log.error('Activity failed', { error: err }); - throw err; // Or use fallback, compensate, etc. - } -} -``` - -### Wrong Retry Classification +- **Transient errors** (retry): network timeouts, temporary service unavailability, rate limits +- **Permanent errors** (don't retry): invalid input, authentication failure, resource not found ```typescript -// BAD - Network errors should be retried -export async function callApi(): Promise { - try { - return await fetch(url); - } catch (err) { - throw ApplicationFailure.nonRetryable('Connection failed'); - } -} +// BAD: Retrying a permanent error +throw ApplicationFailure.create({ message: 'User not found' }); +// This will retry indefinitely! -// GOOD - Only permanent failures are non-retryable -export async function callApi(): Promise { - try { - return await fetch(url); - } catch (err) { - if (err instanceof InvalidCredentialsError) { - throw ApplicationFailure.nonRetryable('Invalid API key'); - } - throw err; // Let Temporal retry network errors - } -} +// GOOD: Mark permanent errors as non-retryable +throw ApplicationFailure.nonRetryable('User not found'); ``` -## Retry Policies - -### Too Aggressive - -```typescript -// BAD - Gives up too easily -const result = await activities.flakyApiCall({ - scheduleToCloseTimeout: '30 seconds', - retry: { maximumAttempts: 1 }, -}); - -// GOOD - Resilient to transient failures -const result = await activities.flakyApiCall({ - scheduleToCloseTimeout: '10 minutes', - retry: { - initialInterval: '1 second', - maximumInterval: '1 minute', - backoffCoefficient: 2, - maximumAttempts: 10, - }, -}); -``` +For detailed guidance on error classification and retry policies, see `error-handling.md`. ## Cancellation diff --git a/references/typescript/observability.edits.md b/references/typescript/observability.edits.md index a0f8a48..d88c7b8 100644 --- a/references/typescript/observability.edits.md +++ b/references/typescript/observability.edits.md @@ -1,6 +1,6 @@ # TypeScript observability.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/typescript/observability.md b/references/typescript/observability.md index 74e24bc..cf7abb9 100644 --- a/references/typescript/observability.md +++ b/references/typescript/observability.md @@ -80,71 +80,6 @@ const logger = new DefaultLogger('DEBUG', (entry) => { Runtime.install({ logger }); ``` -## OpenTelemetry Integration - -The `@temporalio/interceptors-opentelemetry` package provides tracing for workflows and activities. - -### Setup - -```typescript -// instrumentation.ts - require before other imports -import { NodeSDK } from '@opentelemetry/sdk-node'; -import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node'; -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; -import { Resource } from '@opentelemetry/resources'; -import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; - -export const resource = new Resource({ - [ATTR_SERVICE_NAME]: 'my-temporal-service', -}); - -// Use OTLP exporter for production -export const traceExporter = new OTLPTraceExporter({ - url: 'http://127.0.0.1:4317', - timeoutMillis: 1000, -}); - -export const otelSdk = new NodeSDK({ - resource, - traceExporter, -}); - -otelSdk.start(); -``` - -### Worker Configuration - -```typescript -import { Worker } from '@temporalio/worker'; -import { - OpenTelemetryActivityInboundInterceptor, - OpenTelemetryActivityOutboundInterceptor, - makeWorkflowExporter, -} from '@temporalio/interceptors-opentelemetry/lib/worker'; -import { resource, traceExporter } from './instrumentation'; -import * as activities from './activities'; - -const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), - activities, - taskQueue: 'my-queue', - - // OpenTelemetry sinks and interceptors - sinks: { - exporter: makeWorkflowExporter(traceExporter, resource), - }, - interceptors: { - workflowModules: [require.resolve('./workflows')], - activity: [ - (ctx) => ({ - inbound: new OpenTelemetryActivityInboundInterceptor(ctx), - outbound: new OpenTelemetryActivityOutboundInterceptor(ctx), - }), - ], - }, -}); -``` - ## Metrics ### Prometheus Metrics @@ -178,49 +113,6 @@ Runtime.install({ }); ``` -## Debugging with Event History - -### Viewing Event History - -Use the Temporal CLI or Web UI to inspect workflow execution history: - -```bash -# CLI -temporal workflow show --workflow-id my-workflow - -# Get history as JSON -temporal workflow show --workflow-id my-workflow --output json -``` - -### Key Events to Look For - -| Event | Indicates | -|-------|-----------| -| `ActivityTaskScheduled` | Activity was requested | -| `ActivityTaskStarted` | Worker started executing activity | -| `ActivityTaskCompleted` | Activity completed successfully | -| `ActivityTaskFailed` | Activity threw an error | -| `ActivityTaskTimedOut` | Activity exceeded timeout | -| `TimerStarted` | `sleep()` called | -| `TimerFired` | Sleep completed | -| `WorkflowTaskFailed` | Non-deterministic error or workflow bug | - -### Debugging Non-Determinism - -If you see `WorkflowTaskFailed` with a non-determinism error: - -1. Export the history: `temporal workflow show -w -o json > history.json` -2. Run replay test to reproduce: - -```typescript -import { Worker } from '@temporalio/worker'; - -await Worker.runReplayHistory( - { workflowsPath: require.resolve('./workflows') }, - history -); -``` - ## Best Practices 1. Use `log` from `@temporalio/workflow` - never `console.log` in workflows diff --git a/references/typescript/patterns.edits.md b/references/typescript/patterns.edits.md index 2504007..0f1665f 100644 --- a/references/typescript/patterns.edits.md +++ b/references/typescript/patterns.edits.md @@ -1,6 +1,6 @@ # TypeScript patterns.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/typescript/patterns.md b/references/typescript/patterns.md index 3e36c13..dda49db 100644 --- a/references/typescript/patterns.md +++ b/references/typescript/patterns.md @@ -49,6 +49,8 @@ export async function dynamicSignalWorkflow(): Promise ## Queries +**Important:** Queries must NOT modify workflow state or have side effects. + ```typescript import { defineQuery, setHandler } from '@temporalio/workflow'; @@ -219,11 +221,18 @@ export async function longRunningWorkflow(state: State): Promise { ## Saga Pattern +**Important:** Compensation activities should be idempotent. + ```typescript +import { log } from '@temporalio/workflow'; + export async function sagaWorkflow(order: Order): Promise { const compensations: Array<() => Promise> = []; try { + // IMPORTANT: Save compensation BEFORE calling the activity + // If activity fails after completing but before returning, + // compensation must still be registered await reserveInventory(order); compensations.push(() => releaseInventory(order)); @@ -237,7 +246,7 @@ export async function sagaWorkflow(order: Order): Promise { try { await compensate(); } catch (compErr) { - console.log('Compensation failed', compErr); + log.warn('Compensation failed', { error: compErr }); } } throw err; diff --git a/references/typescript/testing.edits.md b/references/typescript/testing.edits.md index b4fe6b3..0c4142e 100644 --- a/references/typescript/testing.edits.md +++ b/references/typescript/testing.edits.md @@ -1,6 +1,6 @@ # TypeScript testing.md Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/typescript/testing.md b/references/typescript/testing.md index 70e4a0b..dcaa95d 100644 --- a/references/typescript/testing.md +++ b/references/typescript/testing.md @@ -43,24 +43,7 @@ describe('Workflow', () => { }); ``` -## Time Skipping - -```typescript -// Create time-skipping environment -const testEnv = await TestWorkflowEnvironment.createTimeSkipping(); - -// Time automatically advances when workflows wait -await worker.runUntil(async () => { - const result = await client.workflow.execute(longRunningWorkflow, { - taskQueue: 'test', - workflowId: 'test-workflow', - }); - // Even if workflow has 1-hour timer, test completes instantly -}); - -// Manual time advancement -await testEnv.sleep('1 day'); -``` +The test environment automatically skips time when the workflow is waiting on timers, making tests fast. ## Activity Mocking @@ -76,6 +59,80 @@ const worker = await Worker.create({ }); ``` +## Testing Signals and Queries + +```typescript +it('handles signals and queries', async () => { + await worker.runUntil(async () => { + const handle = await client.workflow.start(approvalWorkflow, { + taskQueue: 'test', + workflowId: 'approval-test', + }); + + // Query current state + const status = await handle.query('getStatus'); + expect(status).toEqual('pending'); + + // Send signal + await handle.signal('approve'); + + // Wait for completion + const result = await handle.result(); + expect(result).toEqual('Approved!'); + }); +}); +``` + +## Testing Failure Cases + +Test that workflows handle errors correctly: + +```typescript +import { TestWorkflowEnvironment } from '@temporalio/testing'; +import { Worker } from '@temporalio/worker'; +import assert from 'assert'; + +describe('Failure handling', () => { + let testEnv: TestWorkflowEnvironment; + + before(async () => { + testEnv = await TestWorkflowEnvironment.createLocal(); + }); + + after(async () => { + await testEnv?.teardown(); + }); + + it('handles activity failure', async () => { + const { client, nativeConnection } = testEnv; + + const worker = await Worker.create({ + connection: nativeConnection, + taskQueue: 'test', + workflowsPath: require.resolve('./workflows'), + activities: { + // Mock activity that always fails + myActivity: async () => { + throw new Error('Activity failed'); + }, + }, + }); + + await worker.runUntil(async () => { + try { + await client.workflow.execute(myWorkflow, { + workflowId: 'test-failure', + taskQueue: 'test', + }); + assert.fail('Expected workflow to fail'); + } catch (err) { + assert(err instanceof WorkflowFailedError); + } + }); + }); +}); +``` + ## Replay Testing ```typescript @@ -95,30 +152,36 @@ describe('Replay', () => { }); ``` -## Testing Signals and Queries - -```typescript -it('handles signals and queries', async () => { - await worker.runUntil(async () => { - const handle = await client.workflow.start(approvalWorkflow, { - taskQueue: 'test', - workflowId: 'approval-test', - }); +## Activity Testing - // Query current state - const status = await handle.query('getStatus'); - expect(status).toEqual('pending'); +Test activities in isolation without running a workflow: - // Send signal - await handle.signal('approve'); +```typescript +import { MockActivityEnvironment } from '@temporalio/testing'; +import { myActivity } from './activities'; +import assert from 'assert'; + +describe('Activity tests', () => { + it('completes successfully', async () => { + const env = new MockActivityEnvironment(); + const result = await env.run(myActivity, 'input'); + assert.equal(result, 'expected output'); + }); - // Wait for completion - const result = await handle.result(); - expect(result).toEqual('Approved!'); + it('handles cancellation', async () => { + const env = new MockActivityEnvironment({ cancelled: true }); + try { + await env.run(longRunningActivity, 'input'); + assert.fail('Expected cancellation'); + } catch (err) { + assert(err instanceof CancelledFailure); + } }); }); ``` +**Note:** `MockActivityEnvironment` provides `heartbeat()` and cancellation support for testing activity behavior. + ## Best Practices 1. Use time-skipping for workflows with timers diff --git a/references/typescript/typescript.edits.md b/references/typescript/typescript.edits.md index b8dd433..a9e2f26 100644 --- a/references/typescript/typescript.edits.md +++ b/references/typescript/typescript.edits.md @@ -1,6 +1,6 @@ # TypeScript typescript.md (top-level) Edits -## Status: PENDING +## Status: DONE --- diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 47b2551..2268ff4 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -6,43 +6,26 @@ The Temporal TypeScript SDK provides a modern async/await approach to building d **CRITICAL**: All `@temporalio/*` packages must have the same version number. -## How Temporal Works: History Replay +## Understanding Replay -Understanding how Temporal achieves durable execution is essential for writing correct workflows. - -### The Replay Mechanism - -When a Worker executes workflow code, it creates **Commands** (requests for operations like starting an Activity or Timer) and sends them to the Temporal Cluster. The Cluster maintains an **Event History** - a durable log of everything that happened during the workflow execution. - -**Key insight**: During replay, the Worker re-executes your workflow code but uses the Event History to restore state instead of re-executing Activities. When it encounters an Activity call that has a corresponding `ActivityTaskCompleted` event in history, it returns the stored result instead of scheduling a new execution. - -This is why **determinism matters**: The Worker validates that Commands generated during replay match the Events in history. A mismatch causes a non-deterministic error because the Worker cannot reliably restore state. - -### Commands and Events - -| Workflow Code | Command Generated | Resulting Event | -|--------------|-------------------|-----------------| -| Activity call | `ScheduleActivityTask` | `ActivityTaskScheduled` | -| `sleep()` | `StartTimer` | `TimerStarted` | -| Child workflow | `StartChildWorkflowExecution` | `ChildWorkflowExecutionStarted` | -| Return/complete | `CompleteWorkflowExecution` | `WorkflowExecutionCompleted` | - -### When Replay Occurs - -- Worker crashes and recovers -- Worker's cache fills and evicts workflow state -- Workflow continues after long timer -- Testing with replay histories +Temporal workflows are durable through history replay. For details on how this works, see `core/determinism.md`. ## Quick Start +**Add Dependencies:** Install the Temporal SDK packages: +```bash +npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity +``` + +**activities.ts** - Activity definitions (separate file for bundling performance): ```typescript -// activities.ts export async function greet(name: string): Promise { return `Hello, ${name}!`; } +``` -// workflows.ts +**workflows.ts** - Workflow definition (use type-only imports for activities): +```typescript import { proxyActivities } from '@temporalio/workflow'; import type * as activities from './activities'; @@ -53,8 +36,10 @@ const { greet } = proxyActivities({ export async function greetingWorkflow(name: string): Promise { return await greet(name); } +``` -// worker.ts +**worker.ts** - Worker setup (imports activities and workflows, runs indefinitely): +```typescript import { Worker } from '@temporalio/worker'; import * as activities from './activities'; @@ -66,8 +51,37 @@ async function run() { }); await worker.run(); } + +run().catch(console.error); ``` +**Start the dev server:** Start `temporal server start-dev` in the background. + +**Start the worker:** Run `npx ts-node worker.ts` in the background. + +**client.ts** - Start a workflow execution: +```typescript +import { Client } from '@temporalio/client'; +import { greetingWorkflow } from './workflows'; +import { v4 as uuid } from 'uuid'; + +async function run() { + const client = new Client(); + + const result = await client.workflow.execute(greetingWorkflow, { + workflowId: uuid(), + taskQueue: 'greeting-queue', + args: ['my name'], + }); + + console.log(`Result: ${result}`); +} + +run().catch(console.error); +``` + +**Run the workflow:** Run `npx ts-node client.ts`. Should output: `Result: Hello, my name!`. + ## Key Concepts ### Workflow Definition @@ -84,6 +98,31 @@ async function run() { - Use `Worker.create()` with workflowsPath - Import activities directly (not via proxy) +## File Organization Best Practice + +**Keep Workflow definitions in separate files from Activity definitions.** The TypeScript SDK bundles workflow files separately. Minimizing workflow file contents improves Worker startup time. + +``` +my_temporal_app/ +├── workflows/ +│ └── greeting.ts # Only Workflow functions +├── activities/ +│ └── translate.ts # Only Activity functions +├── worker.ts # Worker setup, imports both +└── client.ts # Client code to start workflows +``` + +**In the Workflow file, use type-only imports for activities:** +```typescript +// workflows/greeting.ts +import { proxyActivities } from '@temporalio/workflow'; +import type * as activities from '../activities/translate'; + +const { translate } = proxyActivities({ + startToCloseTimeout: '1 minute', +}); +``` + ## Determinism Rules The TypeScript SDK runs workflows in an isolated V8 sandbox. @@ -107,6 +146,8 @@ See `determinism.md` for detailed rules. 3. **Direct I/O in workflows** - Use activities for external calls 4. **Missing `proxyActivities`** - Required to call activities from workflows 5. **Forgetting to bundle workflows** - Worker needs workflowsPath +6. **Forgetting to heartbeat** - Long-running activities need `heartbeat()` calls +7. **Using console.log in workflows** - Use `log` from `@temporalio/workflow` for replay-safe logging ## Writing Tests @@ -115,13 +156,13 @@ See `references/typescript/testing.md` for info on writing tests. ## Additional Resources ### Reference Files -- **`references/python/patterns.md`** - Signals, queries, child workflows, saga pattern, etc. -- **`references/python/determinism.md`** - Essentials of determinism in TypeScript -- **`references/python/gotchas.md`** - TypeScript-specific mistakes and anti-patterns -- **`references/python/error-handling.md`** - ApplicationError, retry policies, non-retryable errors, idempotency -- **`references/python/observability.md`** - Logging, metrics, tracing, Search Attributes -- **`references/python/testing.md`** - TestWorkflowEnvironment, time-skipping, activity mocking -- **`references/python/advanced-features.md`** - Schedules, worker tuning, and more -- **`references/python/data-handling.md`** - Data converters, payload encryption, etc. -- **`references/python/versioning.md`** - Patching API, workflow type versioning, Worker Versioning -- **`references/python/determinism-protection.md`** - V8 sandbox and bundling +- **`references/typescript/patterns.md`** - Signals, queries, child workflows, saga pattern, etc. +- **`references/typescript/determinism.md`** - Essentials of determinism in TypeScript +- **`references/typescript/gotchas.md`** - TypeScript-specific mistakes and anti-patterns +- **`references/typescript/error-handling.md`** - ApplicationFailure, retry policies, non-retryable errors +- **`references/typescript/observability.md`** - Logging, metrics, tracing +- **`references/typescript/testing.md`** - TestWorkflowEnvironment, time-skipping, activity mocking +- **`references/typescript/advanced-features.md`** - Schedules, worker tuning, and more +- **`references/typescript/data-handling.md`** - Data converters, payload encryption, etc. +- **`references/typescript/versioning.md`** - Patching API, workflow type versioning, Worker Versioning +- **`references/typescript/determinism-protection.md`** - V8 sandbox and bundling From ed9d2fc7f0af52dc8d4497fbe202696484866190 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 25 Feb 2026 19:05:15 -0500 Subject: [PATCH 08/50] update tracking file --- .../{typescript => }/alignment_checking.md | 413 +++++++++--------- 1 file changed, 202 insertions(+), 211 deletions(-) rename references/{typescript => }/alignment_checking.md (75%) diff --git a/references/typescript/alignment_checking.md b/references/alignment_checking.md similarity index 75% rename from references/typescript/alignment_checking.md rename to references/alignment_checking.md index 8a75a37..feb9b3c 100644 --- a/references/typescript/alignment_checking.md +++ b/references/alignment_checking.md @@ -6,7 +6,9 @@ Track content alignment (do files have the right sections?) and style alignment **Style target:** Python is the reference style (code-first, minimal prose). TypeScript should match. -**Resume instructions:** Find sections marked `unchecked` or `needs review` and continue from there. +**Implementation Status:** ✅ **COMPLETE** (2026-02-25) + +All TODO/DEL/BUG items have been implemented. See `.edits.md` files for details of changes made. --- @@ -58,10 +60,10 @@ Shows which sections exist in each language. Organized by file. | Overview | — | ✓ | 1 | ✓ | 1 | | | Default Data Converter | — | ✓ | 2 | ✓ | 2 | | | Pydantic Integration | — | ✓ | 3 | — | — | | -| Custom Data Converter | — | ✓ | 4 | ✓ | 5 | | -| Payload Encryption | — | ✓ | 5 | ✓ | 6 | | -| Search Attributes | — | ✓ | 6 | ✓ | 3 | | -| Workflow Memo | — | ✓ | 7 | ✓ | 4 | | +| Custom Data Converter | — | ✓ | 4 | ✓ | 3 | | +| Payload Encryption | — | ✓ | 5 | ✓ | 4 | | +| Search Attributes | — | ✓ | 6 | ✓ | 5 | | +| Workflow Memo | — | ✓ | 7 | ✓ | 6 | | | Protobuf Support | — | — | — | ✓ | 7 | | | Large Payloads | — | ✓ | 8 | ✓ | 8 | | | Deterministic APIs for Values | — | ✓ | 9 | — | — | | @@ -78,30 +80,30 @@ Shows which sections exist in each language. Organized by file. | Handling Activity Errors in Workflows | — | ✓ | 4 | ✓ | 4 | | | Retry Configuration | — | ✓ | 5 | ✓ | 5 | | | Timeout Configuration | — | ✓ | 6 | ✓ | 6 | | -| Workflow Failure | — | ✓ | 7 | TODO | — | | -| Cancellation Handling in Activities | — | — | — | DEL | 7 | | -| Idempotency Patterns | — | — | — | DEL | 8 | | +| Workflow Failure | — | ✓ | 7 | ✓ | 7 | | +| Cancellation Handling in Activities | — | — | — | — | — | | +| Idempotency Patterns | — | — | — | — | — | | | Best Practices | — | ✓ | 8 | ✓ | 9 | | ### gotchas.md | Section | Core | Core# | Python | Py# | TypeScript | TS# | Go | |---------|------|-------|--------|-----|------------|-----|-----| -| Idempotency / Non-Idempotent Activities | ✓ | 1 | — | — | DEL | 1 | | -| Replay Safety / Side Effects & Non-Determinism | ✓ | 2 | — | — | DEL | 2 | | +| Idempotency / Non-Idempotent Activities | ✓ | 1 | — | — | — | — | | +| Replay Safety / Side Effects & Non-Determinism | ✓ | 2 | — | — | — | — | | | Multiple Workers with Different Code | ✓ | 3 | — | — | — | — | | -| Retry Policies / Failing Activities Too Quickly | ✓ | 4 | — | — | DEL | 7 | | -| Query Handlers / Query Handler Mistakes | ✓ | 5 | — | — | DEL | 3 | | +| Retry Policies / Failing Activities Too Quickly | ✓ | 4 | — | — | — | — | | +| Query Handlers / Query Handler Mistakes | ✓ | 5 | — | — | — | — | | | File Organization | ✓ | 6 | ✓ | 1 | — | — | | -| Activity Imports | — | — | — | — | ✓ | 4 | | -| Bundling Issues | — | — | — | — | ✓ | 5 | | +| Activity Imports | — | — | — | — | ✓ | 1 | | +| Bundling Issues | — | — | — | — | ✓ | 2 | | | Async vs Sync Activities | — | — | ✓ | 2 | — | — | | -| Error Handling | ✓ | 8 | — | — | DEL | 6 | | -| Wrong Retry Classification | ✓ | 8 | ✓ | 3 | TODO | — | | -| Cancellation | — | — | — | — | ✓ | 8 | | -| Heartbeating | — | — | ✓ | 4 | ✓ | 9 | | -| Testing | ✓ | 7 | ✓ | 5 | ✓ | 10 | | -| Timers and Sleep | — | — | TODO | — | ✓ | 11 | | +| Error Handling | ✓ | 8 | — | — | — | — | | +| Wrong Retry Classification | ✓ | 8 | ✓ | 3 | ✓ | 3 | | +| Cancellation | — | — | — | — | ✓ | 4 | | +| Heartbeating | — | — | ✓ | 4 | ✓ | 5 | | +| Testing | ✓ | 7 | ✓ | 5 | ✓ | 6 | | +| Timers and Sleep | — | — | ✓ | 6 | ✓ | 7 | | ### observability.md @@ -110,11 +112,11 @@ Shows which sections exist in each language. Organized by file. | Overview | — | ✓ | 1 | ✓ | 1 | | | Logging / Replay-Aware Logging | — | ✓ | 2 | ✓ | 2 | | | Customizing the Logger | — | ✓ | 2 | ✓ | 3 | | -| OpenTelemetry Integration | — | — | — | DEL | 4 | | -| Metrics | — | ✓ | 3 | ✓ | 5 | | +| OpenTelemetry Integration | — | — | — | — | — | | +| Metrics | — | ✓ | 3 | ✓ | 4 | | | Search Attributes (Visibility) | — | ✓ | 4 | — | — | | -| Debugging with Event History | — | — | — | DEL | 6 | | -| Best Practices | — | ✓ | 5 | ✓ | 7 | | +| Debugging with Event History | — | — | — | — | — | | +| Best Practices | — | ✓ | 5 | ✓ | 5 | | ### testing.md @@ -122,13 +124,13 @@ Shows which sections exist in each language. Organized by file. |---------|------|--------|-----|------------|-----|-----| | Overview | — | ✓ | 1 | ✓ | 1 | | | Test Environment Setup | — | ✓ | 2 | ✓ | 2 | | -| Time Skipping | — | — | — | DEL | 3 | | -| Activity Mocking | — | ✓ | 3 | ✓ | 4 | | -| Testing Signals and Queries | — | ✓ | 4 | ✓ | 6 | | -| Testing Failure Cases | — | ✓ | 5 | TODO | — | | -| Replay Testing | — | ✓ | 6 | ✓ | 5 | | -| Activity Testing | — | ✓ | 7 | TODO | — | | -| Best Practices | — | ✓ | 8 | ✓ | 7 | | +| Time Skipping | — | — | — | — | — | | +| Activity Mocking | — | ✓ | 3 | ✓ | 3 | | +| Testing Signals and Queries | — | ✓ | 4 | ✓ | 4 | | +| Testing Failure Cases | — | ✓ | 5 | ✓ | 5 | | +| Replay Testing | — | ✓ | 6 | ✓ | 6 | | +| Activity Testing | — | ✓ | 7 | ✓ | 7 | | +| Best Practices | — | ✓ | 8 | ✓ | 8 | | ### versioning.md @@ -139,7 +141,7 @@ Shows which sections exist in each language. Organized by file. | Patching API | ✓ | 3 | ✓ | 3 | ✓ | 3 | | | Workflow Type Versioning | ✓ | 4 | ✓ | 4 | ✓ | 4 | | | Worker Versioning | ✓ | 5 | ✓ | 5 | ✓ | 5 | | -| Choosing a Strategy | ✓ | 6 | TODO | — | ✓ | 6 | | +| Choosing a Strategy | ✓ | 6 | ✓ | 6 | ✓ | 6 | | | Best Practices | ✓ | 7 | ✓ | 6 | ✓ | 7 | | | Finding Workflows by Version | ✓ | 8 | — | — | — | — | | | Common Mistakes | ✓ | 9 | — | — | — | — | | @@ -149,14 +151,15 @@ Shows which sections exist in each language. Organized by file. | Section | Core | Python | Py# | TypeScript | TS# | Go | |---------|------|--------|-----|------------|-----|-----| | Overview | — | ✓ | 1 | ✓ | 1 | | -| How Temporal Works: History Replay | — | — | — | DEL | 2 | | +| How Temporal Works: History Replay | — | — | — | — | — | | +| Understanding Replay | — | — | — | ✓ | 2 | | | Quick Start / Quick Demo | — | ✓ | 2 | ✓ | 3 | | | Key Concepts | — | ✓ | 3 | ✓ | 4 | | -| Determinism Rules | — | — | — | ✓ | 5 | | -| File Organization Best Practice | — | ✓ | 4 | TODO | — | | -| Common Pitfalls | — | ✓ | 5 | ✓ | 6 | | -| Writing Tests | — | ✓ | 6 | ✓ | 7 | | -| Additional Resources | — | ✓ | 7 | ✓ | 8 | | +| File Organization Best Practice | — | ✓ | 4 | ✓ | 5 | | +| Determinism Rules | — | — | — | ✓ | 6 | | +| Common Pitfalls | — | ✓ | 5 | ✓ | 7 | | +| Writing Tests | — | ✓ | 6 | ✓ | 8 | | +| Additional Resources | — | ✓ | 7 | ✓ | 9 | | ### determinism-protection.md @@ -215,8 +218,8 @@ Shows which sections exist in each language. Organized by file. #### Queries - **Python:** One-liner note ("must NOT modify state") + code -- **TypeScript:** Code only -- **Decision:** ⚠️ needs fix — TS should add "**Important:** Queries must NOT modify workflow state or have side effects" note +- **TypeScript:** One-liner note ("must NOT modify state") + code +- **Decision:** ✅ FIXED — TS now has "**Important:** Queries must NOT modify workflow state or have side effects" note --- @@ -264,11 +267,11 @@ Shows which sections exist in each language. Organized by file. #### Saga Pattern - **Python:** One-liner note ("compensation activities should be idempotent") + code with detailed comments explaining WHY save compensation BEFORE activity -- **TypeScript:** Code only (simpler example, no inline comments) -- **Decision:** ⚠️ needs fix - - TS should add "**Important:** Compensation activities should be idempotent" note - - TS should add comments explaining WHY compensation is saved BEFORE the activity (critical edge case) - - ⚠️ **BUG:** TS uses `console.log` in catch block — should use `log` from `@temporalio/workflow` for replay-safe logging +- **TypeScript:** One-liner note + code with detailed comments + replay-safe logging +- **Decision:** ✅ FIXED + - TS now has "**Important:** Compensation activities should be idempotent" note + - TS now has comments explaining WHY compensation is saved BEFORE the activity + - TS now uses `log` from `@temporalio/workflow` for replay-safe logging --- @@ -432,15 +435,15 @@ Shows which sections exist in each language. Organized by file. #### Handling Activity Errors in Workflows - **Python:** Code showing try/except with ActivityError, uses `workflow.logger` -- **TypeScript:** Code showing try/catch with ApplicationFailure instanceof check -- **Decision:** ⚠️ **BUG:** TS uses `console.log` in workflow — should use `log` from `@temporalio/workflow` +- **TypeScript:** Code showing try/catch with ApplicationFailure instanceof check, uses `log` +- **Decision:** ✅ FIXED — TS now uses `log` from `@temporalio/workflow` --- #### Retry Configuration - **Python:** Code + note about preferring defaults ("Only set options... if you have a domain-specific reason to") -- **TypeScript:** Code only -- **Decision:** ⚠️ needs fix — TS should add note about preferring defaults +- **TypeScript:** Code + note about preferring defaults +- **Decision:** ✅ FIXED — TS now has note about preferring defaults --- @@ -451,24 +454,24 @@ Shows which sections exist in each language. Organized by file. --- -#### Workflow Failure (Python only) +#### Workflow Failure - **Python:** Code + note about not using non_retryable in workflows -- **TypeScript:** N/A -- **Decision:** TODO — Add TS equivalent with nonRetryable warning +- **TypeScript:** Code + note about not using nonRetryable in workflows +- **Decision:** ✅ FIXED — TS now has Workflow Failure section --- -#### Cancellation Handling in Activities (TypeScript only) +#### Cancellation Handling in Activities - **Python:** N/A -- **TypeScript:** Code showing CancelledFailure handling with heartbeat -- **Decision:** DEL from error-handling.md — Move to patterns.md (Python already has Cancellation Handling there) +- **TypeScript:** N/A (removed from error-handling.md) +- **Decision:** ✅ DONE — Removed from error-handling.md (patterns.md has Cancellation Scopes) --- -#### Idempotency Patterns (TypeScript only) +#### Idempotency Patterns (error-handling.md) - **Python:** N/A (brief mention in Best Practices, references core/patterns.md) -- **TypeScript:** Detailed section with WHY + Using Keys + Granular Activities subsections -- **Decision:** DEL — Remove from TS, replace with reference to core (like Python does) +- **TypeScript:** Brief reference to core/patterns.md +- **Decision:** ✅ DONE — Replaced detailed section with brief reference to core --- @@ -483,24 +486,24 @@ Shows which sections exist in each language. Organized by file. **Note:** These files have very different structures. TypeScript has 11 detailed sections with code examples. Python has 5 sections and references other docs more heavily. -#### Idempotency (TypeScript only) +#### Idempotency - **Python:** N/A (covered in core/patterns.md) -- **TypeScript:** BAD/GOOD code example -- **Decision:** DEL — Remove from TypeScript (Core coverage sufficient) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript (Core coverage sufficient) --- -#### Replay Safety (TypeScript only) +#### Replay Safety - **Python:** N/A -- **TypeScript:** Subsections for Side Effects and Non-Deterministic Operations with code -- **Decision:** DEL — Remove from TypeScript (Core coverage sufficient) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript (Core coverage sufficient) --- -#### Query Handlers (TypeScript only) +#### Query Handlers - **Python:** N/A -- **TypeScript:** Subsections for Modifying State and Blocking in Queries -- **Decision:** DEL — Remove from TypeScript (Core coverage sufficient) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript (Core coverage sufficient) --- @@ -532,24 +535,24 @@ Shows which sections exist in each language. Organized by file. --- -#### Error Handling (TypeScript only) +#### Error Handling (gotchas.md) - **Python:** N/A (references error-handling.md) -- **TypeScript:** Subsections for Swallowing Errors and Wrong Retry Classification -- **Decision:** all good (Python references separate file) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript gotchas.md (covered in core and error-handling.md) --- #### Wrong Retry Classification - **Python:** Brief note referencing error-handling.md -- **TypeScript:** N/A -- **Decision:** TODO — Add brief note + reference to TypeScript (like Python has) +- **TypeScript:** Brief note + code example + reference to error-handling.md +- **Decision:** ✅ FIXED — Added to TypeScript --- -#### Retry Policies (TypeScript only) +#### Retry Policies - **Python:** N/A -- **TypeScript:** "Too Aggressive" subsection with code -- **Decision:** DEL — Remove from TypeScript (Core coverage sufficient) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript (Core coverage sufficient) --- @@ -574,10 +577,10 @@ Shows which sections exist in each language. Organized by file. --- -#### Timers and Sleep (TypeScript only) -- **Python:** N/A +#### Timers and Sleep +- **Python:** "Using asyncio.sleep" BAD/GOOD example - **TypeScript:** "Using JavaScript setTimeout" BAD/GOOD example -- **Decision:** TODO — Add Python equivalent section for `asyncio.sleep` vs `workflow.sleep` +- **Decision:** ✅ FIXED — Added to Python --- @@ -604,10 +607,10 @@ Shows which sections exist in each language. Organized by file. --- -#### OpenTelemetry Integration (TypeScript only) +#### OpenTelemetry Integration - **Python:** N/A -- **TypeScript:** Setup + Worker Configuration subsections with full code -- **Decision:** DEL — Remove from TypeScript (too detailed for reference docs) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript (too detailed for reference docs) --- @@ -625,10 +628,10 @@ Shows which sections exist in each language. Organized by file. --- -#### Debugging with Event History (TypeScript only) +#### Debugging with Event History - **Python:** N/A -- **TypeScript:** Viewing Event History, Key Events table, Debugging Non-Determinism -- **Decision:** DEL — Remove from TypeScript (too detailed) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript (too detailed) --- @@ -655,10 +658,10 @@ Shows which sections exist in each language. Organized by file. --- -#### Time Skipping (TypeScript only) +#### Time Skipping - **Python:** N/A (mentioned inline in environment section) -- **TypeScript:** Separate section with code examples -- **Decision:** DEL — Remove dedicated section from TypeScript (mention inline like Python) +- **TypeScript:** N/A (mentioned inline in environment section) +- **Decision:** ✅ DONE — Removed dedicated section from TypeScript (now inline like Python) --- @@ -676,10 +679,10 @@ Shows which sections exist in each language. Organized by file. --- -#### Testing Failure Cases (Python only) +#### Testing Failure Cases - **Python:** Code example with pytest.raises -- **TypeScript:** N/A -- **Decision:** TODO — Add TS equivalent section +- **TypeScript:** Code example with try/catch and WorkflowFailedError +- **Decision:** ✅ FIXED — Added to TypeScript --- @@ -690,10 +693,10 @@ Shows which sections exist in each language. Organized by file. --- -#### Activity Testing (Python only) +#### Activity Testing - **Python:** Code with ActivityEnvironment -- **TypeScript:** N/A -- **Decision:** TODO — Add TS equivalent for isolated activity testing +- **TypeScript:** Code with MockActivityEnvironment +- **Decision:** ✅ FIXED — Added to TypeScript --- @@ -748,9 +751,9 @@ Shows which sections exist in each language. Organized by file. #### Choosing a Strategy - **Core:** Decision table -- **Python:** N/A +- **Python:** Decision table - **TypeScript:** Decision table -- **Decision:** TODO — Add decision table to Python +- **Decision:** ✅ FIXED — Added decision table to Python --- @@ -787,17 +790,17 @@ Shows which sections exist in each language. Organized by file. --- -#### How Temporal Works: History Replay (TypeScript only) +#### How Temporal Works: History Replay - **Python:** N/A (covered in core/determinism.md) -- **TypeScript:** Detailed explanation with Commands/Events table, When Replay Occurs -- **Decision:** DEL — Remove from TypeScript; both languages should reference core/determinism.md +- **TypeScript:** N/A (removed, replaced with brief "Understanding Replay" section) +- **Decision:** ✅ DONE — Removed from TypeScript; now references core/determinism.md --- #### Quick Start / Quick Demo - **Python:** Full multi-file example with activities, workflows, worker, starter -- **TypeScript:** Shorter example with activities, workflows, worker -- **Decision:** TODO — Expand TypeScript to match Python (see detailed gaps below) +- **TypeScript:** Full multi-file example with activities, workflows, worker, client +- **Decision:** ✅ FIXED — Expanded TypeScript to match Python (see detailed gaps now resolved) --- @@ -815,10 +818,10 @@ Shows which sections exist in each language. Organized by file. --- -#### File Organization Best Practice (Python only) +#### File Organization Best Practice - **Python:** Directory structure + code example for sandbox imports -- **TypeScript:** N/A -- **Decision:** TODO — Add to TypeScript with bundling/import guidance +- **TypeScript:** Directory structure + code example for type-only imports +- **Decision:** ✅ FIXED — Added to TypeScript with bundling/import guidance --- @@ -838,8 +841,8 @@ Shows which sections exist in each language. Organized by file. #### Additional Resources - **Python:** Correct references to python files -- **TypeScript:** ⚠️ BUG — all references point to `references/python/` instead of `references/typescript/` -- **Decision:** FIX REQUIRED — update TypeScript file paths +- **TypeScript:** Correct references to typescript files +- **Decision:** ✅ FIXED — Updated TypeScript file paths --- @@ -1050,10 +1053,10 @@ Shows which sections exist in each language. Organized by file. **Order alignment:** ✓ Aligned — TS# monotonically increases -**Style alignment issues:** -- ⚠️ **Queries:** TS missing "Important: must NOT modify state" note -- ⚠️ **Saga Pattern:** TS missing idempotency note, missing critical comments about saving compensation BEFORE activity -- ⚠️ **Saga Pattern BUG:** TS uses `console.log` — should use `log` from `@temporalio/workflow` +**Style alignment:** ✅ All issues fixed +- ✅ **Queries:** TS now has "Important: must NOT modify state" note +- ✅ **Saga Pattern:** TS now has idempotency note, comments about saving compensation BEFORE activity +- ✅ **Saga Pattern:** TS now uses `log` from `@temporalio/workflow` ### data-handling.md @@ -1066,12 +1069,7 @@ Shows which sections exist in each language. Organized by file. - Protobuf Support: TS-specific section (Python handles protobufs via default converter) - Deterministic APIs for Values: Python-specific (`workflow.uuid4()`, `workflow.random()`) -**Order alignment:** ⚠️ NOT ALIGNED — TS# column is not monotonic (5, 6, 3, 4...) -- Search Attributes: Py#6, TS#3 -- Workflow Memo: Py#7, TS#4 -- Custom Data Converter: Py#4, TS#5 -- Payload Encryption: Py#5, TS#6 -- **Action:** Reorder TypeScript to match Python order +**Order alignment:** ✅ ALIGNED — Reordered TypeScript to match Python order **Style alignment:** All TypeScript sections aligned with Python. No changes needed. @@ -1092,14 +1090,14 @@ Shows which sections exist in each language. Organized by file. **Order alignment:** ✓ Aligned — TS# monotonically increases -**Action items:** -- **TypeScript TODO:** Add Workflow Failure section with nonRetryable warning -- **TypeScript DEL:** Move Cancellation Handling in Activities to patterns.md (Python already has it there) -- Idempotency Patterns: Python references core/patterns.md which is appropriate; no change needed +**Action items:** ✅ All completed +- ✅ **TypeScript:** Added Workflow Failure section with nonRetryable warning +- ✅ **TypeScript:** Removed Cancellation Handling in Activities section +- ✅ **TypeScript:** Replaced Idempotency Patterns with brief reference to core -**Style alignment issues:** -- ⚠️ **Handling Activity Errors BUG:** TS uses `console.log` — should use `log` from `@temporalio/workflow` -- ⚠️ **Retry Configuration:** TS missing note about preferring defaults +**Style alignment:** ✅ All issues fixed +- ✅ **Handling Activity Errors:** TS now uses `log` from `@temporalio/workflow` +- ✅ **Retry Configuration:** TS now has note about preferring defaults ### gotchas.md @@ -1122,12 +1120,12 @@ Shows which sections exist in each language. Organized by file. **Order alignment:** N/A after cleanup — Core has conceptual sections, language files have implementation-specific sections -**Action items:** -- **TypeScript DEL:** Remove Idempotency, Replay Safety, Retry Policies, Query Handlers, Error Handling (move to Core only) -- **TypeScript TODO:** Add Wrong Retry Classification section (brief note + reference, like Python) -- **Python TODO:** Add Timers and Sleep section for `asyncio.sleep` vs `workflow.sleep` gotcha +**Action items:** ✅ All completed +- ✅ **TypeScript DEL:** Removed Idempotency, Replay Safety, Retry Policies, Query Handlers, Error Handling +- ✅ **TypeScript:** Added Wrong Retry Classification section +- ✅ **Python:** Added Timers and Sleep section -**Style alignment:** After changes: +**Style alignment:** ✅ Complete: - Core: 8 conceptual sections with symptoms/fixes (authoritative for cross-cutting concerns) - TypeScript: 7 sections (Activity Imports, Bundling, Cancellation, Heartbeating, Testing, Timers, Wrong Retry Classification) - Python: 5 sections (File Organization, Async vs Sync, Wrong Retry Classification, Heartbeating, Testing) @@ -1147,11 +1145,11 @@ Shows which sections exist in each language. Organized by file. **Order alignment:** ✓ Aligned — TS# monotonically increases (Py# 2 maps to both TS# 2 and 3, but order preserved) -**Action items:** -- DEL: Remove OpenTelemetry Integration from TypeScript (too detailed) -- DEL: Remove Debugging with Event History from TypeScript (too detailed) +**Action items:** ✅ All completed +- ✅ DEL: Removed OpenTelemetry Integration from TypeScript +- ✅ DEL: Removed Debugging with Event History from TypeScript -**Style alignment:** Mostly aligned. After removing both sections, TS will be more concise like Python. +**Style alignment:** ✅ Complete. TS is now concise like Python. ### testing.md @@ -1166,17 +1164,14 @@ Shows which sections exist in each language. Organized by file. **Sections marked DEL:** - Time Skipping: Remove dedicated section from TypeScript (mention inline like Python) -**Order alignment:** ⚠️ NOT ALIGNED — TS# not monotonic -- Testing Signals and Queries: Py#4 → TS#6 -- Replay Testing: Py#6 → TS#5 -- TS has: 1, 2, 3, 4, 6, 5, 7 (jumps 4→6, then back to 5) +**Order alignment:** ✅ ALIGNED — Reordered TS sections to match Python order -**Action items:** -- TODO: Add Testing Failure Cases section to TypeScript -- TODO: Add Activity Testing section to TypeScript -- Reorder TS sections to match Python order (Signals/Queries before Replay) +**Action items:** ✅ All completed +- ✅ Added Testing Failure Cases section to TypeScript +- ✅ Added Activity Testing section to TypeScript +- ✅ Reordered TS sections (Signals/Queries before Replay) -**Style alignment:** Mostly aligned. Python has more sections (Failure Cases, Activity Testing) — adding to TS. +**Style alignment:** ✅ Complete. Python and TypeScript now have matching sections. ### versioning.md @@ -1190,8 +1185,8 @@ Shows which sections exist in each language. Organized by file. **Order alignment:** ✓ Aligned — all three files follow same order (Overview, Why, Patching, Type Versioning, Worker Versioning, Best Practices) -**Action items:** -- TODO: Add Choosing a Strategy decision table to Python versioning.md +**Action items:** ✅ All completed +- ✅ Added Choosing a Strategy decision table to Python versioning.md **Decided to keep as-is:** - Common Mistakes: Core-only (conceptual coverage sufficient) @@ -1221,34 +1216,30 @@ Shows which sections exist in each language. Organized by file. - TS has separate "Determinism Rules" section; Python has it as Key Concepts subsection - Python has "File Organization" section that TS doesn't have -**Action items:** -- **FIX BUG:** TypeScript Additional Resources section has wrong paths (all say `references/python/` instead of `references/typescript/`) -- **TypeScript DEL:** Remove "How Temporal Works: History Replay" section (reference core/determinism.md instead) -- **TypeScript TODO:** Add "File Organization Best Practice" section - -**Detailed gaps in TypeScript Quick Start (vs Python Quick Demo):** -- ⚠️ Missing: "Add Dependency" instruction (`npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity`) -- ⚠️ Missing: File descriptions explaining purpose (e.g., "separate file for performance") -- ⚠️ Missing: "Start the dev server" instruction (`temporal server start-dev`) -- ⚠️ Missing: "Start the worker" instruction (`npx ts-node worker.ts`) -- ⚠️ Missing: starter.ts file showing how to execute a workflow from client -- ⚠️ Missing: "Run the workflow" instruction with expected output - -**Detailed gaps in TypeScript Key Concepts (vs Python):** -- Activity Definition: Python has 5 bullets, TS has 3 bullets - - ⚠️ Missing: `heartbeat()` mention for long operations - - N/A: sync vs async guidance (TS activities are always async) - -**Detailed gaps in TypeScript Common Pitfalls (vs Python):** -- Python has 7 items, TypeScript has 5 items -- ⚠️ Missing: "Forgetting to heartbeat" — important for long-running activities -- ⚠️ Missing: "Using console.log in workflows" — should use `log` from `@temporalio/workflow` for replay-safe logging - -**Style alignment:** ⚠️ TypeScript significantly less comprehensive -- Python Quick Demo: Full tutorial with dependency install, 4 files, run instructions, expected output -- TypeScript Quick Start: Just 3 code blocks with no context or instructions -- Python Key Concepts: More detailed Activity Definition guidance -- Python Common Pitfalls: More items including heartbeat and logging +**Action items:** ✅ All completed +- ✅ **FIX BUG:** Fixed TypeScript Additional Resources paths +- ✅ **TypeScript DEL:** Removed "How Temporal Works: History Replay" section (now brief "Understanding Replay") +- ✅ **TypeScript:** Added "File Organization Best Practice" section + +**TypeScript Quick Start gaps:** ✅ All fixed +- ✅ Added "Add Dependency" instruction +- ✅ Added File descriptions explaining purpose +- ✅ Added "Start the dev server" instruction +- ✅ Added "Start the worker" instruction +- ✅ Added client.ts file showing how to execute a workflow +- ✅ Added "Run the workflow" instruction with expected output + +**TypeScript Key Concepts gaps:** ✅ Fixed +- ✅ Added `heartbeat()` mention for long operations + +**TypeScript Common Pitfalls gaps:** ✅ All fixed +- ✅ Added "Forgetting to heartbeat" pitfall +- ✅ Added "Using console.log in workflows" pitfall + +**Style alignment:** ✅ Complete +- Python Quick Demo and TypeScript Quick Start now match (full tutorial with 4 files, run instructions, expected output) +- Python and TypeScript Key Concepts now aligned +- Python and TypeScript Common Pitfalls now aligned ### determinism-protection.md @@ -1303,20 +1294,20 @@ Shows which sections exist in each language. Organized by file. | Section | Core | Python | Py# | TypeScript | TS# | Go | |---------|------|--------|-----|------------|-----|-----| -| Schedules | — | ✓ | 1 | ✓ | 5 | | -| Async Activity Completion | — | ✓ | 2 | TODO | | | +| Schedules | — | ✓ | 1 | ✓ | 1 | | +| Async Activity Completion | — | ✓ | 2 | ✓ | 2 | | | Sandbox Customization | — | ✓ | 3 | — | — | | | Gevent Compatibility Warning | — | ✓ | 4 | — | — | | -| Worker Tuning | — | ✓ | 5 | TODO | | | +| Worker Tuning | — | ✓ | 5 | ✓ | 3 | | | Workflow Init Decorator | — | ✓ | 6 | — | — | | | Workflow Failure Exception Types | — | ✓ | 7 | — | — | | -| Continue-as-New | — | — | — | DEL | 1 | | -| Workflow Updates | — | — | — | DEL | 2 | | -| Nexus Operations | — | — | — | DEL | 3 | | -| Activity Cancellation and Heartbeating | — | — | — | DEL | 4 | | -| Sinks | — | — | — | ✓ | 6 | | -| CancellationScope Patterns | — | — | — | DEL | 7 | | -| Best Practices | — | — | — | DEL | 8 | | +| Continue-as-New | — | — | — | — | — | | +| Workflow Updates | — | — | — | — | — | | +| Nexus Operations | — | — | — | — | — | | +| Activity Cancellation and Heartbeating | — | — | — | — | — | | +| Sinks | — | — | — | ✓ | 4 | | +| CancellationScope Patterns | — | — | — | — | — | | +| Best Practices | — | — | — | — | — | | --- @@ -1331,8 +1322,8 @@ Shows which sections exist in each language. Organized by file. #### Async Activity Completion - **Python:** Detailed section with task_token pattern, external completion code -- **TypeScript:** N/A (not in advanced-features.md) -- **Decision:** TODO — Add to TypeScript +- **TypeScript:** Detailed section with task_token pattern, external completion code +- **Decision:** ✅ FIXED — Added to TypeScript --- @@ -1352,8 +1343,8 @@ Shows which sections exist in each language. Organized by file. #### Worker Tuning - **Python:** Code example with max_concurrent_*, activity_executor, graceful_shutdown_timeout -- **TypeScript:** N/A (not in advanced-features.md) -- **Decision:** TODO — Add to TypeScript +- **TypeScript:** Code example with max_concurrent_*, shutdown timeout, cache settings +- **Decision:** ✅ FIXED — Added to TypeScript --- @@ -1371,31 +1362,31 @@ Shows which sections exist in each language. Organized by file. --- -#### Continue-as-New (TypeScript - DUPLICATE) +#### Continue-as-New - **Python:** N/A (in patterns.md) -- **TypeScript:** Full code example in advanced-features.md -- **Decision:** DEL — Already in patterns.md (TS#10). Remove from advanced-features.md +- **TypeScript:** N/A (removed, covered in patterns.md) +- **Decision:** ✅ DONE — Removed from advanced-features.md (already in patterns.md) --- -#### Workflow Updates (TypeScript - DUPLICATE) +#### Workflow Updates - **Python:** N/A (in patterns.md) -- **TypeScript:** Full code example with validators, client calling -- **Decision:** DEL — Already in patterns.md (TS#5). Remove from advanced-features.md +- **TypeScript:** N/A (removed, covered in patterns.md) +- **Decision:** ✅ DONE — Removed from advanced-features.md (already in patterns.md) --- -#### Nexus Operations (TypeScript only) +#### Nexus Operations - **Python:** N/A -- **TypeScript:** WHY/WHEN + service definition + handlers + workflow calling -- **Decision:** DEL — Remove from TypeScript (too advanced for reference docs) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript (too advanced for reference docs) --- -#### Activity Cancellation and Heartbeating (TypeScript only) +#### Activity Cancellation and Heartbeating - **Python:** N/A (Heartbeat Details in patterns.md) -- **TypeScript:** ActivityCancellationType + Heartbeat Details for resumption -- **Decision:** DEL — Remove from TypeScript (Heartbeat Details already in patterns.md; ActivityCancellationType not needed) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript (Heartbeat Details already in patterns.md) --- @@ -1406,17 +1397,17 @@ Shows which sections exist in each language. Organized by file. --- -#### CancellationScope Patterns (TypeScript - DUPLICATE) +#### CancellationScope Patterns - **Python:** N/A (has Cancellation Handling in patterns.md) -- **TypeScript:** nonCancellable + cancellable scope patterns -- **Decision:** DEL — Already in patterns.md as "Cancellation Scopes" (TS#12). Remove from advanced-features.md +- **TypeScript:** N/A (removed, covered in patterns.md) +- **Decision:** ✅ DONE — Removed from advanced-features.md (already in patterns.md) --- -#### Best Practices (TypeScript only) +#### Best Practices (advanced-features.md) - **Python:** N/A -- **TypeScript:** 7 items covering continue-as-new, updates, sinks, cancellation -- **Decision:** DEL — Remove from TypeScript (best practices covered in individual sections) +- **TypeScript:** N/A (removed) +- **Decision:** ✅ DONE — Removed from TypeScript (best practices covered in individual sections) --- @@ -1449,15 +1440,15 @@ Shows which sections exist in each language. Organized by file. **Order alignment:** N/A — files have very different structures; TS has many duplicates that should be removed -**Action items:** -1. **TypeScript DEL:** Remove Continue-as-New, Workflow Updates, CancellationScope Patterns (duplicates from patterns.md) -2. **TypeScript DEL:** Remove Nexus Operations (too advanced) -3. **TypeScript DEL:** Remove Activity Cancellation and Heartbeating (Heartbeat Details in patterns.md; ActivityCancellationType not needed) -4. **TypeScript DEL:** Remove Best Practices (covered in individual sections) -5. **TypeScript TODO:** Add Async Activity Completion -6. **TypeScript TODO:** Add Worker Tuning - -**Style alignment:** After changes: +**Action items:** ✅ All completed +1. ✅ **TypeScript DEL:** Removed Continue-as-New, Workflow Updates, CancellationScope Patterns +2. ✅ **TypeScript DEL:** Removed Nexus Operations +3. ✅ **TypeScript DEL:** Removed Activity Cancellation and Heartbeating +4. ✅ **TypeScript DEL:** Removed Best Practices +5. ✅ **TypeScript:** Added Async Activity Completion +6. ✅ **TypeScript:** Added Worker Tuning + +**Style alignment:** ✅ Complete: - Python: 7 sections (Schedules, Async Activity Completion, Sandbox Customization, Gevent Warning, Worker Tuning, Workflow Init, Failure Exception Types) - TypeScript: 4 sections (Schedules, Async Activity Completion, Worker Tuning, Sinks) - Both serve as "miscellaneous advanced topics" not covered elsewhere From 2dd0711bb6e4c2a4bebda76f7cc40d9c30794644 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 25 Feb 2026 20:11:35 -0500 Subject: [PATCH 09/50] clean up tmp files --- references/python/gotchas.edits.md | 63 ------ references/python/versioning.edits.md | 47 ---- .../typescript/advanced-features.edits.md | 173 --------------- references/typescript/data-handling.edits.md | 55 ----- references/typescript/determinism.edits.md | 53 ----- references/typescript/editing_prompt.md | 21 -- references/typescript/error-handling.edits.md | 113 ---------- references/typescript/observability.edits.md | 50 ----- references/typescript/patterns.edits.md | 93 -------- references/typescript/testing.edits.md | 151 ------------- references/typescript/typescript.edits.md | 204 ------------------ 11 files changed, 1023 deletions(-) delete mode 100644 references/python/gotchas.edits.md delete mode 100644 references/python/versioning.edits.md delete mode 100644 references/typescript/advanced-features.edits.md delete mode 100644 references/typescript/data-handling.edits.md delete mode 100644 references/typescript/determinism.edits.md delete mode 100644 references/typescript/editing_prompt.md delete mode 100644 references/typescript/error-handling.edits.md delete mode 100644 references/typescript/observability.edits.md delete mode 100644 references/typescript/patterns.edits.md delete mode 100644 references/typescript/testing.edits.md delete mode 100644 references/typescript/typescript.edits.md diff --git a/references/python/gotchas.edits.md b/references/python/gotchas.edits.md deleted file mode 100644 index 33cd1c0..0000000 --- a/references/python/gotchas.edits.md +++ /dev/null @@ -1,63 +0,0 @@ -# Python gotchas.md Edits - -## Status: DONE - ---- - -## Content to ADD - -### 1. Timers and Sleep section - -**Location:** After "Testing" section (at the end of file) - -**Add this section:** -```markdown -## Timers and Sleep - -### Using asyncio.sleep - -```python -# BAD: asyncio.sleep is not deterministic during replay -import asyncio - -@workflow.defn -class BadWorkflow: - @workflow.run - async def run(self) -> None: - await asyncio.sleep(60) # Non-deterministic! -``` - -```python -# GOOD: Use workflow.sleep for deterministic timers -from temporalio import workflow -from datetime import timedelta - -@workflow.defn -class GoodWorkflow: - @workflow.run - async def run(self) -> None: - await workflow.sleep(timedelta(seconds=60)) # Deterministic - # Or with string duration: - await workflow.sleep("1 minute") -``` - -**Why this matters:** `asyncio.sleep` uses the system clock, which differs between original execution and replay. `workflow.sleep` creates a durable timer in the event history, ensuring consistent behavior during replay. -``` - ---- - -## Content to DELETE - -None. - ---- - -## Content to FIX - -None. - ---- - -## Order Changes - -None - Python gotchas.md order is the reference order. diff --git a/references/python/versioning.edits.md b/references/python/versioning.edits.md deleted file mode 100644 index 73c8d06..0000000 --- a/references/python/versioning.edits.md +++ /dev/null @@ -1,47 +0,0 @@ -# Python versioning.md Edits - -## Status: DONE - ---- - -## Content to ADD - -### 1. Choosing a Strategy section - -**Location:** After "Worker Versioning" section, before "Best Practices" - -**Add this section:** -```markdown -## Choosing a Strategy - -| Scenario | Recommended Approach | -|----------|---------------------| -| Minor bug fix, compatible change | Patching API (`patched()`) | -| Major logic change, incompatible | Workflow Type Versioning (new workflow name) | -| Infrastructure change, gradual rollout | Worker Versioning (Build ID) | -| Need to query/signal old workflows | Patching (keeps same workflow type) | -| Clean break, no backward compatibility | Workflow Type Versioning | - -**Decision factors:** -- **Patching API**: Best for incremental changes where you need to maintain the same workflow type and can gradually migrate -- **Workflow Type Versioning**: Best for major changes where a clean break is acceptable -- **Worker Versioning**: Best for infrastructure-level changes or when you need fine-grained deployment control -``` - ---- - -## Content to DELETE - -None. - ---- - -## Content to FIX - -None. - ---- - -## Order Changes - -None - Python versioning.md order is already the reference order. diff --git a/references/typescript/advanced-features.edits.md b/references/typescript/advanced-features.edits.md deleted file mode 100644 index 8e53696..0000000 --- a/references/typescript/advanced-features.edits.md +++ /dev/null @@ -1,173 +0,0 @@ -# TypeScript advanced-features.md Edits - -## Status: DONE - ---- - -## Content to DELETE - -### 1. Continue-as-New section - -**Location:** Entire "Continue-as-New" section - -**Action:** DELETE this entire section. - -**Reason:** DUPLICATE - Already exists in patterns.md (TS#10). Remove from advanced-features.md. - ---- - -### 2. Workflow Updates section - -**Location:** Entire "Workflow Updates" section - -**Action:** DELETE this entire section. - -**Reason:** DUPLICATE - Already exists in patterns.md (TS#5 "Updates"). Remove from advanced-features.md. - ---- - -### 3. Nexus Operations section - -**Location:** Entire "Nexus Operations" section (includes service definition, handlers, workflow calling) - -**Action:** DELETE this entire section. - -**Reason:** Too advanced for reference docs. Users needing Nexus should consult official Temporal documentation. - ---- - -### 4. Activity Cancellation and Heartbeating section - -**Location:** Entire "Activity Cancellation and Heartbeating" section (includes ActivityCancellationType, Heartbeat Details) - -**Action:** DELETE this entire section. - -**Reason:** -- Heartbeat Details already in patterns.md (TS#16) -- ActivityCancellationType coverage not needed per user decision - ---- - -### 5. CancellationScope Patterns section - -**Location:** Entire "CancellationScope Patterns" section - -**Action:** DELETE this entire section. - -**Reason:** DUPLICATE - Already exists in patterns.md (TS#12 "Cancellation Scopes"). Remove from advanced-features.md. - ---- - -### 6. Best Practices section - -**Location:** Entire "Best Practices" section at the end - -**Action:** DELETE this entire section. - -**Reason:** Best practices are covered in individual sections throughout the reference docs. A generic list here is redundant. - ---- - -## Content to ADD - -### 7. Async Activity Completion section - -**Location:** After "Schedules" section - -**Add this section:** -```markdown -## Async Activity Completion - -Complete an activity asynchronously from outside the activity function. Useful when the activity needs to wait for an external event. - -**In the activity - return the task token:** -```typescript -import { Context } from '@temporalio/activity'; - -export async function asyncActivity(): Promise { - const ctx = Context.current(); - const taskToken = ctx.info.taskToken; - - // Store taskToken somewhere (database, queue, etc.) - await saveTaskToken(taskToken); - - // Throw to indicate async completion - throw Context.current().createAsyncCompletionHandle(); -} -``` - -**External completion (from another process):** -```typescript -import { Client } from '@temporalio/client'; - -async function completeActivity(taskToken: Uint8Array, result: string) { - const client = new Client(); - - await client.activity.complete(taskToken, result); - // Or for failure: - // await client.activity.fail(taskToken, new Error('Failed')); -} -``` - -**When to use:** -- Waiting for human approval -- Waiting for external webhook callback -- Long-polling external systems -``` - ---- - -### 8. Worker Tuning section - -**Location:** After "Async Activity Completion" section - -**Add this section:** -```markdown -## Worker Tuning - -Configure worker capacity for production workloads: - -```typescript -import { Worker, NativeConnection } from '@temporalio/worker'; - -const worker = await Worker.create({ - connection: await NativeConnection.connect({ address: 'temporal:7233' }), - taskQueue: 'my-queue', - workflowsPath: require.resolve('./workflows'), - activities, - - // Workflow execution concurrency - maxConcurrentWorkflowTaskExecutions: 100, - - // Activity execution concurrency - maxConcurrentActivityTaskExecutions: 100, - - // Graceful shutdown timeout - shutdownGraceTime: '30 seconds', - - // Enable sticky workflow cache (default: true) - enableStickyQueues: true, - - // Max cached workflows (memory vs latency tradeoff) - maxCachedWorkflows: 1000, -}); -``` - -**Key settings:** -- `maxConcurrentWorkflowTaskExecutions`: Max workflows running simultaneously -- `maxConcurrentActivityTaskExecutions`: Max activities running simultaneously -- `shutdownGraceTime`: Time to wait for in-progress work before forced shutdown -- `maxCachedWorkflows`: Number of workflows to keep in cache (reduces replay on cache hit) -``` - ---- - -## Order Changes - -After deletions and additions, the sections should be: -1. Schedules -2. Async Activity Completion (new) -3. Worker Tuning (new) -4. Sinks (TS-specific, keep) - -This provides a focused advanced-features file matching the pattern of Python's version. diff --git a/references/typescript/data-handling.edits.md b/references/typescript/data-handling.edits.md deleted file mode 100644 index b12cbf8..0000000 --- a/references/typescript/data-handling.edits.md +++ /dev/null @@ -1,55 +0,0 @@ -# TypeScript data-handling.md Edits - -## Status: DONE - ---- - -## Order Changes - -**Issue:** TypeScript section order does not match Python order. - -**Current order (by section):** -1. Overview -2. Default Data Converter -3. Search Attributes (Py#6) -4. Workflow Memo (Py#7) -5. Custom Data Converter (Py#4) -6. Payload Encryption (Py#5) -7. Protobuf Support -8. Large Payloads -9. Best Practices - -**Target order (matching Python):** -1. Overview -2. Default Data Converter -3. Custom Data Converter -4. Payload Encryption -5. Search Attributes -6. Workflow Memo -7. Protobuf Support (TS-specific, keep at end) -8. Large Payloads -9. Best Practices - -**Action:** Reorder sections to match Python order: -- Move "Custom Data Converter" up to position 3 -- Move "Payload Encryption" up to position 4 -- Move "Search Attributes" down to position 5 -- Move "Workflow Memo" down to position 6 - ---- - -## Content to FIX - -None. - ---- - -## Content to DELETE - -None. - ---- - -## Content to ADD - -None. diff --git a/references/typescript/determinism.edits.md b/references/typescript/determinism.edits.md deleted file mode 100644 index 8b05274..0000000 --- a/references/typescript/determinism.edits.md +++ /dev/null @@ -1,53 +0,0 @@ -# TypeScript determinism.md Edits - -## Status: DONE - ---- - -## Content to ADD - -### 1. uuid4() Utility - -**Location:** After "Temporal's V8 Sandbox" section, before "Forbidden Operations" - -**Add this section:** -```markdown -## Deterministic UUID Generation - -Generate deterministic UUIDs safe to use in workflows. Uses the workflow seeded PRNG, so the same UUID is generated during replay. - -```typescript -import { uuid4 } from '@temporalio/workflow'; - -export async function workflowWithIds(): Promise { - const childWorkflowId = uuid4(); - await executeChild(childWorkflow, { - workflowId: childWorkflowId, - args: [input], - }); -} -``` - -**When to use:** -- Generating unique IDs for child workflows -- Creating idempotency keys -- Any situation requiring unique identifiers in workflow code -``` - ---- - -## Content to DELETE - -None. - ---- - -## Content to FIX - -None. - ---- - -## Order Changes - -None - order is aligned. diff --git a/references/typescript/editing_prompt.md b/references/typescript/editing_prompt.md deleted file mode 100644 index 807fb23..0000000 --- a/references/typescript/editing_prompt.md +++ /dev/null @@ -1,21 +0,0 @@ -I'm working on building a Skill for developing temporal code. The skill is rooted at plugins/temporal-developer/skills/temporal-developer/. - -It is broken down into 4 components: -- SKILL.md: the top-level skill info, getting started -- references/core/: All the language-agnostic documentation -- references/python/: the python specific documentation -- references/typescript/: the typescript specific documentation - -Generally, core describes concepts, such as conceptual patterns, common gotchas, etc. The language-specific directories then show concrete examples of those concepts, in the language. - -Currently, SKILL.md, core, and python are complete and in a good state. I'm working on the typescript specific documentation now. - -**I want you to help me edit references/typescript/patterns.md**. This should be parallel structure to references/core/patterns.md and references/python/patterns.md, but with typescript specific examples. - -Right now it already has a lot of content, but it has not yet been reviewed or polished. Please take these steps: -1. Review it, in comparison to references/core/patterns.md and references/python/patterns.md for overall structure and content. Note any gaps that it has relative to those. Also note content it has that is not in Python. -2. Based on that review, in references/typescript/patterns.edits.md, create a list of content that should be added and/or removed. Note that there *may* be some content that is only relevant for TypeScript, and it may thus be appropriate to have it even if it doesn't correspond to Python or core. Conversely, some content in Python may not be applicable to TypeScript. -3. Consult with me on the list of content to add and/or remove. -4. Once we agree on the list, now you should edit references/typescript/patterns.md to add and/or remove the content. -5. Consult with me again. We may loop some here on additional edits. -6. Finally, you will need to do a pass for **correctness** in the content. At this point, you should use the context7 mcp server with /temporalio/sdk-typescript and temporal-docs mcp server to verify correctness of every bit of content. \ No newline at end of file diff --git a/references/typescript/error-handling.edits.md b/references/typescript/error-handling.edits.md deleted file mode 100644 index b683dc8..0000000 --- a/references/typescript/error-handling.edits.md +++ /dev/null @@ -1,113 +0,0 @@ -# TypeScript error-handling.md Edits - -## Status: DONE - ---- - -## BUGS to FIX - -### 1. Handling Activity Errors - Replace console.log with workflow logger - -**Location:** Inside the catch block of the activity error handling example - -**Current code:** -```typescript -} catch (err) { - console.log('Activity failed', err); - // ... -} -``` - -**Change to:** -```typescript -import { log } from '@temporalio/workflow'; - -// ... in catch block: -} catch (err) { - log.warn('Activity failed', { error: err }); - // ... -} -``` - -**WHY:** `console.log` is not replay-safe. Use `log` from `@temporalio/workflow` for replay-aware logging in workflows. - ---- - -## Content to FIX - -### 2. Retry Configuration - Add note about preferring defaults - -**Location:** After the Retry Configuration code example - -**Add this note:** -```markdown -**Note:** Only set retry options if you have a domain-specific reason to. The defaults are suitable for most use cases. -``` - ---- - -## Content to DELETE - -### 3. Cancellation Handling in Activities - -**Location:** Entire "Cancellation Handling in Activities" section - -**Action:** DELETE this entire section. - -**Reason:** Move to patterns.md. Python already has Cancellation Handling in patterns.md. Content should be consolidated there. - -**Note:** When adding to patterns.md, it should complement the existing "Cancellation Scopes" section. - ---- - -### 4. Idempotency Patterns - -**Location:** Entire "Idempotency Patterns" section (includes WHY, Using Keys, Granular Activities subsections) - -**Action:** DELETE this entire section. - -**Reason:** Too detailed for error-handling.md. Replace with brief reference to core/patterns.md like Python does. - -**Replace with:** -```markdown -## Idempotency - -For idempotency patterns (using keys, making activities granular), see `core/patterns.md`. -``` - ---- - -## Content to ADD - -### 5. Workflow Failure section - -**Location:** After "Timeout Configuration" section, before "Best Practices" - -**Add this section:** -```markdown -## Workflow Failure - -Workflows can throw errors to indicate failure: - -```typescript -import { ApplicationFailure } from '@temporalio/workflow'; - -export async function myWorkflow(): Promise { - if (someCondition) { - throw ApplicationFailure.create({ - message: 'Workflow failed due to invalid state', - type: 'InvalidStateError', - }); - } - return 'success'; -} -``` - -**Warning:** Do NOT use `nonRetryable: true` for workflow failures in most cases. Unlike activities, workflow retries are controlled by the caller, not retry policies. Use `nonRetryable` only for errors that are truly unrecoverable (e.g., invalid input that will never be valid). -``` - ---- - -## Order Changes - -None - order is aligned after deletions. diff --git a/references/typescript/observability.edits.md b/references/typescript/observability.edits.md deleted file mode 100644 index d88c7b8..0000000 --- a/references/typescript/observability.edits.md +++ /dev/null @@ -1,50 +0,0 @@ -# TypeScript observability.md Edits - -## Status: DONE - ---- - -## Content to DELETE - -### 1. OpenTelemetry Integration section - -**Location:** Entire "OpenTelemetry Integration" section (includes "Setup" and "Worker Configuration" subsections) - -**Action:** DELETE this entire section. - -**Reason:** Too detailed for reference docs. Users needing OpenTelemetry should consult the official Temporal documentation. - ---- - -### 2. Debugging with Event History section - -**Location:** Entire "Debugging with Event History" section (includes "Viewing Event History", "Key Events" table, "Debugging Non-Determinism" subsections) - -**Action:** DELETE this entire section. - -**Reason:** Too detailed for reference docs. This is operational knowledge better suited for official documentation or tutorials. - ---- - -## Content to FIX - -None. - ---- - -## Content to ADD - -None. - ---- - -## Order Changes - -After deletions, the remaining sections should be: -1. Overview -2. Replay-Aware Logging -3. Customizing the Logger -4. Metrics -5. Best Practices - -This provides a focused observability reference matching Python's structure. diff --git a/references/typescript/patterns.edits.md b/references/typescript/patterns.edits.md deleted file mode 100644 index 0f1665f..0000000 --- a/references/typescript/patterns.edits.md +++ /dev/null @@ -1,93 +0,0 @@ -# TypeScript patterns.md Edits - -## Status: DONE - ---- - -## Content to FIX - -### 1. Queries - Add "Important" note - -**Location:** After the `## Queries` header, before the code block - -**Add this note:** -```markdown -**Important:** Queries must NOT modify workflow state or have side effects. -``` - ---- - -### 2. Saga Pattern - Add idempotency note - -**Location:** After the `## Saga Pattern` header, before the code block - -**Add this note:** -```markdown -**Important:** Compensation activities should be idempotent. -``` - ---- - -### 3. Saga Pattern - Add compensation comments - -**Location:** Inside the saga workflow code example - -**Current code (simplified):** -```typescript -await reserveInventory(order); -compensations.push(() => releaseInventory(order)); -``` - -**Change to:** -```typescript -// IMPORTANT: Save compensation BEFORE calling the activity -// If activity fails after completing but before returning, -// compensation must still be registered -await reserveInventory(order); -compensations.push(() => releaseInventory(order)); -``` - ---- - -## BUGS to FIX - -### 4. Saga Pattern - Replace console.log with workflow logger - -**Location:** Inside the catch block of the saga workflow - -**Current code:** -```typescript -} catch (compErr) { - console.log('Compensation failed', compErr); -} -``` - -**Change to:** -```typescript -import { log } from '@temporalio/workflow'; - -// ... in catch block: -} catch (compErr) { - log.warn('Compensation failed', { error: compErr }); -} -``` - -**WHY:** `console.log` is not replay-safe. Use `log` from `@temporalio/workflow` for replay-aware logging in workflows. - ---- - -## Content to DELETE - -None. - ---- - -## Content to ADD - -None. - ---- - -## Order Changes - -None - order is already aligned. diff --git a/references/typescript/testing.edits.md b/references/typescript/testing.edits.md deleted file mode 100644 index 0c4142e..0000000 --- a/references/typescript/testing.edits.md +++ /dev/null @@ -1,151 +0,0 @@ -# TypeScript testing.md Edits - -## Status: DONE - ---- - -## Content to DELETE - -### 1. Time Skipping section (as dedicated section) - -**Location:** Entire "Time Skipping" section - -**Action:** DELETE as a dedicated section. - -**Reason:** Python mentions time skipping inline in the Test Environment Setup section. TypeScript should do the same for consistency. - -**Alternative:** Add a brief mention in Test Environment Setup: -```markdown -The test environment automatically skips time when the workflow is waiting on timers, making tests fast. -``` - ---- - -## Content to ADD - -### 2. Testing Failure Cases section - -**Location:** After "Activity Mocking" section, before "Replay Testing" - -**Add this section:** -```markdown -## Testing Failure Cases - -Test that workflows handle errors correctly: - -```typescript -import { TestWorkflowEnvironment } from '@temporalio/testing'; -import { Worker } from '@temporalio/worker'; -import assert from 'assert'; - -describe('Failure handling', () => { - let testEnv: TestWorkflowEnvironment; - - before(async () => { - testEnv = await TestWorkflowEnvironment.createLocal(); - }); - - after(async () => { - await testEnv?.teardown(); - }); - - it('handles activity failure', async () => { - const { client, nativeConnection } = testEnv; - - const worker = await Worker.create({ - connection: nativeConnection, - taskQueue: 'test', - workflowsPath: require.resolve('./workflows'), - activities: { - // Mock activity that always fails - myActivity: async () => { - throw new Error('Activity failed'); - }, - }, - }); - - await worker.runUntil(async () => { - try { - await client.workflow.execute(myWorkflow, { - workflowId: 'test-failure', - taskQueue: 'test', - }); - assert.fail('Expected workflow to fail'); - } catch (err) { - assert(err instanceof WorkflowFailedError); - } - }); - }); -}); -``` -``` - ---- - -### 3. Activity Testing section - -**Location:** After "Replay Testing" section, before "Best Practices" - -**Add this section:** -```markdown -## Activity Testing - -Test activities in isolation without running a workflow: - -```typescript -import { MockActivityEnvironment } from '@temporalio/testing'; -import { myActivity } from './activities'; -import assert from 'assert'; - -describe('Activity tests', () => { - it('completes successfully', async () => { - const env = new MockActivityEnvironment(); - const result = await env.run(myActivity, 'input'); - assert.equal(result, 'expected output'); - }); - - it('handles cancellation', async () => { - const env = new MockActivityEnvironment({ cancelled: true }); - try { - await env.run(longRunningActivity, 'input'); - assert.fail('Expected cancellation'); - } catch (err) { - assert(err instanceof CancelledFailure); - } - }); -}); -``` - -**Note:** `MockActivityEnvironment` provides `heartbeat()` and cancellation support for testing activity behavior. -``` - ---- - -## Order Changes - -**Current order:** -1. Overview -2. Test Environment Setup -3. Time Skipping -4. Activity Mocking -5. Testing Signals and Queries (TS#6) -6. Replay Testing (TS#5) -7. Best Practices - -**Target order (matching Python, after edits):** -1. Overview -2. Test Environment Setup (with inline time skipping mention) -3. Activity Mocking -4. Testing Signals and Queries -5. Testing Failure Cases (new) -6. Replay Testing -7. Activity Testing (new) -8. Best Practices - -**Action:** Reorder "Testing Signals and Queries" to come before "Replay Testing" (matching Python order). - ---- - -## Content to FIX - -None. diff --git a/references/typescript/typescript.edits.md b/references/typescript/typescript.edits.md deleted file mode 100644 index a9e2f26..0000000 --- a/references/typescript/typescript.edits.md +++ /dev/null @@ -1,204 +0,0 @@ -# TypeScript typescript.md (top-level) Edits - -## Status: DONE - ---- - -## BUGS to FIX - -### 1. Additional Resources - Wrong file paths - -**Location:** "Additional Resources" section at the end of the file - -**Current paths (WRONG):** -```markdown -- **`references/python/patterns.md`** - ... -- **`references/python/determinism.md`** - ... -- **`references/python/gotchas.md`** - ... -- **`references/python/error-handling.md`** - ... -- **`references/python/observability.md`** - ... -- **`references/python/testing.md`** - ... -- **`references/python/advanced-features.md`** - ... -- **`references/python/data-handling.md`** - ... -- **`references/python/versioning.md`** - ... -- **`references/python/determinism-protection.md`** - ... -``` - -**Change to (CORRECT):** -```markdown -- **`references/typescript/patterns.md`** - Signals, queries, child workflows, saga pattern, etc. -- **`references/typescript/determinism.md`** - Essentials of determinism in TypeScript -- **`references/typescript/gotchas.md`** - TypeScript-specific mistakes and anti-patterns -- **`references/typescript/error-handling.md`** - ApplicationFailure, retry policies, non-retryable errors -- **`references/typescript/observability.md`** - Logging, metrics, tracing -- **`references/typescript/testing.md`** - TestWorkflowEnvironment, time-skipping, activity mocking -- **`references/typescript/advanced-features.md`** - Schedules, worker tuning, and more -- **`references/typescript/data-handling.md`** - Data converters, payload encryption, etc. -- **`references/typescript/versioning.md`** - Patching API, workflow type versioning, Worker Versioning -- **`references/typescript/determinism-protection.md`** - V8 sandbox and bundling -``` - ---- - -## Content to DELETE - -### 2. How Temporal Works: History Replay section - -**Location:** Entire "How Temporal Works: History Replay" section (includes subsections: The Replay Mechanism, Commands and Events table, When Replay Occurs) - -**Action:** DELETE this entire section. - -**Reason:** This content belongs in core/determinism.md, not in the language-specific top-level file. Both Python and TypeScript should reference the Core conceptual content. - -**Replace with a brief reference (optional):** -```markdown -## Understanding Replay - -Temporal workflows are durable through history replay. For details on how this works, see `core/determinism.md`. -``` - ---- - -## Content to ADD - -### 3. File Organization Best Practice section - -**Location:** After "Key Concepts" section, before "Determinism Rules" - -**Add this section:** -```markdown -## File Organization Best Practice - -**Keep Workflow definitions in separate files from Activity definitions.** The TypeScript SDK bundles workflow files separately. Minimizing workflow file contents improves Worker startup time. - -``` -my_temporal_app/ -├── workflows/ -│ └── greeting.ts # Only Workflow functions -├── activities/ -│ └── translate.ts # Only Activity functions -├── worker.ts # Worker setup, imports both -└── client.ts # Client code to start workflows -``` - -**In the Workflow file, use type-only imports for activities:** -```typescript -// workflows/greeting.ts -import { proxyActivities } from '@temporalio/workflow'; -import type * as activities from '../activities/translate'; - -const { translate } = proxyActivities({ - startToCloseTimeout: '1 minute', -}); -``` -``` - ---- - -### 4. Expand Quick Start section - -**Location:** "Quick Start" section - -**Current state:** Just 3 code blocks with minimal context. - -**Target state (matching Python "Quick Demo"):** - -```markdown -## Quick Start - -**Add Dependencies:** Install the Temporal SDK packages: -```bash -npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity -``` - -**activities.ts** - Activity definitions (separate file for bundling performance): -```typescript -export async function greet(name: string): Promise { - return `Hello, ${name}!`; -} -``` - -**workflows.ts** - Workflow definition (use type-only imports for activities): -```typescript -import { proxyActivities } from '@temporalio/workflow'; -import type * as activities from './activities'; - -const { greet } = proxyActivities({ - startToCloseTimeout: '1 minute', -}); - -export async function greetingWorkflow(name: string): Promise { - return await greet(name); -} -``` - -**worker.ts** - Worker setup (imports activities and workflows, runs indefinitely): -```typescript -import { Worker } from '@temporalio/worker'; -import * as activities from './activities'; - -async function run() { - const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), - activities, - taskQueue: 'greeting-queue', - }); - await worker.run(); -} - -run().catch(console.error); -``` - -**Start the dev server:** Start `temporal server start-dev` in the background. - -**Start the worker:** Run `npx ts-node worker.ts` in the background. - -**client.ts** - Start a workflow execution: -```typescript -import { Client } from '@temporalio/client'; -import { greetingWorkflow } from './workflows'; -import { v4 as uuid } from 'uuid'; - -async function run() { - const client = new Client(); - - const result = await client.workflow.execute(greetingWorkflow, { - workflowId: uuid(), - taskQueue: 'greeting-queue', - args: ['my name'], - }); - - console.log(`Result: ${result}`); -} - -run().catch(console.error); -``` - -**Run the workflow:** Run `npx ts-node client.ts`. Should output: `Result: Hello, my name!`. -``` - ---- - -### 5. Common Pitfalls - Add missing items - -**Location:** "Common Pitfalls" section - -**Add these items:** -```markdown -6. **Forgetting to heartbeat** - Long-running activities need `heartbeat()` calls -7. **Using console.log in workflows** - Use `log` from `@temporalio/workflow` for replay-safe logging -``` - ---- - -## Order Changes - -After edits, the order should be: -1. Overview -2. Quick Start (expanded) -3. Key Concepts -4. File Organization Best Practice (new) -5. Determinism Rules -6. Common Pitfalls (expanded) -7. Writing Tests -8. Additional Resources (fixed paths) From 51b813353326dcc12b731612e68d1feee8db5147 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 25 Feb 2026 20:15:55 -0500 Subject: [PATCH 10/50] more tmp file cleanup and reorg --- references/typescript/correctness_checking.md | 312 ------------------ references/typescript/gotchas.edits.md | 105 ------ 2 files changed, 417 deletions(-) delete mode 100644 references/typescript/correctness_checking.md delete mode 100644 references/typescript/gotchas.edits.md diff --git a/references/typescript/correctness_checking.md b/references/typescript/correctness_checking.md deleted file mode 100644 index a887722..0000000 --- a/references/typescript/correctness_checking.md +++ /dev/null @@ -1,312 +0,0 @@ -# TypeScript References Correctness Check - -## Task Prompt (for session recovery) - -**Goal:** Verify correctness of every factual statement and code example in TypeScript reference files. - -**Workflow for each section:** - -1. Read the section from the reference file -2. Query documentation sources to verify: - - Use `mcp__context7__query-docs` with `libraryId: "/temporalio/sdk-typescript"` for SDK-specific API verification - - Use `mcp__temporal-docs__search_temporal_knowledge_sources` for conceptual/pattern verification -3. Compare the code example against official documentation -4. Update this tracking file: - - Update the table row with status and sources consulted - - Update the detailed notes section with verification details and any needed edits -5. If edits are needed, apply them to the source file after documenting here - -**Status values:** -- `unchecked` - Not yet verified -- `all good` - Verified correct, no changes needed -- `FIXED` - Issues found and corrected - -**Resume instructions:** Find the first `unchecked` section in any table and continue from there. - ---- - -## patterns.md - -**File:** `references/typescript/patterns.md` - -### Tracking - -| # | Section | Status | Fix Applied | Sources | -|---|---------|--------|-------------|---------| -| 1 | Signals | all good | | context7 sdk-typescript, temporal-docs | -| 2 | Dynamic Signal Handlers | FIXED | Used `setDefaultSignalHandler` | context7 sdk-typescript, temporal-docs | -| 3 | Queries | all good | | context7 sdk-typescript | -| 4 | Dynamic Query Handlers | FIXED | Used `setDefaultQueryHandler` | temporal-docs | -| 5 | Updates | all good | | context7 sdk-typescript, temporal-docs | -| 6 | Child Workflows | all good | | context7 sdk-typescript, temporal-docs | -| 7 | Child Workflow Options | all good | | context7 sdk-typescript, temporal-docs | -| 8 | Handles to External Workflows | all good | | context7 sdk-typescript | -| 9 | Parallel Execution | all good | | context7 sdk-typescript | -| 10 | Continue-as-New | all good | | context7 sdk-typescript | -| 11 | Saga Pattern | all good | | temporal-docs, samples-typescript | -| 12 | Cancellation Scopes | all good | | context7 sdk-typescript, temporal-docs | -| 13 | Triggers (Promise-like Signals) | all good | | temporal-docs api reference | -| 14 | Wait Condition with Timeout | all good | | context7 sdk-typescript, temporal-docs | -| 15 | Waiting for All Handlers to Finish | FIXED | Simplified to `await condition(allHandlersFinished)` | temporal-docs api reference | -| 16 | Activity Heartbeat Details | all good | | context7 sdk-typescript, temporal-docs | -| 17 | Timers | all good | | context7 sdk-typescript | -| 18 | Local Activities | FIXED | Wrong import: `executeLocalActivity` → `proxyLocalActivities` | context7 sdk-typescript | - -### Detailed Notes - -#### 1. Signals -**Status:** all good - -**Verified:** -- `defineSignal`, `setHandler`, `condition` imports from `@temporalio/workflow` ✓ -- `defineSignal<[boolean]>('approve')` syntax with type parameter as array of arg types ✓ -- `setHandler(signal, handler)` pattern ✓ -- `await condition(() => approved)` for waiting on state ✓ - ---- - -#### 2. Dynamic Signal Handlers -**Status:** FIXED - -**Issue:** The current code uses a non-existent predicate-based `setHandler` API. The TypeScript SDK uses `setDefaultSignalHandler` for handling signals with unknown names. - -**Before:** -```typescript -setHandler( - (signalName: string) => true, // This API doesn't exist - (signalName: string, ...args: unknown[]) => { ... } -); -``` - -**After:** -```typescript -import { setDefaultSignalHandler, condition } from '@temporalio/workflow'; - -export async function dynamicSignalWorkflow(): Promise> { - const signals: Record = {}; - - setDefaultSignalHandler((signalName: string, ...args: unknown[]) => { - if (!signals[signalName]) { - signals[signalName] = []; - } - signals[signalName].push(args); - }); - - await condition(() => signals['done'] !== undefined); - return signals; -} -``` - -**Source:** https://typescript.temporal.io/api/namespaces/workflow#setdefaultsignalhandler - ---- - -#### 3. Queries -**Status:** all good - -**Verified:** -- `defineQuery`, `setHandler` imports from `@temporalio/workflow` ✓ -- `defineQuery('status')` - return type as type parameter ✓ -- `setHandler(query, () => value)` - synchronous handler returning value ✓ -- Query handlers must be synchronous (not async) ✓ - ---- - -#### 4. Dynamic Query Handlers -**Status:** FIXED - -**Issue:** Same as Dynamic Signal Handlers - uses non-existent predicate-based API. - -**Correct API:** `setDefaultQueryHandler` - -```typescript -setDefaultQueryHandler((queryName: string, ...args: any[]) => { - // return value -}); -``` - -**Source:** https://typescript.temporal.io/api/namespaces/workflow#setdefaultqueryhandler - ---- - -#### 5. Updates -**Status:** all good - -**Verified:** -- `defineUpdate('name')` syntax - return type first, then args as tuple type ✓ -- `setHandler(update, handler, { validator })` pattern matches official docs ✓ -- Validator is synchronous, throws error to reject ✓ -- Handler can be sync or async, returns a value ✓ -- Imports `defineUpdate`, `setHandler`, `condition` from `@temporalio/workflow` ✓ - ---- - -#### 6. Child Workflows -**Status:** all good - -**Verified:** -- `executeChild` import from `@temporalio/workflow` ✓ -- `executeChild(workflowFunc, { args, workflowId })` syntax correct ✓ -- Child scheduled on same task queue as parent by default ✓ - ---- - -#### 7. Child Workflow Options -**Status:** all good - -**Verified:** -- `ParentClosePolicy` values: `TERMINATE` (default), `ABANDON`, `REQUEST_CANCEL` ✓ -- `ChildWorkflowCancellationType` values: `WAIT_CANCELLATION_COMPLETED` (default), `WAIT_CANCELLATION_REQUESTED`, `TRY_CANCEL`, `ABANDON` ✓ -- Both imported from `@temporalio/workflow` ✓ - ---- - -#### 8. Handles to External Workflows -**Status:** all good - -**Verified:** -- `getExternalWorkflowHandle(workflowId)` from `@temporalio/workflow` ✓ -- Synchronous function (not async) ✓ -- `handle.signal()` and `handle.cancel()` methods exist ✓ - ---- - -#### 9. Parallel Execution -**Status:** all good - -**Verified:** -- `Promise.all` for parallel execution is standard pattern ✓ -- Used in official examples for parallel child workflows ✓ - ---- - -#### 10. Continue-as-New -**Status:** all good - -**Verified:** -- `continueAsNew`, `workflowInfo` imports from `@temporalio/workflow` ✓ -- `await continueAsNew(args)` syntax correct ✓ -- `workflowInfo().continueAsNewSuggested` property exists ✓ -- Checking history length threshold is standard pattern ✓ - ---- - -#### 11. Saga Pattern -**Status:** all good - -**Verified:** -- Array of compensation functions pattern ✓ -- Try/catch with compensations in reverse order ✓ -- Official samples-typescript/saga uses same pattern ✓ -- No built-in Saga class in TS SDK (unlike Java), manual implementation correct ✓ - ---- - -#### 12. Cancellation Scopes -**Status:** all good - -**Verified:** -- `CancellationScope` from `@temporalio/workflow` ✓ -- `CancellationScope.nonCancellable(fn)` - prevents cancellation propagation ✓ -- `CancellationScope.withTimeout(timeout, fn)` - auto-cancels after timeout ✓ -- `new CancellationScope()` + `scope.run(fn)` + `scope.cancel()` pattern ✓ - ---- - -#### 13. Triggers (Promise-like Signals) -**Status:** all good - -**Verified:** -- `Trigger` class from `@temporalio/workflow` ✓ -- `new Trigger()` creates a PromiseLike that exposes resolve/reject ✓ -- `trigger.resolve(value)` to resolve from signal handler ✓ -- `await trigger` works because Trigger implements PromiseLike ✓ -- CancellationScope-aware (throws when scope cancelled) ✓ - ---- - -#### 14. Wait Condition with Timeout -**Status:** all good - -**Verified:** -- `condition(fn, timeout)` with timeout returns `Promise` ✓ -- Returns `true` if condition met, `false` if timeout expires ✓ -- String duration format `'24 hours'` supported (ms-formatted string) ✓ -- Import of `CancelledFailure` unused in example but harmless ✓ - ---- - -#### 15. Waiting for All Handlers to Finish -**Status:** FIXED - -**Issue:** Current code used overly complex condition with `workflowInfo().unsafe.isReplaying` and was missing import of `allHandlersFinished`. - -**Before:** -```typescript -import { condition, workflowInfo } from '@temporalio/workflow'; -// ... -await condition(() => workflowInfo().unsafe.isReplaying || allHandlersFinished()); -``` - -**After:** -```typescript -import { condition, allHandlersFinished } from '@temporalio/workflow'; -// ... -await condition(allHandlersFinished); -``` - -**Source:** https://typescript.temporal.io/api/namespaces/workflow#allhandlersfinished - -**Notes:** -- `allHandlersFinished` is a function that returns `boolean` -- Pass it directly to `condition()` (not wrapped in a lambda) -- Official pattern: `await wf.condition(wf.allHandlersFinished)` - ---- - -#### 16. Activity Heartbeat Details -**Status:** all good - -**Verified:** -- `heartbeat`, `activityInfo` imports from `@temporalio/activity` ✓ -- `activityInfo().heartbeatDetails` gets heartbeat from previous failed attempt ✓ -- `heartbeat(details)` records checkpoint for resume ✓ -- Pattern matches official samples-typescript/activities-cancellation-heartbeating ✓ - -**Notes:** -- `activityInfo()` is convenience function for `Context.current().info` -- heartbeatDetails can be any serializable value (number, object, etc.) - ---- - -#### 17. Timers -**Status:** all good - -**Verified:** -- `sleep` from `@temporalio/workflow` accepts duration strings ✓ -- `CancellationScope` for cancellable timers ✓ -- `scope.run()` and `scope.cancel()` pattern ✓ - -**Notes:** -- Example uses `setHandler` and `cancelSignal` without imports (pattern demonstration) - ---- - -#### 18. Local Activities -**Status:** FIXED - -**Issue:** Wrong import - code imported `executeLocalActivity` but used `proxyLocalActivities`. - -**Before:** -```typescript -import { executeLocalActivity } from '@temporalio/workflow'; -``` - -**After:** -```typescript -import { proxyLocalActivities } from '@temporalio/workflow'; -``` - -**Source:** context7 sdk-typescript documentation - ---- diff --git a/references/typescript/gotchas.edits.md b/references/typescript/gotchas.edits.md deleted file mode 100644 index 0d41a66..0000000 --- a/references/typescript/gotchas.edits.md +++ /dev/null @@ -1,105 +0,0 @@ -# TypeScript gotchas.md Edits - -## Status: DONE - ---- - -## Content to DELETE - -### 1. Idempotency section - -**Location:** Entire "Idempotency" section with BAD/GOOD code example - -**Action:** DELETE this entire section. - -**Reason:** Core coverage in core/gotchas.md is sufficient. No need to duplicate. - ---- - -### 2. Replay Safety section - -**Location:** Entire "Replay Safety" section (includes "Side Effects" and "Non-Deterministic Operations" subsections) - -**Action:** DELETE this entire section. - -**Reason:** Core coverage in core/gotchas.md is sufficient. No need to duplicate. - ---- - -### 3. Query Handlers section - -**Location:** Entire "Query Handlers" section (includes "Modifying State" and "Blocking in Queries" subsections) - -**Action:** DELETE this entire section. - -**Reason:** Core coverage in core/gotchas.md is sufficient. No need to duplicate. - ---- - -### 4. Error Handling section - -**Location:** Entire "Error Handling" section - -**Action:** DELETE this entire section. - -**Reason:** Core coverage in core/gotchas.md is sufficient. TypeScript-specific error handling is covered in error-handling.md. - ---- - -### 5. Retry Policies section - -**Location:** Entire "Retry Policies" section (includes "Too Aggressive" subsection) - -**Action:** DELETE this entire section. - -**Reason:** Core coverage in core/gotchas.md is sufficient. No need to duplicate. - ---- - -## Content to ADD - -### 6. Wrong Retry Classification section - -**Location:** After existing sections, before Testing section - -**Add this section:** -```markdown -## Wrong Retry Classification - -A common mistake is treating transient errors as permanent (or vice versa): - -- **Transient errors** (retry): network timeouts, temporary service unavailability, rate limits -- **Permanent errors** (don't retry): invalid input, authentication failure, resource not found - -```typescript -// BAD: Retrying a permanent error -throw ApplicationFailure.create({ message: 'User not found' }); -// This will retry indefinitely! - -// GOOD: Mark permanent errors as non-retryable -throw ApplicationFailure.nonRetryable('User not found'); -``` - -For detailed guidance on error classification and retry policies, see `error-handling.md`. -``` - ---- - -## Content to FIX - -None. - ---- - -## Order Changes - -After deletions, the remaining sections should be: -1. Activity Imports (TS-specific) -2. Bundling Issues (TS-specific) -3. Wrong Retry Classification (new) -4. Cancellation (TS-specific) -5. Heartbeating -6. Testing -7. Timers and Sleep (TS-specific) - -This provides a focused TypeScript-specific gotchas file that complements (rather than duplicates) the Core gotchas. From 52126fef631e4ab05ffa1889a3eb77cb460710dd Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 25 Feb 2026 20:16:00 -0500 Subject: [PATCH 11/50] x --- references/alignment_checking.md | 1460 ------------------------------ 1 file changed, 1460 deletions(-) delete mode 100644 references/alignment_checking.md diff --git a/references/alignment_checking.md b/references/alignment_checking.md deleted file mode 100644 index feb9b3c..0000000 --- a/references/alignment_checking.md +++ /dev/null @@ -1,1460 +0,0 @@ -# Alignment Checking - -## Purpose - -Track content alignment (do files have the right sections?) and style alignment (is prose level appropriate?) across reference files. - -**Style target:** Python is the reference style (code-first, minimal prose). TypeScript should match. - -**Implementation Status:** ✅ **COMPLETE** (2026-02-25) - -All TODO/DEL/BUG items have been implemented. See `.edits.md` files for details of changes made. - ---- - -## Section Inventory - -Shows which sections exist in each language. Organized by file. - -**Legend:** -- `✓` = present -- ` ` (empty) = missing, unknown if intentional (needs review) -- `—` = missing, intentional (language doesn't need this) -- `TODO` = missing, should add -- `DEL` = present, should remove or merge -- `Py#` / `TS#` = section order in file (should monotonically increase if order is aligned) - -### patterns.md - -| Section | Core | Python | Py# | TypeScript | TS# | Go | -|---------|------|--------|-----|------------|-----|-----| -| Signals | ✓ | ✓ | 1 | ✓ | 1 | | -| Dynamic Signal Handlers | — | ✓ | 2 | ✓ | 2 | | -| Queries | ✓ | ✓ | 3 | ✓ | 3 | | -| Dynamic Query Handlers | — | ✓ | 4 | ✓ | 4 | | -| Updates | ✓ | ✓ | 5 | ✓ | 5 | | -| Child Workflows | ✓ | ✓ | 6 | ✓ | 6 | | -| Child Workflow Options | — | — | — | ✓ | 7 | | -| Handles to External Workflows | — | ✓ | 7 | ✓ | 8 | | -| Parallel Execution | ✓ | ✓ | 8 | ✓ | 9 | | -| Deterministic Asyncio Alternatives | — | ✓ | 9 | — | — | | -| Continue-as-New | ✓ | ✓ | 10 | ✓ | 10 | | -| Saga Pattern | ✓ | ✓ | 11 | ✓ | 11 | | -| Cancellation Handling (asyncio) | — | ✓ | 12 | — | — | | -| Cancellation Scopes | — | — | — | ✓ | 12 | | -| Triggers | — | — | — | ✓ | 13 | | -| Wait Condition with Timeout | — | ✓ | 13 | ✓ | 14 | | -| Waiting for All Handlers to Finish | — | ✓ | 14 | ✓ | 15 | | -| Activity Heartbeat Details | — | ✓ | 15 | ✓ | 16 | | -| Timers | ✓ | ✓ | 16 | ✓ | 17 | | -| Local Activities | ✓ | ✓ | 17 | ✓ | 18 | | -| Entity Workflow Pattern | ✓ | — | — | — | — | | -| Polling Patterns | ✓ | — | — | — | — | | -| Idempotency Patterns | ✓ | — | — | — | — | | -| Using Pydantic Models | — | ✓ | 18 | — | — | | - -### data-handling.md - -| Section | Core | Python | Py# | TypeScript | TS# | Go | -|---------|------|--------|-----|------------|-----|-----| -| Overview | — | ✓ | 1 | ✓ | 1 | | -| Default Data Converter | — | ✓ | 2 | ✓ | 2 | | -| Pydantic Integration | — | ✓ | 3 | — | — | | -| Custom Data Converter | — | ✓ | 4 | ✓ | 3 | | -| Payload Encryption | — | ✓ | 5 | ✓ | 4 | | -| Search Attributes | — | ✓ | 6 | ✓ | 5 | | -| Workflow Memo | — | ✓ | 7 | ✓ | 6 | | -| Protobuf Support | — | — | — | ✓ | 7 | | -| Large Payloads | — | ✓ | 8 | ✓ | 8 | | -| Deterministic APIs for Values | — | ✓ | 9 | — | — | | -| Best Practices | — | ✓ | 10 | ✓ | 9 | | - -### error-handling.md - -| Section | Core | Python | Py# | TypeScript | TS# | Go | -|---------|------|--------|-----|------------|-----|-----| -| Overview | — | ✓ | 1 | ✓ | 1 | | -| Application Errors/Failures | — | ✓ | 2 | ✓ | 2 | | -| Non-Retryable Errors | — | ✓ | 3 | — | — | | -| Activity Errors | — | — | — | ✓ | 3 | | -| Handling Activity Errors in Workflows | — | ✓ | 4 | ✓ | 4 | | -| Retry Configuration | — | ✓ | 5 | ✓ | 5 | | -| Timeout Configuration | — | ✓ | 6 | ✓ | 6 | | -| Workflow Failure | — | ✓ | 7 | ✓ | 7 | | -| Cancellation Handling in Activities | — | — | — | — | — | | -| Idempotency Patterns | — | — | — | — | — | | -| Best Practices | — | ✓ | 8 | ✓ | 9 | | - -### gotchas.md - -| Section | Core | Core# | Python | Py# | TypeScript | TS# | Go | -|---------|------|-------|--------|-----|------------|-----|-----| -| Idempotency / Non-Idempotent Activities | ✓ | 1 | — | — | — | — | | -| Replay Safety / Side Effects & Non-Determinism | ✓ | 2 | — | — | — | — | | -| Multiple Workers with Different Code | ✓ | 3 | — | — | — | — | | -| Retry Policies / Failing Activities Too Quickly | ✓ | 4 | — | — | — | — | | -| Query Handlers / Query Handler Mistakes | ✓ | 5 | — | — | — | — | | -| File Organization | ✓ | 6 | ✓ | 1 | — | — | | -| Activity Imports | — | — | — | — | ✓ | 1 | | -| Bundling Issues | — | — | — | — | ✓ | 2 | | -| Async vs Sync Activities | — | — | ✓ | 2 | — | — | | -| Error Handling | ✓ | 8 | — | — | — | — | | -| Wrong Retry Classification | ✓ | 8 | ✓ | 3 | ✓ | 3 | | -| Cancellation | — | — | — | — | ✓ | 4 | | -| Heartbeating | — | — | ✓ | 4 | ✓ | 5 | | -| Testing | ✓ | 7 | ✓ | 5 | ✓ | 6 | | -| Timers and Sleep | — | — | ✓ | 6 | ✓ | 7 | | - -### observability.md - -| Section | Core | Python | Py# | TypeScript | TS# | Go | -|---------|------|--------|-----|------------|-----|-----| -| Overview | — | ✓ | 1 | ✓ | 1 | | -| Logging / Replay-Aware Logging | — | ✓ | 2 | ✓ | 2 | | -| Customizing the Logger | — | ✓ | 2 | ✓ | 3 | | -| OpenTelemetry Integration | — | — | — | — | — | | -| Metrics | — | ✓ | 3 | ✓ | 4 | | -| Search Attributes (Visibility) | — | ✓ | 4 | — | — | | -| Debugging with Event History | — | — | — | — | — | | -| Best Practices | — | ✓ | 5 | ✓ | 5 | | - -### testing.md - -| Section | Core | Python | Py# | TypeScript | TS# | Go | -|---------|------|--------|-----|------------|-----|-----| -| Overview | — | ✓ | 1 | ✓ | 1 | | -| Test Environment Setup | — | ✓ | 2 | ✓ | 2 | | -| Time Skipping | — | — | — | — | — | | -| Activity Mocking | — | ✓ | 3 | ✓ | 3 | | -| Testing Signals and Queries | — | ✓ | 4 | ✓ | 4 | | -| Testing Failure Cases | — | ✓ | 5 | ✓ | 5 | | -| Replay Testing | — | ✓ | 6 | ✓ | 6 | | -| Activity Testing | — | ✓ | 7 | ✓ | 7 | | -| Best Practices | — | ✓ | 8 | ✓ | 8 | | - -### versioning.md - -| Section | Core | Core# | Python | Py# | TypeScript | TS# | Go | -|---------|------|-------|--------|-----|------------|-----|-----| -| Overview | ✓ | 1 | ✓ | 1 | ✓ | 1 | | -| Why Versioning is Needed | ✓ | 2 | ✓ | 2 | ✓ | 2 | | -| Patching API | ✓ | 3 | ✓ | 3 | ✓ | 3 | | -| Workflow Type Versioning | ✓ | 4 | ✓ | 4 | ✓ | 4 | | -| Worker Versioning | ✓ | 5 | ✓ | 5 | ✓ | 5 | | -| Choosing a Strategy | ✓ | 6 | ✓ | 6 | ✓ | 6 | | -| Best Practices | ✓ | 7 | ✓ | 6 | ✓ | 7 | | -| Finding Workflows by Version | ✓ | 8 | — | — | — | — | | -| Common Mistakes | ✓ | 9 | — | — | — | — | | - -### {language}.md (top-level files) - -| Section | Core | Python | Py# | TypeScript | TS# | Go | -|---------|------|--------|-----|------------|-----|-----| -| Overview | — | ✓ | 1 | ✓ | 1 | | -| How Temporal Works: History Replay | — | — | — | — | — | | -| Understanding Replay | — | — | — | ✓ | 2 | | -| Quick Start / Quick Demo | — | ✓ | 2 | ✓ | 3 | | -| Key Concepts | — | ✓ | 3 | ✓ | 4 | | -| File Organization Best Practice | — | ✓ | 4 | ✓ | 5 | | -| Determinism Rules | — | — | — | ✓ | 6 | | -| Common Pitfalls | — | ✓ | 5 | ✓ | 7 | | -| Writing Tests | — | ✓ | 6 | ✓ | 8 | | -| Additional Resources | — | ✓ | 7 | ✓ | 9 | | - -### determinism-protection.md - -| Section | Core | Python | Py# | TypeScript | TS# | Go | -|---------|------|--------|-----|------------|-----|-----| -| Overview | — | ✓ | 1 | ✓ | 1 | | -| How the Sandbox Works | — | ✓ | 2 | — | — | | -| Import Blocking | — | — | — | ✓ | 2 | | -| Forbidden Operations | — | ✓ | 3 | — | — | | -| Function Replacement | — | — | — | ✓ | 3 | | -| Pass-Through Pattern | — | ✓ | 4 | — | — | | -| Importing Activities | — | ✓ | 5 | — | — | | -| Disabling the Sandbox | — | ✓ | 6 | — | — | | -| Customizing Invalid Module Members | — | ✓ | 7 | — | — | | -| Import Notification Policy | — | ✓ | 8 | — | — | | -| Disable Lazy sys.modules Passthrough | — | ✓ | 9 | — | — | | -| File Organization | — | ✓ | 10 | — | — | | -| Common Issues | — | ✓ | 11 | — | — | | -| Best Practices | — | ✓ | 12 | — | — | | - -### determinism.md - -| Section | Core | Core# | Python | Py# | TypeScript | TS# | Go | -|---------|------|-------|--------|-----|------------|-----|-----| -| Overview | ✓ | 1 | ✓ | 1 | ✓ | 1 | | -| Why Determinism Matters | ✓ | 2 | ✓ | 2 | ✓ | 2 | | -| Sources of Non-Determinism | ✓ | 3 | — | — | — | — | | -| Central Concept: Activities | ✓ | 4 | — | — | — | — | | -| SDK Protection / Sandbox | ✓ | 5 | ✓ | 6 | ✓ | 3 | | -| Forbidden Operations | — | — | ✓ | 3 | ✓ | 4 | | -| Safe Builtin Alternatives | — | — | ✓ | 4 | — | — | | -| Detecting Non-Determinism | ✓ | 6 | — | — | — | — | | -| Recovery from Non-Determinism | ✓ | 7 | — | — | — | — | | -| Testing Replay Compatibility | — | — | ✓ | 5 | ✓ | 5 | | -| Best Practices | ✓ | 8 | ✓ | 7 | ✓ | 6 | | - ---- - -## Style Alignment: TypeScript vs Python - -### patterns.md - -#### Signals -- **Python:** Code only -- **TypeScript:** Code only -- **Decision:** all good - ---- - -#### Dynamic Signal Handlers -- **Python:** One-liner intro + code -- **TypeScript:** One-liner intro + code -- **Decision:** all good - ---- - -#### Queries -- **Python:** One-liner note ("must NOT modify state") + code -- **TypeScript:** One-liner note ("must NOT modify state") + code -- **Decision:** ✅ FIXED — TS now has "**Important:** Queries must NOT modify workflow state or have side effects" note - ---- - -#### Dynamic Query Handlers -- **Python:** Code only -- **TypeScript:** One-liner intro + code -- **Decision:** all good - ---- - -#### Updates -- **Python:** Code only (validator shown inline) -- **TypeScript:** Code only (shows both simple and validated handlers) -- **Decision:** all good - ---- - -#### Child Workflows -- **Python:** Code only (shows parent_close_policy inline) -- **TypeScript:** Code only + separate "Child Workflow Options" subsection -- **Decision:** all good (TS has more options to show, subsection is appropriate) - ---- - -#### Handles to External Workflows -- **Python:** Code only -- **TypeScript:** Code only -- **Decision:** all good - ---- - -#### Parallel Execution -- **Python:** Code + "Deterministic Alternatives to asyncio" subsection -- **TypeScript:** Code only (just Promise.all) -- **Decision:** all good (Python needs asyncio alternatives, TS doesn't) - ---- - -#### Continue-as-New -- **Python:** Code only -- **TypeScript:** Code only -- **Decision:** all good - ---- - -#### Saga Pattern -- **Python:** One-liner note ("compensation activities should be idempotent") + code with detailed comments explaining WHY save compensation BEFORE activity -- **TypeScript:** One-liner note + code with detailed comments + replay-safe logging -- **Decision:** ✅ FIXED - - TS now has "**Important:** Compensation activities should be idempotent" note - - TS now has comments explaining WHY compensation is saved BEFORE the activity - - TS now uses `log` from `@temporalio/workflow` for replay-safe logging - ---- - -#### Cancellation Handling (Python) vs Cancellation Scopes (TypeScript) -- **Python:** "Cancellation Handling - leverages standard asyncio cancellation" - code showing asyncio.CancelledError -- **TypeScript:** "Cancellation Scopes" - one-liner + code showing CancellationScope patterns -- **Decision:** all good (different language idioms - Python uses asyncio, TS uses CancellationScope) - ---- - -#### Triggers (TypeScript only) -- **Python:** N/A (no equivalent) -- **TypeScript:** WHY/WHEN + code -- **Decision:** all good (TS-specific pattern, needs explanation) - ---- - -#### Wait Condition with Timeout -- **Python:** Code only -- **TypeScript:** Code only -- **Decision:** all good - ---- - -#### Waiting for All Handlers to Finish -- **Python:** WHY/WHEN (as ### headers) + code -- **TypeScript:** WHY/WHEN (as ### headers) + code -- **Decision:** all good (both have same structure, important pattern worth explaining) - ---- - -#### Activity Heartbeat Details -- **Python:** WHY/WHEN (as ### headers) + code -- **TypeScript:** WHY/WHEN (as ### headers) + code -- **Decision:** all good (both have same structure) - ---- - -#### Timers -- **Python:** Code only (simple sleep example) -- **TypeScript:** Code only (shows sleep + cancellable timer with CancellationScope) -- **Decision:** all good (TS shows more because CancellationScope is TS-specific) - ---- - -#### Local Activities -- **Python:** Purpose note + code -- **TypeScript:** Purpose note + code -- **Decision:** all good (both have same structure, warning is important) - ---- - -### data-handling.md - -#### Overview -- **Python:** One-liner describing data converters -- **TypeScript:** One-liner describing data converters -- **Decision:** all good - ---- - -#### Default Data Converter -- **Python:** Bullet list of supported types -- **TypeScript:** Bullet list of supported types -- **Decision:** all good - ---- - -#### Pydantic Integration (Python only) -- **Python:** Explanation + two code blocks (model definition + client setup) -- **TypeScript:** N/A (no equivalent) -- **Decision:** all good (Python-specific feature) - ---- - -#### Custom Data Converter -- **Python:** Brief explanation + links to example files -- **TypeScript:** Brief explanation + full code example -- **Decision:** all good (TS inline example is appropriate; Python linking to samples is also valid) - ---- - -#### Payload Encryption -- **Python:** Code only (with inline comment about GIL) -- **TypeScript:** Code only -- **Decision:** all good - ---- - -#### Search Attributes -- **Python:** Code examples for setting at start, upserting, querying (with typed SearchAttributeKey) -- **TypeScript:** Code examples for setting at start, upserting, reading, querying -- **Decision:** all good (TypeScript has extra "Reading" subsection which is fine) - ---- - -#### Workflow Memo -- **Python:** Code for setting + reading (two separate blocks) -- **TypeScript:** Code for setting + reading (combined block) -- **Decision:** all good - ---- - -#### Protobuf Support (TypeScript only) -- **Python:** N/A -- **TypeScript:** One-liner + code -- **Decision:** all good (TS-specific section) - ---- - -#### Large Payloads -- **Python:** Bullet list + code example (reference pattern) -- **TypeScript:** Bullet list + code example (reference pattern) -- **Decision:** all good - ---- - -#### Deterministic APIs for Values (Python only) -- **Python:** One-liner + code showing workflow.uuid4() and workflow.random() -- **TypeScript:** N/A -- **Decision:** all good (Python-specific; TS doesn't need this section) - ---- - -#### Best Practices -- **Python:** Numbered list (6 items) -- **TypeScript:** Numbered list (6 items) -- **Decision:** all good (content differs slightly but appropriate per language) - ---- - -### error-handling.md - -#### Overview -- **Python:** One-liner describing ApplicationError and retry policy; notes applicability to activities, child workflows, Nexus -- **TypeScript:** One-liner describing ApplicationFailure with non-retryable marking -- **Decision:** all good - ---- - -#### Application Errors/Failures -- **Python:** "Application Errors" - code example in activity context -- **TypeScript:** "Application Failures" - code example in workflow context -- **Decision:** all good (different names match SDK terminology) - ---- - -#### Non-Retryable Errors (Python only) -- **Python:** Dedicated section with detailed code example showing non_retryable=True -- **TypeScript:** N/A (covered inline in Application Failures section) -- **Decision:** all good (TS shows nonRetryable inline, Python splits it out for emphasis) - ---- - -#### Activity Errors (TypeScript only) -- **Python:** N/A (covered in Application Errors) -- **TypeScript:** Separate section showing ApplicationFailure in activity context -- **Decision:** all good (TS splits workflow vs activity contexts) - ---- - -#### Handling Activity Errors in Workflows -- **Python:** Code showing try/except with ActivityError, uses `workflow.logger` -- **TypeScript:** Code showing try/catch with ApplicationFailure instanceof check, uses `log` -- **Decision:** ✅ FIXED — TS now uses `log` from `@temporalio/workflow` - ---- - -#### Retry Configuration -- **Python:** Code + note about preferring defaults ("Only set options... if you have a domain-specific reason to") -- **TypeScript:** Code + note about preferring defaults -- **Decision:** ✅ FIXED — TS now has note about preferring defaults - ---- - -#### Timeout Configuration -- **Python:** Code with inline comments explaining each timeout -- **TypeScript:** Code with inline comments explaining each timeout -- **Decision:** all good - ---- - -#### Workflow Failure -- **Python:** Code + note about not using non_retryable in workflows -- **TypeScript:** Code + note about not using nonRetryable in workflows -- **Decision:** ✅ FIXED — TS now has Workflow Failure section - ---- - -#### Cancellation Handling in Activities -- **Python:** N/A -- **TypeScript:** N/A (removed from error-handling.md) -- **Decision:** ✅ DONE — Removed from error-handling.md (patterns.md has Cancellation Scopes) - ---- - -#### Idempotency Patterns (error-handling.md) -- **Python:** N/A (brief mention in Best Practices, references core/patterns.md) -- **TypeScript:** Brief reference to core/patterns.md -- **Decision:** ✅ DONE — Replaced detailed section with brief reference to core - ---- - -#### Best Practices -- **Python:** Numbered list (6 items), includes reference to core/patterns.md for idempotency -- **TypeScript:** Numbered list (7 items), includes idempotency items -- **Decision:** all good - ---- - -### gotchas.md - -**Note:** These files have very different structures. TypeScript has 11 detailed sections with code examples. Python has 5 sections and references other docs more heavily. - -#### Idempotency -- **Python:** N/A (covered in core/patterns.md) -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript (Core coverage sufficient) - ---- - -#### Replay Safety -- **Python:** N/A -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript (Core coverage sufficient) - ---- - -#### Query Handlers -- **Python:** N/A -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript (Core coverage sufficient) - ---- - -#### File Organization (Python only) -- **Python:** Subsections for Importing Activities and Mixing Workflows/Activities -- **TypeScript:** N/A (covered in Activity Imports section) -- **Decision:** all good (different names, similar concepts) - ---- - -#### Activity Imports (TypeScript only) -- **Python:** N/A (covered in File Organization) -- **TypeScript:** Subsections for type-only imports and Node.js module restrictions -- **Decision:** all good (Python covers import issues in File Organization) - ---- - -#### Bundling Issues (TypeScript only) -- **Python:** N/A -- **TypeScript:** Subsections for Missing Dependencies and Package Version Mismatches -- **Decision:** all good (TS-specific concern due to workflow bundling) - ---- - -#### Async vs Sync Activities (Python only) -- **Python:** Subsections for Blocking in Async and Missing Executor -- **TypeScript:** N/A -- **Decision:** all good (Python-specific concern) - ---- - -#### Error Handling (gotchas.md) -- **Python:** N/A (references error-handling.md) -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript gotchas.md (covered in core and error-handling.md) - ---- - -#### Wrong Retry Classification -- **Python:** Brief note referencing error-handling.md -- **TypeScript:** Brief note + code example + reference to error-handling.md -- **Decision:** ✅ FIXED — Added to TypeScript - ---- - -#### Retry Policies -- **Python:** N/A -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript (Core coverage sufficient) - ---- - -#### Cancellation (TypeScript only) -- **Python:** N/A -- **TypeScript:** "Not Handling Cancellation" with CancellationScope example -- **Decision:** all good (TS-specific due to CancellationScope API) - ---- - -#### Heartbeating -- **Python:** Two subsections: Forgetting to Heartbeat, Timeout Too Short -- **TypeScript:** Two subsections: Forgetting to Heartbeat, Timeout Too Short -- **Decision:** all good (both have equivalent content) - ---- - -#### Testing -- **Python:** Brief note referencing testing.md -- **TypeScript:** Full code examples for failure testing and replay testing -- **Decision:** all good (Python references separate file, TS has inline examples) - ---- - -#### Timers and Sleep -- **Python:** "Using asyncio.sleep" BAD/GOOD example -- **TypeScript:** "Using JavaScript setTimeout" BAD/GOOD example -- **Decision:** ✅ FIXED — Added to Python - ---- - -### observability.md - -#### Overview -- **Python:** One-liner mentioning logging, metrics, tracing, visibility -- **TypeScript:** One-liner mentioning replay-aware logging, metrics, OpenTelemetry -- **Decision:** all good - ---- - -#### Logging / Replay-Aware Logging -- **Python:** "Logging" with subsections for Workflow and Activity logging + code examples -- **TypeScript:** "Replay-Aware Logging" with subsections for Workflow and Activity logging + code examples -- **Decision:** all good (different section names, same content structure) - ---- - -#### Customizing the Logger -- **Python:** Subsection under Logging showing basicConfig -- **TypeScript:** Separate section with Basic Configuration + Winston Integration subsections -- **Decision:** all good (TS has more detail for Winston, appropriate) - ---- - -#### OpenTelemetry Integration -- **Python:** N/A -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript (too detailed for reference docs) - ---- - -#### Metrics -- **Python:** Enabling SDK Metrics + Key SDK Metrics subsections -- **TypeScript:** Prometheus Metrics + OTLP Metrics subsections -- **Decision:** all good (both cover metrics, different focus) - ---- - -#### Search Attributes (Python only) -- **Python:** Brief reference to data-handling.md -- **TypeScript:** N/A (covered in data-handling.md) -- **Decision:** all good (Python includes as observability concept, TS keeps in data-handling) - ---- - -#### Debugging with Event History -- **Python:** N/A -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript (too detailed) - ---- - -#### Best Practices -- **Python:** 4 items -- **TypeScript:** 6 items -- **Decision:** all good - ---- - -### testing.md - -#### Overview -- **Python:** Multi-sentence describing WorkflowEnvironment and ActivityEnvironment -- **TypeScript:** One-liner mentioning TestWorkflowEnvironment -- **Decision:** all good (Python more detailed intro) - ---- - -#### Test Environment Setup -- **Python:** "Workflow Test Environment" - detailed pattern explanation + code -- **TypeScript:** "Test Environment Setup" - code example with before/after hooks -- **Decision:** all good (both comprehensive) - ---- - -#### Time Skipping -- **Python:** N/A (mentioned inline in environment section) -- **TypeScript:** N/A (mentioned inline in environment section) -- **Decision:** ✅ DONE — Removed dedicated section from TypeScript (now inline like Python) - ---- - -#### Activity Mocking -- **Python:** "Mocking Activities" - code with @activity.defn mock -- **TypeScript:** "Activity Mocking" - inline activity object in Worker.create -- **Decision:** all good - ---- - -#### Testing Signals and Queries -- **Python:** Code example with signal/query -- **TypeScript:** Code example with signal/query -- **Decision:** all good - ---- - -#### Testing Failure Cases -- **Python:** Code example with pytest.raises -- **TypeScript:** Code example with try/catch and WorkflowFailedError -- **Decision:** ✅ FIXED — Added to TypeScript - ---- - -#### Replay Testing -- **Python:** "Workflow Replay Testing" - code with Replayer class -- **TypeScript:** "Replay Testing" - code with Worker.runReplayHistory -- **Decision:** all good - ---- - -#### Activity Testing -- **Python:** Code with ActivityEnvironment -- **TypeScript:** Code with MockActivityEnvironment -- **Decision:** ✅ FIXED — Added to TypeScript - ---- - -#### Best Practices -- **Python:** 6 items -- **TypeScript:** 5 items -- **Decision:** all good - ---- - -### versioning.md - -#### Overview -- **Core:** Brief intro listing three approaches -- **Python:** Detailed intro covering all three approaches -- **TypeScript:** Brief intro covering all three approaches -- **Decision:** all good - ---- - -#### Why Versioning is Needed -- **Core:** Conceptual explanation with pseudo-code -- **Python:** Detailed explanation with replay context -- **TypeScript:** "Why Versioning Matters" - similar detailed explanation -- **Decision:** all good - ---- - -#### Patching API -- **Core:** Conceptual with Three-Phase Lifecycle, When to Use, When NOT to Use -- **Python:** Full code examples with patched(), Three-Step Process, Multiple Patches, Query Filters -- **TypeScript:** Full code examples with patched(), Three-Step Process, Multiple Patches, Query Filters -- **Decision:** all good (Core conceptual, languages have implementation details) - ---- - -#### Workflow Type Versioning -- **Core:** Conceptual with process steps -- **Python:** Full code examples with Worker registration -- **TypeScript:** Full code examples with Worker registration -- **Decision:** all good - ---- - -#### Worker Versioning -- **Core:** Key Concepts, PINNED/AUTO_UPGRADE guidance -- **Python:** Full code examples, Deployment Strategies, Querying -- **TypeScript:** Full code examples, Deployment Strategies, Rainbow/Blue-Green -- **Decision:** all good - ---- - -#### Choosing a Strategy -- **Core:** Decision table -- **Python:** Decision table -- **TypeScript:** Decision table -- **Decision:** ✅ FIXED — Added decision table to Python - ---- - -#### Best Practices -- **Core:** 5 items -- **Python:** 7 items -- **TypeScript:** 8 items -- **Decision:** all good - ---- - -#### Finding Workflows by Version (Core only) -- **Core:** CLI examples for querying by version -- **Python:** N/A (covered in Query Filters subsection) -- **TypeScript:** N/A (covered in Query Filters subsection) -- **Decision:** all good (languages include as subsections) - ---- - -#### Common Mistakes (Core only) -- **Core:** 4 common mistakes -- **Python:** N/A -- **TypeScript:** N/A -- **Decision:** — (intentional) — Keep Core-only; conceptual coverage sufficient - ---- - -### {language}.md (top-level files) - -#### Overview -- **Python:** One-liner about async/type-safe, Python 3.9+, sandbox -- **TypeScript:** One-liner about async/await, V8 sandbox + version warning -- **Decision:** all good - ---- - -#### How Temporal Works: History Replay -- **Python:** N/A (covered in core/determinism.md) -- **TypeScript:** N/A (removed, replaced with brief "Understanding Replay" section) -- **Decision:** ✅ DONE — Removed from TypeScript; now references core/determinism.md - ---- - -#### Quick Start / Quick Demo -- **Python:** Full multi-file example with activities, workflows, worker, starter -- **TypeScript:** Full multi-file example with activities, workflows, worker, client -- **Decision:** ✅ FIXED — Expanded TypeScript to match Python (see detailed gaps now resolved) - ---- - -#### Key Concepts -- **Python:** Workflow Definition, Activity Definition (sync vs async), Worker Setup, Determinism subsection -- **TypeScript:** Workflow Definition, Activity Definition, Worker Setup (no Determinism subsection) -- **Decision:** all good (TS has separate Determinism Rules section) - ---- - -#### Determinism Rules (TypeScript only) -- **Python:** N/A (has Determinism subsection in Key Concepts) -- **TypeScript:** Separate section with automatic replacements and safe operations -- **Decision:** all good (different organization, both cover determinism) - ---- - -#### File Organization Best Practice -- **Python:** Directory structure + code example for sandbox imports -- **TypeScript:** Directory structure + code example for type-only imports -- **Decision:** ✅ FIXED — Added to TypeScript with bundling/import guidance - ---- - -#### Common Pitfalls -- **Python:** 7 items -- **TypeScript:** 5 items -- **Decision:** all good - ---- - -#### Writing Tests -- **Python:** Reference to testing.md -- **TypeScript:** Reference to testing.md -- **Decision:** all good - ---- - -#### Additional Resources -- **Python:** Correct references to python files -- **TypeScript:** Correct references to typescript files -- **Decision:** ✅ FIXED — Updated TypeScript file paths - ---- - -### determinism-protection.md - -**Note:** These files have very different structures due to different sandbox implementations. Python has 12 detailed sections; TypeScript has 3 sections. - -#### Overview -- **Python:** One-liner about sandbox protection -- **TypeScript:** One-liner about V8 sandbox -- **Decision:** all good - ---- - -#### How the Sandbox Works (Python only) -- **Python:** Bullet list of sandbox mechanisms -- **TypeScript:** N/A (covered in Overview) -- **Decision:** all good - ---- - -#### Import Blocking (TypeScript only) -- **Python:** N/A -- **TypeScript:** Code example with ignoreModules for bundler -- **Decision:** all good (TS-specific V8 concern) - ---- - -#### Forbidden Operations (Python only) -- **Python:** List of forbidden operations (I/O, threading, subprocess, etc.) -- **TypeScript:** N/A (covered briefly in determinism.md) -- **Decision:** all good - ---- - -#### Function Replacement (TypeScript only) -- **Python:** N/A -- **TypeScript:** Explains Date/Math.random deterministic replacement with code example -- **Decision:** all good (TS-specific V8 feature) - ---- - -#### Pass-Through Pattern (Python only) -- **Python:** Code example with imports_passed_through(), when to use -- **TypeScript:** N/A -- **Decision:** all good (Python-specific sandbox pattern) - ---- - -#### Importing Activities (Python only) -- **Python:** Full code example for activity imports -- **TypeScript:** N/A (uses type-only imports, covered in gotchas.md) -- **Decision:** all good - ---- - -#### Disabling the Sandbox (Python only) -- **Python:** Code with sandbox_unrestricted(), warnings -- **TypeScript:** N/A -- **Decision:** all good (Python-specific escape hatch) - ---- - -#### Customizing Invalid Module Members (Python only) -- **Python:** Detailed code for SandboxRestrictions customization -- **TypeScript:** N/A -- **Decision:** all good (Python-specific advanced config) - ---- - -#### Import Notification Policy (Python only) -- **Python:** Code for warning/error policies -- **TypeScript:** N/A -- **Decision:** all good (Python-specific) - ---- - -#### File Organization (Python only) -- **Python:** Directory structure example -- **TypeScript:** N/A (covered in gotchas.md) -- **Decision:** all good - ---- - -#### Common Issues (Python only) -- **Python:** Import errors and non-determinism from libraries -- **TypeScript:** N/A -- **Decision:** all good - ---- - -#### Best Practices (Python only) -- **Python:** 5 items -- **TypeScript:** N/A (covered in determinism.md) -- **Decision:** all good - ---- - -### determinism.md - -#### Overview -- **Core:** One-liner about determinism and replay -- **Python:** One-liner about sandbox protection -- **TypeScript:** One-liner about V8 sandbox -- **Decision:** all good - ---- - -#### Why Determinism Matters -- **Core:** Detailed with Replay Mechanism, Commands/Events table, Non-Determinism Example -- **Python:** "Why Determinism Matters: History Replay" - brief explanation -- **TypeScript:** "Why Determinism Matters" - brief explanation -- **Decision:** all good (Core has conceptual depth, languages are brief) - ---- - -#### Sources of Non-Determinism (Core only) -- **Core:** Detailed categories: Time, Random, External State, Iteration, Threading -- **Python:** N/A (covered in Forbidden Operations) -- **TypeScript:** N/A (covered in Forbidden Operations) -- **Decision:** all good (Core conceptual, languages list forbidden operations) - ---- - -#### Central Concept: Activities (Core only) -- **Core:** Explains activities as primary mechanism for non-deterministic code -- **Python:** N/A -- **TypeScript:** N/A -- **Decision:** all good (important conceptual point in Core) - ---- - -#### SDK Protection / Sandbox -- **Core:** Brief mention of both Python and TypeScript sandboxes -- **Python:** "Sandbox Behavior" - describes isolation mechanisms -- **TypeScript:** "Temporal's V8 Sandbox" - code example + explanation -- **Decision:** all good - ---- - -#### Forbidden Operations -- **Core:** N/A (covered in Sources) -- **Python:** Bullet list of forbidden operations -- **TypeScript:** Code example of forbidden imports/operations -- **Decision:** all good - ---- - -#### Safe Builtin Alternatives (Python only) -- **Core:** N/A -- **Python:** Table mapping forbidden → safe alternatives -- **TypeScript:** N/A -- **Decision:** — (intentional) — Keep as Python-only; TS V8 sandbox handles this automatically - ---- - -#### Detecting Non-Determinism (Core only) -- **Core:** During Execution + Testing with Replay subsections -- **Python:** N/A -- **TypeScript:** N/A -- **Decision:** all good (conceptual content in Core) - ---- - -#### Recovery from Non-Determinism (Core only) -- **Core:** Accidental Change + Intentional Change subsections -- **Python:** N/A -- **TypeScript:** N/A -- **Decision:** all good (conceptual content in Core) - ---- - -#### Testing Replay Compatibility -- **Core:** N/A (covered in Detecting subsection) -- **Python:** Reference to testing.md -- **TypeScript:** Reference to testing.md -- **Decision:** all good - ---- - -#### Best Practices -- **Core:** 5 items -- **Python:** 7 items -- **TypeScript:** 5 items -- **Decision:** all good - ---- - -## Summary - -### patterns.md - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Decided to keep as Core-only:** -- Polling Patterns: Core conceptual explanation sufficient -- Idempotency Patterns: Core conceptual explanation sufficient - -**Intentionally missing (`—`):** -- Dynamic handlers, External workflow handles, Wait conditions, Heartbeat details: language-specific implementation, core has concepts only -- Child Workflow Options: TS-specific (Python shows inline) -- Deterministic Asyncio Alternatives: Python-specific (TS doesn't have this issue) -- Cancellation Handling vs Cancellation Scopes: different idioms per language -- Triggers: TS-specific pattern -- Entity Workflow Pattern: conceptual in core, implementation left to user -- Using Pydantic Models: Python-specific - -**Order alignment:** ✓ Aligned — TS# monotonically increases - -**Style alignment:** ✅ All issues fixed -- ✅ **Queries:** TS now has "Important: must NOT modify state" note -- ✅ **Saga Pattern:** TS now has idempotency note, comments about saving compensation BEFORE activity -- ✅ **Saga Pattern:** TS now uses `log` from `@temporalio/workflow` - -### data-handling.md - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Intentionally missing (`—`):** -- Core column: data handling is implementation-specific, no core concepts doc needed -- Pydantic Integration: Python-specific (TS uses plain JSON/types) -- Protobuf Support: TS-specific section (Python handles protobufs via default converter) -- Deterministic APIs for Values: Python-specific (`workflow.uuid4()`, `workflow.random()`) - -**Order alignment:** ✅ ALIGNED — Reordered TypeScript to match Python order - -**Style alignment:** All TypeScript sections aligned with Python. No changes needed. - -### error-handling.md - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Intentionally missing (`—`):** -- Core column: error handling is implementation-specific, no core concepts doc needed -- Non-Retryable Errors: TS covers inline in Application Failures -- Activity Errors: Python covers in Application Errors -- Workflow Failure: TS-specific section not needed (different SDK design) -- Idempotency Patterns: TS-specific detailed section; Python references core/patterns.md - -**Sections marked DEL:** -- Cancellation Handling in Activities: Move from error-handling.md to patterns.md - -**Order alignment:** ✓ Aligned — TS# monotonically increases - -**Action items:** ✅ All completed -- ✅ **TypeScript:** Added Workflow Failure section with nonRetryable warning -- ✅ **TypeScript:** Removed Cancellation Handling in Activities section -- ✅ **TypeScript:** Replaced Idempotency Patterns with brief reference to core - -**Style alignment:** ✅ All issues fixed -- ✅ **Handling Activity Errors:** TS now uses `log` from `@temporalio/workflow` -- ✅ **Retry Configuration:** TS now has note about preferring defaults - -### gotchas.md - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Decided to keep as-is:** -- Multiple Workers with Different Code: Core-only (conceptual explanation sufficient) -- Heartbeating: Py/TS-only (language-specific code examples, no Core conceptual section needed) - -**Intentionally missing (`—`):** -- Idempotency, Replay Safety, Query Handlers, Error Handling, Retry Policies: Core-only (conceptual) -- Multiple Workers with Different Code: Core-only (conceptual) -- File Organization: Core + Python; TS covers similar in Activity Imports -- Activity Imports: TS-specific (bundling/sandbox concerns) -- Bundling Issues: TS-specific (workflow bundling) -- Async vs Sync Activities: Python-specific -- Cancellation: TS-specific (CancellationScope API) -- Timers and Sleep: TS-specific - -**Order alignment:** N/A after cleanup — Core has conceptual sections, language files have implementation-specific sections - -**Action items:** ✅ All completed -- ✅ **TypeScript DEL:** Removed Idempotency, Replay Safety, Retry Policies, Query Handlers, Error Handling -- ✅ **TypeScript:** Added Wrong Retry Classification section -- ✅ **Python:** Added Timers and Sleep section - -**Style alignment:** ✅ Complete: -- Core: 8 conceptual sections with symptoms/fixes (authoritative for cross-cutting concerns) -- TypeScript: 7 sections (Activity Imports, Bundling, Cancellation, Heartbeating, Testing, Timers, Wrong Retry Classification) -- Python: 5 sections (File Organization, Async vs Sync, Wrong Retry Classification, Heartbeating, Testing) - -### observability.md - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Intentionally missing (`—`):** -- Core column: no core observability.md exists (implementation-specific) -- Search Attributes: Python includes as observability concept; TS keeps in data-handling.md - -**Sections marked DEL:** -- OpenTelemetry Integration: Remove from TypeScript (too detailed) -- Debugging with Event History: Remove from TypeScript (too detailed) - -**Order alignment:** ✓ Aligned — TS# monotonically increases (Py# 2 maps to both TS# 2 and 3, but order preserved) - -**Action items:** ✅ All completed -- ✅ DEL: Removed OpenTelemetry Integration from TypeScript -- ✅ DEL: Removed Debugging with Event History from TypeScript - -**Style alignment:** ✅ Complete. TS is now concise like Python. - -### testing.md - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Intentionally missing (`—`):** -- Core column: no core testing.md exists (implementation-specific) -- Testing Failure Cases: Python-specific section (adding to TS) -- Activity Testing: Python-specific (ActivityEnvironment) (adding to TS) - -**Sections marked DEL:** -- Time Skipping: Remove dedicated section from TypeScript (mention inline like Python) - -**Order alignment:** ✅ ALIGNED — Reordered TS sections to match Python order - -**Action items:** ✅ All completed -- ✅ Added Testing Failure Cases section to TypeScript -- ✅ Added Activity Testing section to TypeScript -- ✅ Reordered TS sections (Signals/Queries before Replay) - -**Style alignment:** ✅ Complete. Python and TypeScript now have matching sections. - -### versioning.md - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Intentionally missing (`—`):** -- Choosing a Strategy: Python missing (TS + Core have decision table) -- Finding Workflows by Version: Core-only section (languages cover in Query Filters subsections) -- Common Mistakes: Core-only section - -**Order alignment:** ✓ Aligned — all three files follow same order (Overview, Why, Patching, Type Versioning, Worker Versioning, Best Practices) - -**Action items:** ✅ All completed -- ✅ Added Choosing a Strategy decision table to Python versioning.md - -**Decided to keep as-is:** -- Common Mistakes: Core-only (conceptual coverage sufficient) - -**Style alignment:** ✓ Well aligned -- Core: Conceptual explanations with decision guidance -- Python/TypeScript: Full code examples matching Core structure -- All three cover the same three approaches consistently - -### {language}.md (top-level files) - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Intentionally missing (`—`):** -- Core column: no core top-level file (these are language entry points) -- Determinism Rules — TS has separate section; Python has subsection in Key Concepts - -**Sections marked DEL:** -- How Temporal Works: History Replay — Remove from TS (reference core/determinism.md instead) - -**Sections marked TODO:** -- File Organization Best Practice — Add to TypeScript - -**Order alignment:** ⚠️ NOT ALIGNED — different structures -- TS has "How Temporal Works" section that Python doesn't have -- TS has separate "Determinism Rules" section; Python has it as Key Concepts subsection -- Python has "File Organization" section that TS doesn't have - -**Action items:** ✅ All completed -- ✅ **FIX BUG:** Fixed TypeScript Additional Resources paths -- ✅ **TypeScript DEL:** Removed "How Temporal Works: History Replay" section (now brief "Understanding Replay") -- ✅ **TypeScript:** Added "File Organization Best Practice" section - -**TypeScript Quick Start gaps:** ✅ All fixed -- ✅ Added "Add Dependency" instruction -- ✅ Added File descriptions explaining purpose -- ✅ Added "Start the dev server" instruction -- ✅ Added "Start the worker" instruction -- ✅ Added client.ts file showing how to execute a workflow -- ✅ Added "Run the workflow" instruction with expected output - -**TypeScript Key Concepts gaps:** ✅ Fixed -- ✅ Added `heartbeat()` mention for long operations - -**TypeScript Common Pitfalls gaps:** ✅ All fixed -- ✅ Added "Forgetting to heartbeat" pitfall -- ✅ Added "Using console.log in workflows" pitfall - -**Style alignment:** ✅ Complete -- Python Quick Demo and TypeScript Quick Start now match (full tutorial with 4 files, run instructions, expected output) -- Python and TypeScript Key Concepts now aligned -- Python and TypeScript Common Pitfalls now aligned - -### determinism-protection.md - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Intentionally missing (`—`):** -- Core column: no core file (sandbox implementation is language-specific) -- Most sections are language-specific due to different sandbox architectures: - - Python: Pass-through pattern, customization APIs, notification policies - - TypeScript: Import blocking, function replacement (V8-specific) - -**Order alignment:** N/A — files have completely different structures (Python: 12 sections, TS: 3 sections) - -**Action items:** -- None — different sandboxes require different documentation - -**Style alignment:** ⚠️ Very different structures (intentional) -- Python: Comprehensive (12 sections) — complex sandbox with many customization options -- TypeScript: Minimal (3 sections) — V8 sandbox is mostly automatic -- This is appropriate given the different sandbox architectures - -### determinism.md - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Intentionally missing (`—`):** -- Sources of Non-Determinism: Core-only (conceptual categories) -- Central Concept: Activities: Core-only (conceptual) -- Forbidden Operations: Language-specific (Core covers in Sources) -- Safe Builtin Alternatives: Python-only (table format) -- Detecting Non-Determinism: Core-only -- Recovery from Non-Determinism: Core-only -- Testing Replay Compatibility: Language-specific (Core covers in Detecting) - -**Order alignment:** ⚠️ NOT ALIGNED — different structures -- Core#5 (SDK Protection) → Py#6, TS#3 -- Languages have Forbidden Operations (Py#3, TS#4) which Core doesn't have as separate section -- Each file follows own logical structure - -**Action items:** -- None — Safe Builtin Alternatives intentionally Python-only (TS V8 sandbox handles automatically) - -**Style alignment:** ✓ Well aligned -- Core: Deep conceptual content (replay mechanism, commands/events, recovery) -- Python: Practical focus (forbidden operations, safe alternatives table, sandbox) -- TypeScript: Practical focus (V8 sandbox, forbidden operations) -- Good division: Core explains "why", languages explain "how" - -### advanced-features.md - -| Section | Core | Python | Py# | TypeScript | TS# | Go | -|---------|------|--------|-----|------------|-----|-----| -| Schedules | — | ✓ | 1 | ✓ | 1 | | -| Async Activity Completion | — | ✓ | 2 | ✓ | 2 | | -| Sandbox Customization | — | ✓ | 3 | — | — | | -| Gevent Compatibility Warning | — | ✓ | 4 | — | — | | -| Worker Tuning | — | ✓ | 5 | ✓ | 3 | | -| Workflow Init Decorator | — | ✓ | 6 | — | — | | -| Workflow Failure Exception Types | — | ✓ | 7 | — | — | | -| Continue-as-New | — | — | — | — | — | | -| Workflow Updates | — | — | — | — | — | | -| Nexus Operations | — | — | — | — | — | | -| Activity Cancellation and Heartbeating | — | — | — | — | — | | -| Sinks | — | — | — | ✓ | 4 | | -| CancellationScope Patterns | — | — | — | — | — | | -| Best Practices | — | — | — | — | — | | - ---- - -## Style Alignment: advanced-features.md - -#### Schedules -- **Python:** Code example with create_schedule, manage schedules -- **TypeScript:** Code example with schedule.create, manage schedules -- **Decision:** all good - ---- - -#### Async Activity Completion -- **Python:** Detailed section with task_token pattern, external completion code -- **TypeScript:** Detailed section with task_token pattern, external completion code -- **Decision:** ✅ FIXED — Added to TypeScript - ---- - -#### Sandbox Customization (Python only) -- **Python:** Brief reference to determinism-protection.md -- **TypeScript:** N/A (has determinism-protection.md directly) -- **Decision:** all good (Python provides helpful cross-reference) - ---- - -#### Gevent Compatibility Warning (Python only) -- **Python:** Warning about gevent incompatibility with workarounds -- **TypeScript:** N/A -- **Decision:** all good (Python-specific concern) - ---- - -#### Worker Tuning -- **Python:** Code example with max_concurrent_*, activity_executor, graceful_shutdown_timeout -- **TypeScript:** Code example with max_concurrent_*, shutdown timeout, cache settings -- **Decision:** ✅ FIXED — Added to TypeScript - ---- - -#### Workflow Init Decorator (Python only) -- **Python:** Code example with @workflow.init -- **TypeScript:** N/A -- **Decision:** all good (Python-specific feature) - ---- - -#### Workflow Failure Exception Types (Python only) -- **Python:** Code examples for per-workflow and worker-level configuration -- **TypeScript:** N/A -- **Decision:** all good (Python-specific configuration) - ---- - -#### Continue-as-New -- **Python:** N/A (in patterns.md) -- **TypeScript:** N/A (removed, covered in patterns.md) -- **Decision:** ✅ DONE — Removed from advanced-features.md (already in patterns.md) - ---- - -#### Workflow Updates -- **Python:** N/A (in patterns.md) -- **TypeScript:** N/A (removed, covered in patterns.md) -- **Decision:** ✅ DONE — Removed from advanced-features.md (already in patterns.md) - ---- - -#### Nexus Operations -- **Python:** N/A -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript (too advanced for reference docs) - ---- - -#### Activity Cancellation and Heartbeating -- **Python:** N/A (Heartbeat Details in patterns.md) -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript (Heartbeat Details already in patterns.md) - ---- - -#### Sinks (TypeScript only) -- **Python:** N/A -- **TypeScript:** Full example with proxySinks, worker implementation -- **Decision:** all good (TS-specific feature for workflow logging) - ---- - -#### CancellationScope Patterns -- **Python:** N/A (has Cancellation Handling in patterns.md) -- **TypeScript:** N/A (removed, covered in patterns.md) -- **Decision:** ✅ DONE — Removed from advanced-features.md (already in patterns.md) - ---- - -#### Best Practices (advanced-features.md) -- **Python:** N/A -- **TypeScript:** N/A (removed) -- **Decision:** ✅ DONE — Removed from TypeScript (best practices covered in individual sections) - ---- - -## Summary: advanced-features.md - -**Sections marked TODO:** -- Async Activity Completion: Add to TypeScript -- Worker Tuning: Add to TypeScript - -**Sections needing review (empty cells):** -- Go column: all empty — Go files not yet created - -**Intentionally missing (`—`):** -- Core column: advanced features are implementation-specific -- Sandbox Customization: TS has determinism-protection.md directly -- Gevent Compatibility Warning: Python-specific -- Workflow Init Decorator: Python-specific (@workflow.init) -- Workflow Failure Exception Types: Python-specific -- Continue-as-New, Workflow Updates: Python has in patterns.md (appropriate location) -- Nexus Operations: Removed (too advanced) -- Sinks: TS-specific feature - -**Sections marked DEL (duplicates/remove in TypeScript):** -- Continue-as-New: Already in patterns.md TS#10 -- Workflow Updates: Already in patterns.md TS#5 -- Nexus Operations: Too advanced for reference docs -- CancellationScope Patterns: Already in patterns.md TS#12 as "Cancellation Scopes" -- Activity Cancellation and Heartbeating: Heartbeat Details already in patterns.md; ActivityCancellationType not needed -- Best Practices: Remove (covered in individual sections) - -**Order alignment:** N/A — files have very different structures; TS has many duplicates that should be removed - -**Action items:** ✅ All completed -1. ✅ **TypeScript DEL:** Removed Continue-as-New, Workflow Updates, CancellationScope Patterns -2. ✅ **TypeScript DEL:** Removed Nexus Operations -3. ✅ **TypeScript DEL:** Removed Activity Cancellation and Heartbeating -4. ✅ **TypeScript DEL:** Removed Best Practices -5. ✅ **TypeScript:** Added Async Activity Completion -6. ✅ **TypeScript:** Added Worker Tuning - -**Style alignment:** ✅ Complete: -- Python: 7 sections (Schedules, Async Activity Completion, Sandbox Customization, Gevent Warning, Worker Tuning, Workflow Init, Failure Exception Types) -- TypeScript: 4 sections (Schedules, Async Activity Completion, Worker Tuning, Sinks) -- Both serve as "miscellaneous advanced topics" not covered elsewhere - ---- - -### Other files - -Not yet inventoried. Add sections as files are reviewed. From 3f8b790d47389dc80d25bf1fdb6d73bb7881802a Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Fri, 27 Feb 2026 17:38:30 -0500 Subject: [PATCH 12/50] Various edits to TypeScript --- references/typescript/advanced-features.md | 22 +-- references/typescript/data-handling.md | 46 ++++-- .../typescript/determinism-protection.md | 5 +- references/typescript/determinism.md | 2 +- references/typescript/error-handling.md | 4 +- references/typescript/gotchas.md | 15 +- references/typescript/observability.md | 11 +- references/typescript/testing.md | 49 ++++-- references/typescript/typescript.md | 10 +- references/typescript/versioning.md | 151 ++++-------------- 10 files changed, 144 insertions(+), 171 deletions(-) diff --git a/references/typescript/advanced-features.md b/references/typescript/advanced-features.md index 12df0c4..575750f 100644 --- a/references/typescript/advanced-features.md +++ b/references/typescript/advanced-features.md @@ -42,27 +42,21 @@ Complete an activity asynchronously from outside the activity function. Useful w ```typescript import { CompleteAsyncError, activityInfo } from '@temporalio/activity'; -export async function asyncActivity(): Promise { - const taskToken = activityInfo().taskToken; - - // Store taskToken somewhere (database, queue, etc.) - await saveTaskToken(taskToken); - - // Throw to indicate async completion +export async function doSomethingAsync(): Promise { + const taskToken: Uint8Array = activityInfo().taskToken; + setTimeout(() => doSomeWork(taskToken), 1000); throw new CompleteAsyncError(); } ``` -**External completion (from another process):** +**External completion (from another process, machine, etc.):** ```typescript import { AsyncCompletionClient } from '@temporalio/client'; -async function completeActivity(taskToken: Uint8Array, result: string) { +async function doSomeWork(taskToken: Uint8Array): Promise { const client = new AsyncCompletionClient(); - - await client.complete(taskToken, result); - // Or for failure: - // await client.fail(taskToken, new Error('Failed')); + // does some work... + await client.complete(taskToken, "Job's done!"); } ``` @@ -88,7 +82,7 @@ const worker = await Worker.create({ maxConcurrentWorkflowTaskExecutions: 100, // Activity execution concurrency (default: 100) - maxConcurrentActivityTaskExecutions: 100, + maxConcurrentActivityTaskExecutions: 200, // Graceful shutdown timeout (default: 0) shutdownGraceTime: '30 seconds', diff --git a/references/typescript/data-handling.md b/references/typescript/data-handling.md index d63f2db..240b2df 100644 --- a/references/typescript/data-handling.md +++ b/references/typescript/data-handling.md @@ -9,22 +9,24 @@ The TypeScript SDK uses data converters to serialize/deserialize workflow inputs The default converter handles: - `undefined` and `null` - `Uint8Array` (as binary) -- Protobuf messages (if configured) - JSON-serializable types +Note: Protobuf support requires using a data converter (`DefaultPayloadConverterWithProtobufs`). See the Protobuf Support section below. + ## Custom Data Converter Create custom converters for special serialization needs. ```typescript +// payload-converter.ts import { - DataConverter, PayloadConverter, + Payload, defaultPayloadConverter, } from '@temporalio/common'; class CustomPayloadConverter implements PayloadConverter { - toPayload(value: unknown): Payload | undefined { + toPayload(value: T): Payload | undefined { // Custom serialization logic return defaultPayloadConverter.toPayload(value); } @@ -35,22 +37,44 @@ class CustomPayloadConverter implements PayloadConverter { } } -const dataConverter: DataConverter = { - payloadConverter: new CustomPayloadConverter(), -}; +export const payloadConverter = new CustomPayloadConverter(); +``` + +```typescript +// client.ts +import { Client } from '@temporalio/client'; -// Apply to client const client = new Client({ - dataConverter, + dataConverter: { + payloadConverterPath: require.resolve('./payload-converter'), + }, }); +``` + +```typescript +// worker.ts +import { Worker } from '@temporalio/worker'; -// Apply to worker const worker = await Worker.create({ - dataConverter, + dataConverter: { + payloadConverterPath: require.resolve('./payload-converter'), + }, // ... }); ``` +## Composition of Payload Converters + +```typescript +import { CompositePayloadConverter } from '@temporalio/common'; + +// The order matters — converters are tried in sequence until one returns a non-null Payload +export const payloadConverter = new CompositePayloadConverter( + new PayloadConverterFoo(), + new PayloadConverterBar(), +); +``` + ## Payload Codec (Encryption) Encrypt sensitive workflow data. @@ -103,7 +127,7 @@ class EncryptionCodec implements PayloadCodec { // Apply codec const dataConverter: DataConverter = { - payloadCodec: new EncryptionCodec(encryptionKey), + payloadCodecs: [new EncryptionCodec(encryptionKey)], }; ``` diff --git a/references/typescript/determinism-protection.md b/references/typescript/determinism-protection.md index ac812f8..8766156 100644 --- a/references/typescript/determinism-protection.md +++ b/references/typescript/determinism-protection.md @@ -2,7 +2,7 @@ ## Overview -The TypeScript SDK runs workflows in an V8 sandbox that provides automatic protection against non-deterministic operations, and replaces common non-deterministic function calls with deterministic variants. This is unique to the TypeScript SDK. +The TypeScript SDK runs workflows in a V8 sandbox that provides automatic protection against non-deterministic operations, and replaces common non-deterministic function calls with deterministic variants. ## Import Blocking @@ -23,6 +23,9 @@ const worker = await Worker.create({ }); ``` +**Important**: Excluded modules are completely unavailable at runtime. Any attempt to call functions from these modules will throw an error. Only exclude modules when you are certain the code paths using them will never execute during workflow execution. + +**Note**: Modules with the `node:` prefix (e.g., `node:fs`) require additional webpack configuration to ignore. You may need to configure the bundler's `externals` or use webpack `resolve.alias` to handle these imports. Use this with *extreme caution*. diff --git a/references/typescript/determinism.md b/references/typescript/determinism.md index 5c5e1c3..b18e9f0 100644 --- a/references/typescript/determinism.md +++ b/references/typescript/determinism.md @@ -62,7 +62,7 @@ Most non-determinism and side effects, such as the above, should be wrapped in A ## Testing Replay Compatibility -Use the `Replayer` class to verify your code changes are compatible with existing histories. See the Workflow Replay Testing section of `references/typescript/testing.md`. +Use `Worker.runReplayHistory()` to verify your code changes are compatible with existing histories. See the Workflow Replay Testing section of `references/typescript/testing.md`. ## Best Practices diff --git a/references/typescript/error-handling.md b/references/typescript/error-handling.md index 608c93b..b14c3a5 100644 --- a/references/typescript/error-handling.md +++ b/references/typescript/error-handling.md @@ -113,4 +113,6 @@ For idempotency patterns (using keys, making activities granular), see `core/pat 2. Set `nonRetryable: true` for permanent failures in activities 3. Configure `nonRetryableErrorTypes` in retry policy 4. Always re-throw errors after handling in workflows -5. Use `log` from `@temporalio/workflow` for replay-safe logging +5. Use the appropriate `log` import for your context: + - In workflows: `import { log } from '@temporalio/workflow'` (replay-safe) + - In activities: `import { log } from '@temporalio/activity'` diff --git a/references/typescript/gotchas.md b/references/typescript/gotchas.md index 4e213e8..062726d 100644 --- a/references/typescript/gotchas.md +++ b/references/typescript/gotchas.md @@ -51,7 +51,9 @@ export async function myWorkflow(): Promise { const worker = await Worker.create({ workflowsPath: require.resolve('./workflows'), bundlerOptions: { - // Include specific packages if needed + // Exclude Node.js-only packages that cause bundling errors + // WARNING: Modules listed here will be completely unavailable + // at workflow runtime - any imports will fail ignoreModules: ['some-node-only-package'], }, }); @@ -207,15 +209,18 @@ test('handles activity failure', async () => { ```typescript import { Worker } from '@temporalio/worker'; +import * as fs from 'fs'; test('replay compatibility', async () => { - const history = await import('./fixtures/workflow_history.json'); + const history = JSON.parse(await fs.promises.readFile('./fixtures/workflow_history.json', 'utf8')); // Fails if current code is incompatible with history - await Worker.runReplayHistory({ - workflowsPath: require.resolve('./workflows'), + await Worker.runReplayHistory( + { + workflowsPath: require.resolve('./workflows'), + }, history, - }); + ); }); ``` diff --git a/references/typescript/observability.md b/references/typescript/observability.md index cf7abb9..38e28c7 100644 --- a/references/typescript/observability.md +++ b/references/typescript/observability.md @@ -10,6 +10,8 @@ Temporal's logger automatically suppresses duplicate messages during replay, pre ### Workflow Logging +Workflows run in a sandboxed environment and cannot use regular Node.js loggers directly. Since SDK 1.8.0, the `@temporalio/workflow` package exports a `log` object that provides replay-aware logging. Internally, it uses Sinks to funnel messages to the Runtime's logger. + ```typescript import { log } from '@temporalio/workflow'; @@ -25,14 +27,15 @@ export async function orderWorkflow(orderId: string): Promise { **Log levels**: `log.debug()`, `log.info()`, `log.warn()`, `log.error()` +The workflow logger automatically suppresses duplicate messages during replay and includes workflow context metadata (workflowId, runId, etc.) on every log entry. + ### Activity Logging ```typescript -import * as activity from '@temporalio/activity'; +import { log } from '@temporalio/activity'; export async function processPayment(orderId: string): Promise { - const context = activity.Context.current(); - context.log.info('Processing payment', { orderId }); + log.info('Processing payment', { orderId }); // Activity logs don't need replay suppression // since completed activities aren't re-executed @@ -115,7 +118,7 @@ Runtime.install({ ## Best Practices -1. Use `log` from `@temporalio/workflow` - never `console.log` in workflows +1. Use `log` from `@temporalio/workflow` in workflows - workflows run in a sandbox and need replay-aware logging 2. Include correlation IDs (orderId, customerId) in log messages 3. Configure Winston or similar for production log aggregation 4. Enable OpenTelemetry for distributed tracing across services diff --git a/references/typescript/testing.md b/references/typescript/testing.md index dcaa95d..e945ed8 100644 --- a/references/typescript/testing.md +++ b/references/typescript/testing.md @@ -2,7 +2,9 @@ ## Overview -The TypeScript SDK provides `TestWorkflowEnvironment` for testing workflows with time-skipping and activity mocking support. +The TypeScript SDK provides `TestWorkflowEnvironment` for testing workflows with time-skipping and activity mocking support. Use `createTimeSkipping()` for automatic time advancement when testing workflows with timers, or `createLocal()` for a full local server without time-skipping. + +**Note:** Prefer to use `createLocal()` for full-featured support. Only use `createTimeSkipping()` if you genuinely need time skipping for testing your workflow. ## Test Environment Setup @@ -43,8 +45,6 @@ describe('Workflow', () => { }); ``` -The test environment automatically skips time when the workflow is waiting on timers, making tests fast. - ## Activity Mocking ```typescript @@ -62,6 +62,12 @@ const worker = await Worker.create({ ## Testing Signals and Queries ```typescript +import { defineQuery, defineSignal } from '@temporalio/workflow'; + +// Define query and signal (typically in a shared file) +const getStatusQuery = defineQuery('getStatus'); +const approveSignal = defineSignal('approve'); + it('handles signals and queries', async () => { await worker.runUntil(async () => { const handle = await client.workflow.start(approvalWorkflow, { @@ -70,11 +76,11 @@ it('handles signals and queries', async () => { }); // Query current state - const status = await handle.query('getStatus'); + const status = await handle.query(getStatusQuery); expect(status).toEqual('pending'); // Send signal - await handle.signal('approve'); + await handle.signal(approveSignal); // Wait for completion const result = await handle.result(); @@ -90,6 +96,7 @@ Test that workflows handle errors correctly: ```typescript import { TestWorkflowEnvironment } from '@temporalio/testing'; import { Worker } from '@temporalio/worker'; +import { WorkflowFailedError } from '@temporalio/client'; import assert from 'assert'; describe('Failure handling', () => { @@ -137,16 +144,37 @@ describe('Failure handling', () => { ```typescript import { Worker } from '@temporalio/worker'; +import { Client, Connection } from '@temporalio/client'; +import fs from 'fs'; describe('Replay', () => { - it('replays workflow history', async () => { - const history = await fetchWorkflowHistory('workflow-id'); + it('replays workflow history from JSON file', async () => { + // Load history from a JSON file (exported from Web UI or Temporal CLI) + const filePath = './history_file.json'; + const history = JSON.parse(await fs.promises.readFile(filePath, 'utf8')); await Worker.runReplayHistory( { workflowsPath: require.resolve('./workflows'), }, - history + history, + 'my-workflow-id' // Optional: provide workflowId if your workflow depends on it + ); + }); + + it('replays workflow history from server', async () => { + // Fetch history programmatically using the client + const connection = await Connection.connect({ address: 'localhost:7233' }); + const client = new Client({ connection, namespace: 'default' }); + const handle = client.workflow.getHandle('my-workflow-id'); + const history = await handle.fetchHistory(); + + await Worker.runReplayHistory( + { + workflowsPath: require.resolve('./workflows'), + }, + history, + 'my-workflow-id' ); }); }); @@ -158,6 +186,7 @@ Test activities in isolation without running a workflow: ```typescript import { MockActivityEnvironment } from '@temporalio/testing'; +import { CancelledFailure } from '@temporalio/activity'; import { myActivity } from './activities'; import assert from 'assert'; @@ -169,7 +198,9 @@ describe('Activity tests', () => { }); it('handles cancellation', async () => { - const env = new MockActivityEnvironment({ cancelled: true }); + const env = new MockActivityEnvironment(); + // Cancel the activity after a short delay + setTimeout(() => env.cancel(), 100); try { await env.run(longRunningActivity, 'input'); assert.fail('Expected cancellation'); diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 2268ff4..3d9beb8 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -2,13 +2,13 @@ ## Overview -The Temporal TypeScript SDK provides a modern async/await approach to building durable workflows. Workflows run in an isolated V8 sandbox for automatic determinism protection. +The Temporal TypeScript SDK provides a modern async/await approach to building durable workflows. Workflows run in an isolated runtime with bundling and automatic replacements for determinism protection. **CRITICAL**: All `@temporalio/*` packages must have the same version number. ## Understanding Replay -Temporal workflows are durable through history replay. For details on how this works, see `core/determinism.md`. +Temporal workflows are durable through history replay. For details on how this works, see `references/core/determinism.md`. ## Quick Start @@ -87,7 +87,7 @@ run().catch(console.error); ### Workflow Definition - Async functions exported from workflow file - Use `proxyActivities()` with type-only imports -- Use `defineSignal()`, `defineQuery()`, `setHandler()` for handlers +- Use `defineSignal()`, `defineQuery()`, `defineUpdate()`, `setHandler()` for handlers ### Activity Definition - Regular async functions @@ -95,7 +95,7 @@ run().catch(console.error); - Use `heartbeat()` for long operations ### Worker Setup -- Use `Worker.create()` with workflowsPath +- Use `Worker.create()` with workflowsPath and activities - Import activities directly (not via proxy) ## File Organization Best Practice @@ -147,7 +147,7 @@ See `determinism.md` for detailed rules. 4. **Missing `proxyActivities`** - Required to call activities from workflows 5. **Forgetting to bundle workflows** - Worker needs workflowsPath 6. **Forgetting to heartbeat** - Long-running activities need `heartbeat()` calls -7. **Using console.log in workflows** - Use `log` from `@temporalio/workflow` for replay-safe logging +7. **Logging in workflows** - Use `import { log } from '@temporalio/workflow'` for replay-safe logging with automatic workflow context. See `references/typescript/observability.md` for more info. ## Writing Tests diff --git a/references/typescript/versioning.md b/references/typescript/versioning.md index 7ce2a62..ac7cb62 100644 --- a/references/typescript/versioning.md +++ b/references/typescript/versioning.md @@ -6,7 +6,7 @@ The TypeScript SDK provides multiple approaches to safely change Workflow code w ## Why Versioning Matters -Temporal provides durable execution through **History Replay**. When a Worker needs to restore Workflow state, it re-executes the Workflow code from the beginning. If you change Workflow code while executions are still running, replay can fail because the new code produces different Commands than the original history. +Temporal provides durable execution through **History Replay**. When a Worker needs to restore Workflow state, it re-executes the Workflow code from the beginning. If you change Workflow code while executions are still running, replay can fail because the new code follows a different execution path, producing a different sequence of Commands than what was originally recorded in the history. Versioning strategies allow you to safely deploy changes without breaking in-progress Workflow Executions. @@ -86,33 +86,13 @@ export async function shippingConfirmation(): Promise { } ``` -### Multiple Patches - -A Workflow can have multiple patches for different changes: - -```typescript -export async function shippingConfirmation(): Promise { - if (patched('sendEmail')) { - await sendEmail(); - } else if (patched('sendTextMessage')) { - await sendTextMessage(); - } else if (patched('sendTweet')) { - await sendTweet(); - } else { - await sendFax(); - } -} -``` - -You can use a single `patchId` for multiple changes deployed together. - ### Query Filters for Versioned Workflows Use List Filters to find Workflows by version: ``` # Find running Workflows with a specific patch -WorkflowType = "shippingConfirmation" AND ExecutionStatus = "Running" AND TemporalChangeVersion="changedNotificationType" +WorkflowType = "shippingConfirmation" AND ExecutionStatus = "Running" AND TemporalChangeVersion = "changedNotificationType" # Find running Workflows without the patch (started before patching) WorkflowType = "shippingConfirmation" AND ExecutionStatus = "Running" AND TemporalChangeVersion IS NULL @@ -164,127 +144,61 @@ After all V1 executions complete, remove the old Workflow function. ## Worker Versioning -Worker Versioning allows multiple Worker versions to run simultaneously, routing Workflows to specific versions without code-level patching. +Worker Versioning allows multiple Worker versions to run simultaneously, routing Workflows to specific versions without code-level patching. Workflows are pinned to the Worker Deployment Version they started on. + +> **Note:** Worker Versioning is currently in Public Preview. The legacy Worker Versioning API (before 2025) will be removed from Temporal Server in March 2026. ### Key Concepts - **Worker Deployment**: A logical name for your application (e.g., "order-service") -- **Worker Deployment Version**: A specific build of your code (deployment name + Build ID) +- **Worker Deployment Version**: A specific build identified by deployment name + Build ID +- **Workflow Pinning**: Workflows complete on the Worker Deployment Version they started on ### Configuring Workers for Versioning ```typescript +import { Worker, NativeConnection } from '@temporalio/worker'; + const worker = await Worker.create({ workflowsPath: require.resolve('./workflows'), taskQueue: 'my-queue', + connection: await NativeConnection.connect({ address: 'temporal:7233' }), workerDeploymentOptions: { useWorkerVersioning: true, version: { deploymentName: 'order-service', - buildId: '1.0.0', // Or git hash, build number, etc. + buildId: '1.0.0', // Git hash, semver, build number, etc. }, - defaultVersioningBehavior: 'PINNED', // Or 'AUTO_UPGRADE' }, - connection: nativeConnection, }); ``` **Configuration options:** - `useWorkerVersioning`: Enables Worker Versioning - `version.deploymentName`: Logical name for your service (consistent across versions) -- `version.buildId`: Unique identifier for this build (git hash, semver, build number) -- `defaultVersioningBehavior`: How Workflows behave when versions change - -### Versioning Behaviors - -#### PINNED Behavior +- `version.buildId`: Unique identifier for this build -Workflows are locked to the Worker version they started on: +### Deployment Workflow -```typescript -workerDeploymentOptions: { - useWorkerVersioning: true, - version: { buildId: '1.0', deploymentName: 'order-service' }, - defaultVersioningBehavior: 'PINNED', -} -``` +1. Deploy new Worker version with a new `buildId` +2. Use the Temporal CLI to set the new version as current: + ```bash + temporal worker deployment set-current-version \ + --deployment-name order-service \ + --build-id 2.0.0 + ``` +3. New Workflows start on the new version +4. Existing Workflows continue on their original version until completion +5. Decommission old Workers once all their Workflows complete -**Characteristics:** -- Workflows run only on their assigned version -- No patching required in Workflow code -- Cannot use other versioning APIs -- Ideal for short-running Workflows where consistency matters +### When to Use Worker Versioning -**Use PINNED when:** -- You want to eliminate version compatibility complexity -- Workflows are short-running -- Stability is more important than getting latest updates - -#### AUTO_UPGRADE Behavior - -Workflows can move to newer Worker versions: - -```typescript -workerDeploymentOptions: { - useWorkerVersioning: true, - version: { buildId: '1.0', deploymentName: 'order-service' }, - defaultVersioningBehavior: 'AUTO_UPGRADE', -} -``` - -**Characteristics:** -- Workflows can be rerouted to new versions -- Once moved to a newer version, cannot return to older ones -- May require patching to handle version transitions -- Ideal for long-running Workflows that need bug fixes - -**Use AUTO_UPGRADE when:** -- Workflows are long-running (weeks or months) -- You want Workflows to benefit from bug fixes -- Migrating from rolling deployments - -### Deployment Strategies - -#### Blue-Green Deployments - -Maintain two environments and switch traffic between them: - -1. Deploy new version to idle environment -2. Run validation tests -3. Switch traffic to new environment -4. Keep old environment for instant rollback - -#### Rainbow Deployments - -Multiple Worker versions run simultaneously: - -```typescript -// Version 1.0 Workers -const worker1 = await Worker.create({ - workerDeploymentOptions: { - useWorkerVersioning: true, - version: { buildId: '1.0', deploymentName: 'order-service' }, - defaultVersioningBehavior: 'PINNED', - }, - // ... -}); - -// Version 2.0 Workers (deployed alongside 1.0) -const worker2 = await Worker.create({ - workerDeploymentOptions: { - useWorkerVersioning: true, - version: { buildId: '2.0', deploymentName: 'order-service' }, - defaultVersioningBehavior: 'PINNED', - }, - // ... -}); -``` +Worker Versioning is best suited for: +- **Short-running Workflows**: Old Workers only need to run briefly during deployment transitions +- **Frequent deployments**: Eliminates the need for code-level patching on every change +- **Blue-green deployments**: Run old and new versions simultaneously with traffic control -**Benefits:** -- Existing PINNED Workflows complete on their original version -- New Workflows use the latest version -- Add new versions without replacing existing ones -- Supports gradual traffic ramping +For long-running Workflows, consider combining Worker Versioning with the Patching API, or use Continue-as-New to move Workflows to newer versions. ## Choosing a Versioning Strategy @@ -292,8 +206,7 @@ const worker2 = await Worker.create({ |----------|----------|------------| | Patching API | Incremental changes to long-running Workflows | Requires maintaining patch branches in code | | Workflow Type Versioning | Major incompatible changes | Requires code duplication and client updates | -| Worker Versioning (PINNED) | Short-running Workflows, new applications | Requires infrastructure to run multiple versions | -| Worker Versioning (AUTO_UPGRADE) | Long-running Workflows, migrations | May require patching for safe transitions | +| Worker Versioning | Short-running Workflows, frequent deploys | Requires running multiple Worker versions simultaneously | ## Best Practices @@ -302,6 +215,4 @@ const worker2 = await Worker.create({ 3. Use List Filters to verify no running Workflows before removing version support 4. Keep Worker Deployment names consistent across all versions 5. Use unique, traceable Build IDs (git hashes, semver, timestamps) -6. Choose PINNED for new applications with short-running Workflows -7. Choose AUTO_UPGRADE when migrating from rolling deployments or for long-running Workflows -8. Test version transitions with replay tests before deploying +6. Test version transitions with replay tests before deploying From e0aec9716bad10bc130527a4dc137a9140942ed8 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Fri, 27 Feb 2026 17:48:39 -0500 Subject: [PATCH 13/50] versioning cleanup --- references/python/versioning.md | 15 --------------- references/typescript/versioning.md | 20 ++------------------ 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/references/python/versioning.md b/references/python/versioning.md index 91f6406..029eaa5 100644 --- a/references/python/versioning.md +++ b/references/python/versioning.md @@ -297,21 +297,6 @@ temporal workflow list --query \ 'TemporalWorkerDeploymentVersion = "my-service:v1.0.0" AND ExecutionStatus = "Running"' ``` -## Choosing a Strategy - -| Scenario | Recommended Approach | -|----------|---------------------| -| Minor bug fix, compatible change | Patching API (`patched()`) | -| Major logic change, incompatible | Workflow Type Versioning (new workflow name) | -| Infrastructure change, gradual rollout | Worker Versioning (Build ID) | -| Need to query/signal old workflows | Patching (keeps same workflow type) | -| Clean break, no backward compatibility | Workflow Type Versioning | - -**Decision factors:** -- **Patching API**: Best for incremental changes where you need to maintain the same workflow type and can gradually migrate -- **Workflow Type Versioning**: Best for major changes where a clean break is acceptable -- **Worker Versioning**: Best for infrastructure-level changes or when you need fine-grained deployment control - ## Best Practices 1. **Check for open executions** before removing old code paths diff --git a/references/typescript/versioning.md b/references/typescript/versioning.md index ac7cb62..e30040d 100644 --- a/references/typescript/versioning.md +++ b/references/typescript/versioning.md @@ -1,16 +1,8 @@ # TypeScript SDK Versioning -## Overview +For conceptual overview and guidance on choosing an approach, see `references/core/versioning.md`. -The TypeScript SDK provides multiple approaches to safely change Workflow code while maintaining compatibility with running Workflows: the Patching API, Workflow Type Versioning, and Worker Versioning. - -## Why Versioning Matters - -Temporal provides durable execution through **History Replay**. When a Worker needs to restore Workflow state, it re-executes the Workflow code from the beginning. If you change Workflow code while executions are still running, replay can fail because the new code follows a different execution path, producing a different sequence of Commands than what was originally recorded in the history. - -Versioning strategies allow you to safely deploy changes without breaking in-progress Workflow Executions. - -## Workflow Versioning with the Patching API +## Patching API The Patching API lets you change Workflow Definitions without causing non-deterministic behavior in running Workflows. @@ -200,14 +192,6 @@ Worker Versioning is best suited for: For long-running Workflows, consider combining Worker Versioning with the Patching API, or use Continue-as-New to move Workflows to newer versions. -## Choosing a Versioning Strategy - -| Strategy | Best For | Trade-offs | -|----------|----------|------------| -| Patching API | Incremental changes to long-running Workflows | Requires maintaining patch branches in code | -| Workflow Type Versioning | Major incompatible changes | Requires code duplication and client updates | -| Worker Versioning | Short-running Workflows, frequent deploys | Requires running multiple Worker versions simultaneously | - ## Best Practices 1. Use descriptive `patchId` names that explain the change From 6455a09e9694d9cf1bc8215579982a0f5a40a1d9 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Fri, 27 Feb 2026 17:53:49 -0500 Subject: [PATCH 14/50] revert python files back to dev --- references/python/determinism.md | 19 +++++++++++++++++-- references/python/gotchas.md | 31 ------------------------------- references/python/python.md | 4 ++-- references/python/versioning.md | 10 ++++++++-- 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/references/python/determinism.md b/references/python/determinism.md index 7276360..cdca358 100644 --- a/references/python/determinism.md +++ b/references/python/determinism.md @@ -6,7 +6,7 @@ The Python SDK runs workflows in a sandbox that provides automatic protection ag ## Why Determinism Matters: History Replay -Temporal provides durable execution through **History Replay**. When a Worker needs to restore workflow state (after a crash, cache eviction, or to continue after a long timer), it re-executes the workflow code from the beginning, which requires the workflow code to be **deterministic**. +Temporal achieves durability through **history replay**. Understanding this mechanism is key to writing correct Workflow code. ## Forbidden Operations @@ -29,7 +29,22 @@ Temporal provides durable execution through **History Replay**. When a Worker ne ## Testing Replay Compatibility -Use the `Replayer` class to verify your code changes are compatible with existing histories. See the Workflow Replay Testing section of `references/python/testing.md`. +Use the `Replayer` class to verify your code changes are compatible with existing histories: + +```python +from temporalio.worker import Replayer +from temporalio.client import WorkflowHistory + +async def test_replay_compatibility(): + replayer = Replayer(workflows=[MyWorkflow]) + + # Test against a saved history + with open("workflow_history.json") as f: + history = WorkflowHistory.from_json("my-workflow-id", f.read()) + + # This will raise NondeterminismError if incompatible + await replayer.replay_workflow(history) +``` ## Sandbox Behavior diff --git a/references/python/gotchas.md b/references/python/gotchas.md index b0735b1..81534c6 100644 --- a/references/python/gotchas.md +++ b/references/python/gotchas.md @@ -170,34 +170,3 @@ It is important to make sure workflows work as expected under failure paths in a ### Not Testing Replay Replay tests help you test that you do not have hidden sources of non-determinism bugs in your workflow code, and should be considered in addition to standard testing. Please see `references/python/testing.md` for more info. - -## Timers and Sleep - -### Using asyncio.sleep - -```python -# BAD: asyncio.sleep is not deterministic during replay -import asyncio - -@workflow.defn -class BadWorkflow: - @workflow.run - async def run(self) -> None: - await asyncio.sleep(60) # Non-deterministic! -``` - -```python -# GOOD: Use workflow.sleep for deterministic timers -from temporalio import workflow -from datetime import timedelta - -@workflow.defn -class GoodWorkflow: - @workflow.run - async def run(self) -> None: - await workflow.sleep(timedelta(seconds=60)) # Deterministic - # Or with string duration: - await workflow.sleep("1 minute") -``` - -**Why this matters:** `asyncio.sleep` uses the system clock, which differs between original execution and replay. `workflow.sleep` creates a durable timer in the event history, ensuring consistent behavior during replay. diff --git a/references/python/python.md b/references/python/python.md index e79d849..a096009 100644 --- a/references/python/python.md +++ b/references/python/python.md @@ -160,14 +160,14 @@ See `references/python/testing.md` for info on writing tests. ## Additional Resources ### Reference Files -- **`references/python/patterns.md`** - Signals, queries, child workflows, saga pattern, etc. +- **`references/python/patterns.md`** - Signals, queries, child workflows, saga pattern - **`references/python/determinism.md`** - Sandbox behavior, safe alternatives, pass-through pattern, history replay - **`references/python/gotchas.md`** - Python-specific mistakes and anti-patterns - **`references/python/error-handling.md`** - ApplicationError, retry policies, non-retryable errors, idempotency - **`references/python/observability.md`** - Logging, metrics, tracing, Search Attributes - **`references/python/testing.md`** - WorkflowEnvironment, time-skipping, activity mocking - **`references/python/sync-vs-async.md`** - Sync vs async activities, event loop blocking, executor configuration -- **`references/python/advanced-features.md`** - Schedules, worker tuning, and more +- **`references/python/advanced-features.md`** - Continue-as-new, updates, schedules, and more - **`references/python/data-handling.md`** - Data converters, Pydantic, payload encryption - **`references/python/versioning.md`** - Patching API, workflow type versioning, Worker Versioning - **`references/python/determinism-protection.md`** - Python sandbox specifics, forbidden operations, pass-through imports diff --git a/references/python/versioning.md b/references/python/versioning.md index 029eaa5..dfb7431 100644 --- a/references/python/versioning.md +++ b/references/python/versioning.md @@ -1,8 +1,14 @@ # Python SDK Versioning -For conceptual overview and guidance on choosing an approach, see `references/core/versioning.md`. +## Overview -## Patching API +Workflow versioning allows you to safely deploy changes to Workflow code without causing non-deterministic errors in running Workflow Executions. The Python SDK provides multiple approaches: the Patching API for code-level version management, Workflow Type versioning for incompatible changes, and Worker Versioning for deployment-level control. + +## Why Versioning is Needed + +When Workers restart after a deployment, they resume open Workflow Executions through History Replay. If the updated Workflow Definition produces a different sequence of Commands than the original code, it causes a non-deterministic error. Versioning ensures backward compatibility by preserving the original execution path for existing workflows while allowing new workflows to use updated code. + +## Workflow Versioning with Patching API ### The patched() Function From c64b0d54773e6132cf830793d37719926da04a69 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Fri, 27 Feb 2026 17:58:55 -0500 Subject: [PATCH 15/50] fix link --- references/typescript/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 3d9beb8..762693f 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -137,7 +137,7 @@ The TypeScript SDK runs workflows in an isolated V8 sandbox. - `condition()` for waiting - Standard JavaScript operations -See `determinism.md` for detailed rules. +See `references/typescript/determinism.md` for detailed rules. ## Common Pitfalls From b3de6c7d06d788e9b5461761694e0d3dc4e049ec Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Fri, 27 Feb 2026 18:37:02 -0500 Subject: [PATCH 16/50] Typo fix Co-authored-by: James Watkins-Harvey --- SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SKILL.md b/SKILL.md index bbe339e..43942d3 100644 --- a/SKILL.md +++ b/SKILL.md @@ -109,7 +109,7 @@ Once you've downloaded the file, extract the downloaded archive and add the temp - **`references/core/interactive-workflows.md`** - Testing signals, updates, queries - **`references/core/dev-management.md`** - Dev cycle & management of server and workers - **`references/core/ai-patterns.md`** - AI/LLM pattern concepts - + Langauge-specific info at `references/{your_language}/determinism.md`, if available. Currently Python only. + + Language-specific info at `references/{your_language}/determinism.md`, if available. Currently Python only. ## Additional Topics - **`references/{your_langauge}/observability.md`** - See for language-specific implementation guidance on observability in Temporal From 37afc1184a3b190449f6fc5d55941b1363438081 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 2 Mar 2026 22:45:44 -0500 Subject: [PATCH 17/50] Update references/typescript/typescript.md Co-authored-by: Chris Olszewski --- references/typescript/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 762693f..ddc7a47 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -2,7 +2,7 @@ ## Overview -The Temporal TypeScript SDK provides a modern async/await approach to building durable workflows. Workflows run in an isolated runtime with bundling and automatic replacements for determinism protection. +The Temporal TypeScript SDK provides a modern Promise based approach to building durable workflows. Workflows are bundled and run in an isolated runtime with automatic replacements for determinism protection. **CRITICAL**: All `@temporalio/*` packages must have the same version number. From e19b37d7137f2477b31df848ed21d5352891ca87 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 2 Mar 2026 22:46:22 -0500 Subject: [PATCH 18/50] Update references/typescript/typescript.md Co-authored-by: Chris Olszewski --- references/typescript/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index ddc7a47..f753ec4 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -17,7 +17,7 @@ Temporal workflows are durable through history replay. For details on how this w npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity ``` -**activities.ts** - Activity definitions (separate file for bundling performance): +**activities.ts** - Activity definitions (separate file to distinguish workflow vs activity code): ```typescript export async function greet(name: string): Promise { return `Hello, ${name}!`; From c17e9e96546b37652e5c9c0eea15f892667c4063 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 2 Mar 2026 22:52:47 -0500 Subject: [PATCH 19/50] remove otel for now --- references/typescript/observability.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/references/typescript/observability.md b/references/typescript/observability.md index 38e28c7..2a2e78d 100644 --- a/references/typescript/observability.md +++ b/references/typescript/observability.md @@ -121,6 +121,5 @@ Runtime.install({ 1. Use `log` from `@temporalio/workflow` in workflows - workflows run in a sandbox and need replay-aware logging 2. Include correlation IDs (orderId, customerId) in log messages 3. Configure Winston or similar for production log aggregation -4. Enable OpenTelemetry for distributed tracing across services -5. Monitor Prometheus metrics for worker health -6. Use Event History for debugging workflow issues +4. Monitor Prometheus metrics for worker health +5. Use Event History for debugging workflow issues From a53bec514b1f59342543e0ce6321b6ee6bb2684e Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 2 Mar 2026 23:03:57 -0500 Subject: [PATCH 20/50] fix console.log --- references/typescript/determinism.md | 1 - references/typescript/observability.md | 2 +- references/typescript/typescript.md | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/references/typescript/determinism.md b/references/typescript/determinism.md index b18e9f0..66d335a 100644 --- a/references/typescript/determinism.md +++ b/references/typescript/determinism.md @@ -55,7 +55,6 @@ export async function workflowWithIds(): Promise { // DO NOT do these in workflows: import fs from 'fs'; // Node.js modules fetch('https://...'); // Network I/O -console.log(); // Side effects (use workflow.log) ``` Most non-determinism and side effects, such as the above, should be wrapped in Activities. diff --git a/references/typescript/observability.md b/references/typescript/observability.md index 2a2e78d..480d5c6 100644 --- a/references/typescript/observability.md +++ b/references/typescript/observability.md @@ -118,7 +118,7 @@ Runtime.install({ ## Best Practices -1. Use `log` from `@temporalio/workflow` in workflows - workflows run in a sandbox and need replay-aware logging +1. Prefer `log` from `@temporalio/workflow` in workflows for replay-aware logging with workflow context. `console.log` also works (it's patched to include workflow ID) 2. Include correlation IDs (orderId, customerId) in log messages 3. Configure Winston or similar for production log aggregation 4. Monitor Prometheus metrics for worker health diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index f753ec4..3b6d4f4 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -147,7 +147,7 @@ See `references/typescript/determinism.md` for detailed rules. 4. **Missing `proxyActivities`** - Required to call activities from workflows 5. **Forgetting to bundle workflows** - Worker needs workflowsPath 6. **Forgetting to heartbeat** - Long-running activities need `heartbeat()` calls -7. **Logging in workflows** - Use `import { log } from '@temporalio/workflow'` for replay-safe logging with automatic workflow context. See `references/typescript/observability.md` for more info. +7. **Logging in workflows** - Prefer `import { log } from '@temporalio/workflow'` for replay-aware logging with workflow context. `console.log` also works (it's patched to include workflow ID). See `references/typescript/observability.md`. ## Writing Tests From 18686b9406874982104739203d629ddfd58a9ca7 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 2 Mar 2026 23:04:39 -0500 Subject: [PATCH 21/50] remove more otel --- references/typescript/observability.md | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/references/typescript/observability.md b/references/typescript/observability.md index 480d5c6..b569ea4 100644 --- a/references/typescript/observability.md +++ b/references/typescript/observability.md @@ -2,7 +2,7 @@ ## Overview -The TypeScript SDK provides replay-aware logging, metrics, and OpenTelemetry integration for production observability. +The TypeScript SDK provides replay-aware logging, metrics, and integrations for production observability. ## Replay-Aware Logging @@ -101,21 +101,6 @@ Runtime.install({ }); ``` -### OTLP Metrics - -```typescript -Runtime.install({ - telemetryOptions: { - metrics: { - otel: { - url: 'http://127.0.0.1:4317', - metricsExportInterval: '1s', - }, - }, - }, -}); -``` - ## Best Practices 1. Prefer `log` from `@temporalio/workflow` in workflows for replay-aware logging with workflow context. `console.log` also works (it's patched to include workflow ID) From 12601e17811b642830bcec74ec24165939e0c7eb Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 2 Mar 2026 23:22:25 -0500 Subject: [PATCH 22/50] package manager agnostic --- references/typescript/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 3b6d4f4..653bde7 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -12,7 +12,7 @@ Temporal workflows are durable through history replay. For details on how this w ## Quick Start -**Add Dependencies:** Install the Temporal SDK packages: +**Add Dependencies:** Install the Temporal SDK packages (use the package manager appropriate for your project): ```bash npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity ``` From 2d3e66f54011ab5d9adcfbc46c82e5b93d4d44c7 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 3 Mar 2026 00:14:42 -0500 Subject: [PATCH 23/50] improve bundleing --- references/typescript/advanced-features.md | 4 +-- .../typescript/determinism-protection.md | 2 +- references/typescript/gotchas.md | 29 +++++++++++++++++++ references/typescript/typescript.md | 11 +++---- references/typescript/versioning.md | 4 +-- 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/references/typescript/advanced-features.md b/references/typescript/advanced-features.md index 575750f..ae51dd4 100644 --- a/references/typescript/advanced-features.md +++ b/references/typescript/advanced-features.md @@ -75,7 +75,7 @@ import { Worker, NativeConnection } from '@temporalio/worker'; const worker = await Worker.create({ connection: await NativeConnection.connect({ address: 'temporal:7233' }), taskQueue: 'my-queue', - workflowsPath: require.resolve('./workflows'), + workflowBundle: { codePath: require.resolve('./workflow-bundle.js') }, // Pre-bundled for production activities, // Workflow execution concurrency (default: 40) @@ -127,7 +127,7 @@ export async function myWorkflow(input: string): Promise { // Implement sink in worker const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), + workflowsPath: require.resolve('./workflows'), // Use workflowBundle for production activities, taskQueue: 'my-queue', sinks: { diff --git a/references/typescript/determinism-protection.md b/references/typescript/determinism-protection.md index 8766156..2dddf69 100644 --- a/references/typescript/determinism-protection.md +++ b/references/typescript/determinism-protection.md @@ -12,7 +12,7 @@ The sandbox blocks imports of `fs`, `https` modules, and any Node/DOM APIs. Othe ```ts const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), + workflowsPath: require.resolve('./workflows'), // bundlerOptions only apply with workflowsPath activities: require('./activities'), taskQueue: 'my-task-queue', bundlerOptions: { diff --git a/references/typescript/gotchas.md b/references/typescript/gotchas.md index 062726d..17b1177 100644 --- a/references/typescript/gotchas.md +++ b/references/typescript/gotchas.md @@ -42,6 +42,35 @@ export async function myWorkflow(): Promise { ## Bundling Issues +### Using workflowsPath in Production + +`workflowsPath` runs the bundler at Worker startup, which is slow and not suitable for production. Use `workflowBundle` with pre-bundled code instead. + +```typescript +// OK for development/testing, BAD for production - bundles at startup +const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + // ... +}); + +// GOOD for production - use pre-bundled code +import { bundleWorkflowCode } from '@temporalio/worker'; + +// Build step (run once at build time) +const bundle = await bundleWorkflowCode({ + workflowsPath: require.resolve('./workflows'), +}); +await fs.promises.writeFile('./workflow-bundle.js', bundle.code); + +// Worker startup (fast, no bundling) +const worker = await Worker.create({ + workflowBundle: { + codePath: require.resolve('./workflow-bundle.js'), + }, + // ... +}); +``` + ### Missing Dependencies in Workflow Bundle ```typescript diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 653bde7..6f56120 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -45,7 +45,7 @@ import * as activities from './activities'; async function run() { const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), + workflowsPath: require.resolve('./workflows'), // For production, use workflowBundle instead activities, taskQueue: 'greeting-queue', }); @@ -95,7 +95,7 @@ run().catch(console.error); - Use `heartbeat()` for long operations ### Worker Setup -- Use `Worker.create()` with workflowsPath and activities +- Use `Worker.create()` with `workflowsPath` (dev) or `workflowBundle` (production) - see `references/typescript/gotchas.md` - Import activities directly (not via proxy) ## File Organization Best Practice @@ -145,9 +145,10 @@ See `references/typescript/determinism.md` for detailed rules. 2. **Version mismatch** - All @temporalio packages must match 3. **Direct I/O in workflows** - Use activities for external calls 4. **Missing `proxyActivities`** - Required to call activities from workflows -5. **Forgetting to bundle workflows** - Worker needs workflowsPath -6. **Forgetting to heartbeat** - Long-running activities need `heartbeat()` calls -7. **Logging in workflows** - Prefer `import { log } from '@temporalio/workflow'` for replay-aware logging with workflow context. `console.log` also works (it's patched to include workflow ID). See `references/typescript/observability.md`. +5. **Forgetting to bundle workflows** - Worker needs `workflowsPath` or `workflowBundle` +6. **Using workflowsPath in production** - Use `workflowBundle` for production (see `references/typescript/gotchas.md`) +7. **Forgetting to heartbeat** - Long-running activities need `heartbeat()` calls +8. **Logging in workflows** - Prefer `import { log } from '@temporalio/workflow'` for replay-aware logging with workflow context. `console.log` also works (it's patched to include workflow ID). See `references/typescript/observability.md`. ## Writing Tests diff --git a/references/typescript/versioning.md b/references/typescript/versioning.md index e30040d..6c1c310 100644 --- a/references/typescript/versioning.md +++ b/references/typescript/versioning.md @@ -110,7 +110,7 @@ Register both Workflows with the Worker: ```typescript const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), + workflowsPath: require.resolve('./workflows'), // Use workflowBundle for production taskQueue: 'pizza-queue', }); ``` @@ -152,7 +152,7 @@ Worker Versioning allows multiple Worker versions to run simultaneously, routing import { Worker, NativeConnection } from '@temporalio/worker'; const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), + workflowsPath: require.resolve('./workflows'), // Use workflowBundle for production taskQueue: 'my-queue', connection: await NativeConnection.connect({ address: 'temporal:7233' }), workerDeploymentOptions: { From da15feff8457850909a86f9cfcd02ba51caaa6a6 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 3 Mar 2026 00:24:55 -0500 Subject: [PATCH 24/50] cut uuid section --- references/typescript/determinism.md | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/references/typescript/determinism.md b/references/typescript/determinism.md index 66d335a..667af82 100644 --- a/references/typescript/determinism.md +++ b/references/typescript/determinism.md @@ -24,31 +24,10 @@ export async function badWorkflow(): Promise { } ``` -The Temporal workflow sandbox will use the same random seed when replaying a workflow, so the above code will **deterministically** generate pseudo-random numbers. +The Temporal workflow sandbox will use the same random seed when replaying a workflow, so the above code will **deterministically** generate pseudo-random numbers. For UUIDs, use `uuid4()` from `@temporalio/workflow` which also uses the seeded PRNG. See `references/typescript/determinism-protection.md` for more information about the sandbox. -## Deterministic UUID Generation - -Generate deterministic UUIDs safe to use in workflows. Uses the workflow seeded PRNG, so the same UUID is generated during replay. - -```typescript -import { uuid4 } from '@temporalio/workflow'; - -export async function workflowWithIds(): Promise { - const childWorkflowId = uuid4(); - await executeChild(childWorkflow, { - workflowId: childWorkflowId, - args: [input], - }); -} -``` - -**When to use:** -- Generating unique IDs for child workflows -- Creating idempotency keys -- Any situation requiring unique identifiers in workflow code - ## Forbidden Operations ```typescript From 6dc6493dfefd12aa95e9a14e1a634514596bc6d8 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 3 Mar 2026 01:17:13 -0500 Subject: [PATCH 25/50] oops, restore python --- references/python/determinism.md | 19 ++----------------- references/python/gotchas.md | 31 +++++++++++++++++++++++++++++++ references/python/python.md | 4 ++-- references/python/versioning.md | 10 ++-------- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/references/python/determinism.md b/references/python/determinism.md index cdca358..7276360 100644 --- a/references/python/determinism.md +++ b/references/python/determinism.md @@ -6,7 +6,7 @@ The Python SDK runs workflows in a sandbox that provides automatic protection ag ## Why Determinism Matters: History Replay -Temporal achieves durability through **history replay**. Understanding this mechanism is key to writing correct Workflow code. +Temporal provides durable execution through **History Replay**. When a Worker needs to restore workflow state (after a crash, cache eviction, or to continue after a long timer), it re-executes the workflow code from the beginning, which requires the workflow code to be **deterministic**. ## Forbidden Operations @@ -29,22 +29,7 @@ Temporal achieves durability through **history replay**. Understanding this mech ## Testing Replay Compatibility -Use the `Replayer` class to verify your code changes are compatible with existing histories: - -```python -from temporalio.worker import Replayer -from temporalio.client import WorkflowHistory - -async def test_replay_compatibility(): - replayer = Replayer(workflows=[MyWorkflow]) - - # Test against a saved history - with open("workflow_history.json") as f: - history = WorkflowHistory.from_json("my-workflow-id", f.read()) - - # This will raise NondeterminismError if incompatible - await replayer.replay_workflow(history) -``` +Use the `Replayer` class to verify your code changes are compatible with existing histories. See the Workflow Replay Testing section of `references/python/testing.md`. ## Sandbox Behavior diff --git a/references/python/gotchas.md b/references/python/gotchas.md index 81534c6..b0735b1 100644 --- a/references/python/gotchas.md +++ b/references/python/gotchas.md @@ -170,3 +170,34 @@ It is important to make sure workflows work as expected under failure paths in a ### Not Testing Replay Replay tests help you test that you do not have hidden sources of non-determinism bugs in your workflow code, and should be considered in addition to standard testing. Please see `references/python/testing.md` for more info. + +## Timers and Sleep + +### Using asyncio.sleep + +```python +# BAD: asyncio.sleep is not deterministic during replay +import asyncio + +@workflow.defn +class BadWorkflow: + @workflow.run + async def run(self) -> None: + await asyncio.sleep(60) # Non-deterministic! +``` + +```python +# GOOD: Use workflow.sleep for deterministic timers +from temporalio import workflow +from datetime import timedelta + +@workflow.defn +class GoodWorkflow: + @workflow.run + async def run(self) -> None: + await workflow.sleep(timedelta(seconds=60)) # Deterministic + # Or with string duration: + await workflow.sleep("1 minute") +``` + +**Why this matters:** `asyncio.sleep` uses the system clock, which differs between original execution and replay. `workflow.sleep` creates a durable timer in the event history, ensuring consistent behavior during replay. diff --git a/references/python/python.md b/references/python/python.md index a096009..e79d849 100644 --- a/references/python/python.md +++ b/references/python/python.md @@ -160,14 +160,14 @@ See `references/python/testing.md` for info on writing tests. ## Additional Resources ### Reference Files -- **`references/python/patterns.md`** - Signals, queries, child workflows, saga pattern +- **`references/python/patterns.md`** - Signals, queries, child workflows, saga pattern, etc. - **`references/python/determinism.md`** - Sandbox behavior, safe alternatives, pass-through pattern, history replay - **`references/python/gotchas.md`** - Python-specific mistakes and anti-patterns - **`references/python/error-handling.md`** - ApplicationError, retry policies, non-retryable errors, idempotency - **`references/python/observability.md`** - Logging, metrics, tracing, Search Attributes - **`references/python/testing.md`** - WorkflowEnvironment, time-skipping, activity mocking - **`references/python/sync-vs-async.md`** - Sync vs async activities, event loop blocking, executor configuration -- **`references/python/advanced-features.md`** - Continue-as-new, updates, schedules, and more +- **`references/python/advanced-features.md`** - Schedules, worker tuning, and more - **`references/python/data-handling.md`** - Data converters, Pydantic, payload encryption - **`references/python/versioning.md`** - Patching API, workflow type versioning, Worker Versioning - **`references/python/determinism-protection.md`** - Python sandbox specifics, forbidden operations, pass-through imports diff --git a/references/python/versioning.md b/references/python/versioning.md index dfb7431..029eaa5 100644 --- a/references/python/versioning.md +++ b/references/python/versioning.md @@ -1,14 +1,8 @@ # Python SDK Versioning -## Overview +For conceptual overview and guidance on choosing an approach, see `references/core/versioning.md`. -Workflow versioning allows you to safely deploy changes to Workflow code without causing non-deterministic errors in running Workflow Executions. The Python SDK provides multiple approaches: the Patching API for code-level version management, Workflow Type versioning for incompatible changes, and Worker Versioning for deployment-level control. - -## Why Versioning is Needed - -When Workers restart after a deployment, they resume open Workflow Executions through History Replay. If the updated Workflow Definition produces a different sequence of Commands than the original code, it causes a non-deterministic error. Versioning ensures backward compatibility by preserving the original execution path for existing workflows while allowing new workflows to use updated code. - -## Workflow Versioning with Patching API +## Patching API ### The patched() Function From e5e6caa1ed53936188e64bf762cf742f62128061 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 3 Mar 2026 14:39:44 -0500 Subject: [PATCH 26/50] Clean up Date.now() --- references/typescript/determinism-protection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/typescript/determinism-protection.md b/references/typescript/determinism-protection.md index 2dddf69..cf7a5e6 100644 --- a/references/typescript/determinism-protection.md +++ b/references/typescript/determinism-protection.md @@ -51,6 +51,6 @@ for (let x = 0; x < 10; ++x) { } ``` -This means that if you want a workflow to truly be able to check current real-world physical time, you should retrieve the time in an activity. You should consider which is semantically appropriate for your situation. +Generally, this is the behavior you want. Additionally, `FinalizationRegistry` and `WeakRef` are removed because v8's garbage collector is not deterministic. From 366792d97fa5a0d2f57a6c4d3c9c692e18f0b132 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 3 Mar 2026 14:43:29 -0500 Subject: [PATCH 27/50] Fix "bad" workflow thats not bad at all --- references/typescript/determinism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/typescript/determinism.md b/references/typescript/determinism.md index 667af82..8fd03ef 100644 --- a/references/typescript/determinism.md +++ b/references/typescript/determinism.md @@ -13,7 +13,7 @@ Temporal provides durable execution through **History Replay**. When a Worker ne The Temporal TypeScript SDK executes all workflow code in sandbox, which (among other things), replaces common non-deterministic functions with deterministic variants. As an example, consider the code below: ```ts -export async function badWorkflow(): Promise { +export async function myWorkflow(): Promise { await importData(); if (Math.random() > 0.5) { From aa40658abcb5eac315e447aa9bd3c65b608875f8 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 3 Mar 2026 17:26:49 -0500 Subject: [PATCH 28/50] soften sleep --- references/typescript/determinism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/typescript/determinism.md b/references/typescript/determinism.md index 8fd03ef..47f8948 100644 --- a/references/typescript/determinism.md +++ b/references/typescript/determinism.md @@ -46,6 +46,6 @@ Use `Worker.runReplayHistory()` to verify your code changes are compatible with 1. Use type-only imports for activities in workflow files 2. Match all @temporalio package versions -3. Use `sleep()` from workflow package, not `setTimeout` directly +3. Prefer `sleep()` from workflow package — `setTimeout` works but `sleep()` handles cancellation scopes more clearly 4. Keep workflows focused on orchestration 5. Test with replay to verify determinism From 8a627f95494c550b22a23fe99abd182a9ff8cabe Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 3 Mar 2026 17:29:04 -0500 Subject: [PATCH 29/50] improve timestamp language --- references/typescript/determinism-protection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/typescript/determinism-protection.md b/references/typescript/determinism-protection.md index cf7a5e6..54303ba 100644 --- a/references/typescript/determinism-protection.md +++ b/references/typescript/determinism-protection.md @@ -34,7 +34,7 @@ Use this with *extreme caution*. Functions like `Math.random()`, `Date`, and `setTimeout()` are replaced by deterministic versions. -Date-related functions will *deterministically* return the date at the *start of the workflow*, and will only progress in time when a semantic time operation occurs in Temporal, like a durable sleep. For example: +Date-related functions return the timestamp at which the current workflow task was initially executed. That timestamp remains the same when the workflow task is replayed, and only advances when a durable operation occurs (like `sleep()`). For example: ```ts import { sleep } from '@temporalio/workflow'; From 8678055cc6e8f4fce00dc360d45db8de627cdd9a Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 11:00:19 -0500 Subject: [PATCH 30/50] Caveat protobuf --- references/typescript/data-handling.md | 30 ++++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/references/typescript/data-handling.md b/references/typescript/data-handling.md index 240b2df..d45b0f6 100644 --- a/references/typescript/data-handling.md +++ b/references/typescript/data-handling.md @@ -75,6 +75,22 @@ export const payloadConverter = new CompositePayloadConverter( ); ``` +## Protobuf Support + +Using Protocol Buffers for type-safe serialization. + +**Note:** JSON serialization (the default) is preferred for TypeScript applications—it's simpler and more performant. Use Protobuf only when interoperating with services that require it. + +```typescript +import { DefaultPayloadConverterWithProtobufs } from '@temporalio/common/lib/protobufs'; + +const dataConverter: DataConverter = { + payloadConverter: new DefaultPayloadConverterWithProtobufs({ + protobufRoot: myProtobufRoot, + }), +}; +``` + ## Payload Codec (Encryption) Encrypt sensitive workflow data. @@ -228,20 +244,6 @@ export async function orderWorkflow(): Promise { } ``` -## Protobuf Support - -Using Protocol Buffers for type-safe serialization. - -```typescript -import { DefaultPayloadConverterWithProtobufs } from '@temporalio/common/lib/protobufs'; - -const dataConverter: DataConverter = { - payloadConverter: new DefaultPayloadConverterWithProtobufs({ - protobufRoot: myProtobufRoot, - }), -}; -``` - ## Large Payloads For large data, consider: From 1dc52205b59577c410629efdd7deb260f8f29fd5 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 11:33:58 -0500 Subject: [PATCH 31/50] Add size limits --- references/core/gotchas.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/references/core/gotchas.md b/references/core/gotchas.md index 1206494..fa9dc64 100644 --- a/references/core/gotchas.md +++ b/references/core/gotchas.md @@ -150,3 +150,19 @@ See language-specific gotchas for details. **The Fix**: - **Retryable**: Network errors, timeouts, rate limits, temporary unavailability - **Non-retryable**: Invalid input, authentication failures, business rule violations, resource not found + +## Payload Size Limits + +**The Problem**: Temporal has built-in limits on payload sizes. Exceeding them causes workflows to fail. + +**Limits**: +- Max 2MB per individual payload +- Max 4MB per gRPC message +- Max 50MB for complete workflow history (aim for <10MB in practice) + +**Symptoms**: +- Payload too large errors +- gRPC message size exceeded errors +- Workflow history growing unboundedly + +**The Fix**: Store large data externally (S3/GCS) and pass references, use compression codecs, or chunk data across multiple activities. See Large Payloads section in language-specific data handling docs. From cf15c53ff2bc4a5476eed9d238c4ec8399ef02c9 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 11:58:34 -0500 Subject: [PATCH 32/50] re-work large payload handling --- references/core/gotchas.md | 2 +- references/core/patterns.md | 47 ++++++++++++++++++++++++++ references/python/data-handling.md | 30 ++-------------- references/typescript/data-handling.md | 35 ++----------------- 4 files changed, 54 insertions(+), 60 deletions(-) diff --git a/references/core/gotchas.md b/references/core/gotchas.md index fa9dc64..96f8ed1 100644 --- a/references/core/gotchas.md +++ b/references/core/gotchas.md @@ -165,4 +165,4 @@ See language-specific gotchas for details. - gRPC message size exceeded errors - Workflow history growing unboundedly -**The Fix**: Store large data externally (S3/GCS) and pass references, use compression codecs, or chunk data across multiple activities. See Large Payloads section in language-specific data handling docs. +**The Fix**: Store large data externally (S3/GCS) and pass references, use compression codecs, or chunk data across multiple activities. See the Large Data Handling pattern in `references/core/patterns.md`. diff --git a/references/core/patterns.md b/references/core/patterns.md index 2f37465..3057e2e 100644 --- a/references/core/patterns.md +++ b/references/core/patterns.md @@ -331,6 +331,53 @@ Run: This ensures that on replay, already-completed steps are skipped. +## Large Data Handling + +**Purpose**: Handle data that exceeds Temporal's payload limits without polluting workflow history. + +**Limits** (see `references/core/gotchas.md` for details): +- Max 2MB per individual payload +- Max 4MB per gRPC message +- Max 50MB for workflow history (aim for <10MB) + +**Key Principle**: Large data should never flow through workflow history. Activities read and write large data directly, passing only small references through the workflow. + +**Wrong Approach**: +``` +Workflow + │ + ├── downloadFromStorage(ref) ──▶ returns large data (enters history) + │ + ├── processData(largeData) ────▶ large data as argument (enters history AGAIN) + │ + └── uploadToStorage(result) ───▶ large data as argument (enters history AGAIN) +``` + +This defeats the purpose—large data enters workflow history multiple times. + +**Correct Approach**: +``` +Workflow + │ + └── processLargeData(inputRef) ──▶ returns outputRef (small string) + │ + └── Activity internally: + download(inputRef) → process → upload → return outputRef +``` + +The workflow only handles references (small strings). The activity does all large data operations internally. + +**Implementation Pattern**: +1. Accept a reference (URL, S3 key, database ID) as activity input +2. Download/fetch the large data inside the activity +3. Process the data inside the activity +4. Upload/store the result inside the activity +5. Return only a reference to the result + +**Other Strategies**: +- **Compression**: Use a PayloadCodec to compress data automatically +- **Chunking**: Split large collections across multiple activities, each handling a subset + ## Local Activities **Purpose**: Reduce latency for short, lightweight operations by skipping the task queue. ONLY use these when necessary for performance. Do NOT use these by default, as they are not durable and distributed. diff --git a/references/python/data-handling.md b/references/python/data-handling.md index d6a40d2..662101e 100644 --- a/references/python/data-handling.md +++ b/references/python/data-handling.md @@ -202,29 +202,6 @@ class OrderWorkflow: ... ``` -## Large Payloads - -For large data, consider: - -1. **Store externally**: Put large data in S3/GCS, pass references in workflows -2. **Use Payload Codec**: Compress payloads automatically -3. **Chunk data**: Split large lists across multiple activities - -```python -# Example: Reference pattern for large data -@activity.defn -async def upload_to_storage(data: bytes) -> str: - """Upload data and return reference.""" - key = f"data/{uuid.uuid4()}" - await storage_client.upload(key, data) - return key - -@activity.defn -async def download_from_storage(key: str) -> bytes: - """Download data by reference.""" - return await storage_client.download(key) -``` - ## Deterministic APIs for Values Use these APIs within workflows for deterministic random values and UUIDs: @@ -247,8 +224,7 @@ class MyWorkflow: ## Best Practices 1. Use Pydantic for input/output validation -2. Keep payloads small (< 2MB recommended) +2. Keep payloads small—see `references/core/gotchas.md` for limits 3. Encrypt sensitive data with PayloadCodec -4. Store large data externally with references -5. Use dataclasses for simple data structures -6. Use `workflow.uuid4()` and `workflow.random()` for deterministic values +4. Use dataclasses for simple data structures +5. Use `workflow.uuid4()` and `workflow.random()` for deterministic values diff --git a/references/typescript/data-handling.md b/references/typescript/data-handling.md index d45b0f6..bfd4925 100644 --- a/references/typescript/data-handling.md +++ b/references/typescript/data-handling.md @@ -244,39 +244,10 @@ export async function orderWorkflow(): Promise { } ``` -## Large Payloads - -For large data, consider: - -1. **Store externally**: Put large data in S3/GCS, pass references in workflows -2. **Use compression codec**: Compress payloads automatically -3. **Chunk data**: Split large arrays across multiple activities - -```typescript -// Example: Reference pattern for large data -import { proxyActivities } from '@temporalio/workflow'; - -const { uploadToStorage, downloadFromStorage } = proxyActivities({ - startToCloseTimeout: '5 minutes', -}); - -export async function processLargeDataWorkflow(dataRef: string): Promise { - // Download data from storage using reference - const data = await downloadFromStorage(dataRef); - - // Process data... - const result = await processData(data); - - // Upload result and return reference - const resultRef = await uploadToStorage(result); -} -``` - ## Best Practices -1. Keep payloads small (< 2MB recommended) +1. Keep payloads small—see `references/core/gotchas.md` for limits 2. Use search attributes for business-level visibility and filtering 3. Encrypt sensitive data with PayloadCodec -4. Store large data externally with references -5. Use memo for non-searchable metadata -6. Configure the same data converter on both client and worker +4. Use memo for non-searchable metadata +5. Configure the same data converter on both client and worker From b11b28f4eabf093ce50c00391b6617f1fc0c909d Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 12:09:21 -0500 Subject: [PATCH 33/50] don't always re-throw --- references/typescript/error-handling.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/references/typescript/error-handling.md b/references/typescript/error-handling.md index b14c3a5..7072fbd 100644 --- a/references/typescript/error-handling.md +++ b/references/typescript/error-handling.md @@ -112,7 +112,8 @@ For idempotency patterns (using keys, making activities granular), see `core/pat 1. Use specific error types for different failure modes 2. Set `nonRetryable: true` for permanent failures in activities 3. Configure `nonRetryableErrorTypes` in retry policy -4. Always re-throw errors after handling in workflows -5. Use the appropriate `log` import for your context: +4. Log errors before re-raising +5. Use `ApplicationFailure` to catch activity failures in workflows +6. Use the appropriate `log` import for your context: - In workflows: `import { log } from '@temporalio/workflow'` (replay-safe) - In activities: `import { log } from '@temporalio/activity'` From 81bbcc61d073bf65f799cd74962aaf4823d38eef Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 13:12:08 -0500 Subject: [PATCH 34/50] improve logs --- references/typescript/observability.md | 2 +- references/typescript/typescript.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/references/typescript/observability.md b/references/typescript/observability.md index b569ea4..1ea5567 100644 --- a/references/typescript/observability.md +++ b/references/typescript/observability.md @@ -103,7 +103,7 @@ Runtime.install({ ## Best Practices -1. Prefer `log` from `@temporalio/workflow` in workflows for replay-aware logging with workflow context. `console.log` also works (it's patched to include workflow ID) +1. Use `log` from `@temporalio/workflow` for production observability. For temporary print debugging, `console.log()` is fine—it's direct and immediate, whereas `log` goes through sinks which may lose messages on workflow errors 2. Include correlation IDs (orderId, customerId) in log messages 3. Configure Winston or similar for production log aggregation 4. Monitor Prometheus metrics for worker health diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 6f56120..756d939 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -148,7 +148,7 @@ See `references/typescript/determinism.md` for detailed rules. 5. **Forgetting to bundle workflows** - Worker needs `workflowsPath` or `workflowBundle` 6. **Using workflowsPath in production** - Use `workflowBundle` for production (see `references/typescript/gotchas.md`) 7. **Forgetting to heartbeat** - Long-running activities need `heartbeat()` calls -8. **Logging in workflows** - Prefer `import { log } from '@temporalio/workflow'` for replay-aware logging with workflow context. `console.log` also works (it's patched to include workflow ID). See `references/typescript/observability.md`. +8. **Logging in workflows** - For observability, use `import { log } from '@temporalio/workflow'` (routes through sinks). For temporary print debugging, `console.log()` is fine—it's direct and immediate, whereas `log` may lose messages on workflow errors. ## Writing Tests From 2d1007502e0dcbab9da1ee5c384af2ba18386903 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 13:24:37 -0500 Subject: [PATCH 35/50] Add missing await gotcha --- references/python/python.md | 3 ++- references/typescript/typescript.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/references/python/python.md b/references/python/python.md index e79d849..130b1eb 100644 --- a/references/python/python.md +++ b/references/python/python.md @@ -151,7 +151,8 @@ with workflow.unsafe.imports_passed_through(): 4. **Forgetting to heartbeat** - Long activities need `activity.heartbeat()` 5. **Using gevent** - Incompatible with SDK 6. **Using `print()` in workflows** - Use `workflow.logger` instead for replay-safe logging -7. **Mixing Workflows and Activities in same file** - Causes unnecessary reloads, hurts performance, bad structure. +7. **Mixing Workflows and Activities in same file** - Causes unnecessary reloads, hurts performance, bad structure +8. **Forgetting to wait on activity calls** - `workflow.execute_activity()` is async; you must eventually await it (directly or via `asyncio.gather()` for parallel execution) ## Writing Tests diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 756d939..b5782f1 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -149,6 +149,7 @@ See `references/typescript/determinism.md` for detailed rules. 6. **Using workflowsPath in production** - Use `workflowBundle` for production (see `references/typescript/gotchas.md`) 7. **Forgetting to heartbeat** - Long-running activities need `heartbeat()` calls 8. **Logging in workflows** - For observability, use `import { log } from '@temporalio/workflow'` (routes through sinks). For temporary print debugging, `console.log()` is fine—it's direct and immediate, whereas `log` may lose messages on workflow errors. +9. **Forgetting to wait on activity calls** - Activity calls return Promises; you must eventually await them (directly or via `Promise.all()` for parallel execution) ## Writing Tests From b59282fcf9cf6cd0bf355a458b4f17b60a975355 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 13:29:02 -0500 Subject: [PATCH 36/50] caveat dynamic singnal handlers --- references/python/patterns.md | 2 +- references/typescript/patterns.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/references/python/patterns.md b/references/python/patterns.md index 018103f..636e583 100644 --- a/references/python/patterns.md +++ b/references/python/patterns.md @@ -26,7 +26,7 @@ class OrderWorkflow: ### Dynamic Signal Handlers -For handling signals with names not known at compile time: +For handling signals with names not known at compile time. Use cases for this pattern are rare — most workflows should use statically defined signal handlers. ```python @workflow.defn diff --git a/references/typescript/patterns.md b/references/typescript/patterns.md index dda49db..cca006f 100644 --- a/references/typescript/patterns.md +++ b/references/typescript/patterns.md @@ -27,7 +27,7 @@ export async function orderWorkflow(): Promise { ## Dynamic Signal Handlers -For handling signals with names not known at compile time: +For handling signals with names not known at compile time. Use cases for this pattern are rare — most workflows should use statically defined signal handlers. ```typescript import { setDefaultSignalHandler, condition } from '@temporalio/workflow'; From 340bca3cd162b44bf84f1cd14f055e5d2c5bc26f Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 13:30:16 -0500 Subject: [PATCH 37/50] caveat dynamic query handlers --- references/python/patterns.md | 2 ++ references/typescript/patterns.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/references/python/patterns.md b/references/python/patterns.md index 636e583..513879a 100644 --- a/references/python/patterns.md +++ b/references/python/patterns.md @@ -75,6 +75,8 @@ class StatusWorkflow: ### Dynamic Query Handlers +For handling queries with names not known at compile time. Use cases for this pattern are rare — most workflows should use statically defined query handlers. + ```python @workflow.query(dynamic=True) def handle_query(self, name: str, args: Sequence[RawValue]) -> Any: diff --git a/references/typescript/patterns.md b/references/typescript/patterns.md index cca006f..8f9e7aa 100644 --- a/references/typescript/patterns.md +++ b/references/typescript/patterns.md @@ -74,7 +74,7 @@ export async function progressWorkflow(): Promise { ## Dynamic Query Handlers -For handling queries with names not known at compile time: +For handling queries with names not known at compile time. Use cases for this pattern are rare — most workflows should use statically defined query handlers. ```typescript import { setDefaultQueryHandler } from '@temporalio/workflow'; From 21c3b86df3f1de6f29af17b744f0914c6948957d Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 13:37:57 -0500 Subject: [PATCH 38/50] warning on patching process --- references/python/versioning.md | 4 ++++ references/typescript/versioning.md | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/references/python/versioning.md b/references/python/versioning.md index 029eaa5..429fecf 100644 --- a/references/python/versioning.md +++ b/references/python/versioning.md @@ -36,6 +36,10 @@ class ShippingWorkflow: ### Three-Step Patching Process +Patching is a three-step process for safely deploying changes. + +**Warning:** Failing to follow this process correctly will result in non-determinism errors for in-flight workflows. + **Step 1: Patch in New Code** Add the patch with both old and new code paths: diff --git a/references/typescript/versioning.md b/references/typescript/versioning.md index 6c1c310..b169f3a 100644 --- a/references/typescript/versioning.md +++ b/references/typescript/versioning.md @@ -31,7 +31,9 @@ export async function myWorkflow(): Promise { ### Three-Step Patching Process -Patching is a three-step process for safely deploying changes: +Patching is a three-step process for safely deploying changes. + +**Warning:** Failing to follow this process correctly will result in non-determinism errors for in-flight workflows. #### Step 1: Patch in New Code From 56e5bc4eead17639a974885e389a1a4f3348addd Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 13:43:24 -0500 Subject: [PATCH 39/50] fix activity logger rationale --- references/typescript/observability.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/references/typescript/observability.md b/references/typescript/observability.md index 1ea5567..10244d7 100644 --- a/references/typescript/observability.md +++ b/references/typescript/observability.md @@ -36,13 +36,12 @@ import { log } from '@temporalio/activity'; export async function processPayment(orderId: string): Promise { log.info('Processing payment', { orderId }); - - // Activity logs don't need replay suppression - // since completed activities aren't re-executed return 'payment-id-123'; } ``` +The activity logger adds contextual metadata (activity ID, type, namespace) and funnels messages to the runtime's logger for consistent collection. + ## Customizing the Logger ### Basic Configuration From fe82cb914283f95707ecc3bcf7a1ad3c1afac7a4 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 16:24:38 -0500 Subject: [PATCH 40/50] Simplify timers --- references/typescript/patterns.md | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/references/typescript/patterns.md b/references/typescript/patterns.md index 8f9e7aa..de1455b 100644 --- a/references/typescript/patterns.md +++ b/references/typescript/patterns.md @@ -374,24 +374,11 @@ export async function processLargeFile(filePath: string): Promise { ## Timers ```typescript -import { sleep, CancellationScope } from '@temporalio/workflow'; +import { sleep } from '@temporalio/workflow'; export async function timerWorkflow(): Promise { await sleep('1 hour'); - - const timerScope = new CancellationScope(); - const timerPromise = timerScope.run(() => sleep('1 hour')); - - setHandler(cancelSignal, () => { - timerScope.cancel(); - }); - - try { - await timerPromise; - return 'Timer completed'; - } catch { - return 'Timer cancelled'; - } + return 'Timer fired'; } ``` From 6841d580e7ce1f29c42080241d9fbefb0ede352d Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 16:35:46 -0500 Subject: [PATCH 41/50] Heartbeat info --- references/python/gotchas.md | 2 ++ references/typescript/gotchas.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/references/python/gotchas.md b/references/python/gotchas.md index b0735b1..2d65763 100644 --- a/references/python/gotchas.md +++ b/references/python/gotchas.md @@ -161,6 +161,8 @@ await workflow.execute_activity( ) ``` +Set heartbeat timeout as high as acceptable for your use case — each heartbeat counts as an action. + ## Testing ### Not Testing Failures diff --git a/references/typescript/gotchas.md b/references/typescript/gotchas.md index 17b1177..67de5ec 100644 --- a/references/typescript/gotchas.md +++ b/references/typescript/gotchas.md @@ -198,6 +198,8 @@ const { processChunk } = proxyActivities({ }); ``` +Set heartbeat timeout as high as acceptable for your use case — each heartbeat counts as an action. + ## Testing ### Not Testing Failures From 08b69578edc554a317a841745d26ef8c689b1bd8 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Wed, 4 Mar 2026 17:01:20 -0500 Subject: [PATCH 42/50] fix wrong setTimeout --- references/typescript/gotchas.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/references/typescript/gotchas.md b/references/typescript/gotchas.md index 67de5ec..2c0045a 100644 --- a/references/typescript/gotchas.md +++ b/references/typescript/gotchas.md @@ -257,20 +257,4 @@ test('replay compatibility', async () => { ## Timers and Sleep -### Using JavaScript setTimeout - -```typescript -// BAD - setTimeout is not durable -export async function delayedWorkflow(): Promise { - await new Promise(resolve => setTimeout(resolve, 60000)); // Not durable! - await activities.doWork(); -} - -// GOOD - Use workflow sleep -import { sleep } from '@temporalio/workflow'; - -export async function delayedWorkflow(): Promise { - await sleep('1 minute'); // Durable, survives restarts - await activities.doWork(); -} -``` +`setTimeout` works in workflows (the SDK mocks it), but `sleep()` from `@temporalio/workflow` is preferred because its interaction with cancellation scopes is more intuitive. See Timers in `references/typescript/patterns.md`. From a2c2f1cbf9e0dae5a32ab611d1e38e1803893128 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Thu, 5 Mar 2026 09:17:29 -0500 Subject: [PATCH 43/50] correct when to use patching --- references/core/versioning.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/references/core/versioning.md b/references/core/versioning.md index 5cbc34c..226bb83 100644 --- a/references/core/versioning.md +++ b/references/core/versioning.md @@ -53,17 +53,21 @@ else: ### When to Use -- Adding new activities or steps -- Changing activity parameters -- Reordering operations -- Any change that would cause non-determinism +- Adding, removing, or reordering activities/child workflows +- Changing which activity/child workflow is called +- Any change that alters the Command sequence ### When NOT to Use -- Changes to activity implementations (activities aren't replayed) -- Adding new signal/query handlers (additive changes are safe) +- Changing activity implementations (activities aren't replayed) +- Changing arguments passed to activities or child workflows +- Changing retry policies +- Changing timer durations +- Adding new signal/query/update handlers (additive changes are safe) - Bug fixes that don't change Command sequence +Unnecessary patching adds complexity and can make workflow code unmanageable. + ## Approach 2: Workflow Type Versioning ### Concept From 36a119766343bdffd89a024b3122bf6541161368 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Thu, 5 Mar 2026 09:34:16 -0500 Subject: [PATCH 44/50] add context to waiting on handlers --- references/python/patterns.md | 7 +++---- references/typescript/patterns.md | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/references/python/patterns.md b/references/python/patterns.md index 513879a..9a09dd2 100644 --- a/references/python/patterns.md +++ b/references/python/patterns.md @@ -297,10 +297,9 @@ class MyWorkflow: ## Waiting for All Handlers to Finish -### WHY: Ensure all signal/update handlers complete before workflow exits -### WHEN: -- **Workflows with async handlers** - Prevent data loss from in-flight handlers -- **Before continue-as-new** - Ensure handlers complete before resetting +Signal and update handlers should generally be non-async (avoid running activities from them). Otherwise, the workflow may complete before handlers finish their execution. However, making handlers non-async sometimes requires workarounds that add complexity. + +When async handlers are necessary, use `wait_condition(all_handlers_finished)` at the end of your workflow (or before continue-as-new) to prevent completion until all pending handlers complete. ```python @workflow.defn diff --git a/references/typescript/patterns.md b/references/typescript/patterns.md index de1455b..e134147 100644 --- a/references/typescript/patterns.md +++ b/references/typescript/patterns.md @@ -327,10 +327,9 @@ export async function approvalWorkflow(): Promise { ## Waiting for All Handlers to Finish -### WHY: Ensure all signal/update handlers complete before workflow exits -### WHEN: -- **Workflows with async handlers** - Prevent data loss from in-flight handlers -- **Before continue-as-new** - Ensure handlers complete before resetting +Signal and update handlers should generally be non-async (avoid running activities from them). Otherwise, the workflow may complete before handlers finish their execution. However, making handlers non-async sometimes requires workarounds that add complexity. + +When async handlers are necessary, use `condition(allHandlersFinished)` at the end of your workflow (or before continue-as-new) to prevent completion until all pending handlers complete. ```typescript import { condition, allHandlersFinished } from '@temporalio/workflow'; From 4ca3291784358588532bf28f80431cdc3d6b1219 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Thu, 5 Mar 2026 10:27:56 -0500 Subject: [PATCH 45/50] ~ version constraints --- references/typescript/gotchas.md | 24 ++++-------------------- references/typescript/typescript.md | 2 ++ 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/references/typescript/gotchas.md b/references/typescript/gotchas.md index 2c0045a..efd2534 100644 --- a/references/typescript/gotchas.md +++ b/references/typescript/gotchas.md @@ -90,27 +90,11 @@ const worker = await Worker.create({ ### Package Version Mismatches -All `@temporalio/*` packages must have the same version: - -```json -// BAD - Version mismatch -{ - "dependencies": { - "@temporalio/client": "1.9.0", - "@temporalio/worker": "1.8.0", - "@temporalio/workflow": "1.9.1" - } -} +All `@temporalio/*` packages must have the same version. This can be verified by running `npm ls` or the appropriate command for your package manager. -// GOOD - All versions match -{ - "dependencies": { - "@temporalio/client": "1.9.0", - "@temporalio/worker": "1.9.0", - "@temporalio/workflow": "1.9.0" - } -} -``` +### Package Version Constraints - Prod vs. Non-Prod + +For production apps, you should use ~ version constraints (bug fixes only) on Temporal packages. For non-production apps, you may use ^ constraints (the npm default) instead. ## Wrong Retry Classification diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index b5782f1..3c7860b 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -17,6 +17,8 @@ Temporal workflows are durable through history replay. For details on how this w npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity ``` +Note: if you are working in production, it is strongly advised to use ~ version constraints, i.e. `npm install ... --save-prefix='~'`. + **activities.ts** - Activity definitions (separate file to distinguish workflow vs activity code): ```typescript export async function greet(name: string): Promise { From 0d22b88ec3be0d0782c9cb04ddf55783438602c3 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Thu, 5 Mar 2026 10:28:18 -0500 Subject: [PATCH 46/50] npm caveat --- references/typescript/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/typescript/typescript.md b/references/typescript/typescript.md index 3c7860b..9918ee7 100644 --- a/references/typescript/typescript.md +++ b/references/typescript/typescript.md @@ -17,7 +17,7 @@ Temporal workflows are durable through history replay. For details on how this w npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity ``` -Note: if you are working in production, it is strongly advised to use ~ version constraints, i.e. `npm install ... --save-prefix='~'`. +Note: if you are working in production, it is strongly advised to use ~ version constraints, i.e. `npm install ... --save-prefix='~'` if using NPM. **activities.ts** - Activity definitions (separate file to distinguish workflow vs activity code): ```typescript From 048ce454137382ee0da86de9c8ddf67ff3ea0ac0 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Thu, 5 Mar 2026 11:53:45 -0500 Subject: [PATCH 47/50] Add understanding of patching memoization --- references/python/versioning.md | 2 ++ references/typescript/versioning.md | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/references/python/versioning.md b/references/python/versioning.md index 429fecf..abd4445 100644 --- a/references/python/versioning.md +++ b/references/python/versioning.md @@ -34,6 +34,8 @@ class ShippingWorkflow: - For replay with the marker: `patched()` returns `True` (history includes this patch) - For replay without the marker: `patched()` returns `False` (history predates this patch) +**Python-specific behavior:** The `patched()` return value is memoized on first call. This means you cannot reliably use `patched()` in loops—it will return the same value every iteration. Workaround: append a sequence number to the patch ID for each iteration (e.g., `f"my-change-{i}"`). + ### Three-Step Patching Process Patching is a three-step process for safely deploying changes. diff --git a/references/typescript/versioning.md b/references/typescript/versioning.md index b169f3a..a9f57a2 100644 --- a/references/typescript/versioning.md +++ b/references/typescript/versioning.md @@ -29,6 +29,13 @@ export async function myWorkflow(): Promise { - During replay, if the history contains a marker with the same `patchId`, `patched()` returns `true` - During replay, if no matching marker exists, `patched()` returns `false` +**TypeScript-specific behavior:** Unlike Python/.NET/Ruby, `patched()` is not memoized when it returns `false`. This means you can use `patched()` in loops. However, if a single patch requires coordinated behavioral changes at different points in your workflow, you may need to manually memoize the result: + +```typescript +const useNewBehavior = patched('my-change'); +// Use useNewBehavior at multiple points in workflow +``` + ### Three-Step Patching Process Patching is a three-step process for safely deploying changes. From 168a6bf82923a1300f28906a0731c48e1bad7a87 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Thu, 5 Mar 2026 17:58:16 -0500 Subject: [PATCH 48/50] heartbeat -> handle cancellation --- references/core/patterns.md | 27 +++++++++++++++++++++++ references/python/patterns.md | 36 ++++++++++++++++++++++--------- references/typescript/patterns.md | 28 +++++++++++++++++------- 3 files changed, 73 insertions(+), 18 deletions(-) diff --git a/references/core/patterns.md b/references/core/patterns.md index 3057e2e..93f774d 100644 --- a/references/core/patterns.md +++ b/references/core/patterns.md @@ -378,6 +378,33 @@ The workflow only handles references (small strings). The activity does all larg - **Compression**: Use a PayloadCodec to compress data automatically - **Chunking**: Split large collections across multiple activities, each handling a subset +## Activity Heartbeating + +**Purpose**: Enable cancellation delivery and progress tracking for long-running activities. + +**Why Heartbeat**: +1. **Support activity cancellation** - Cancellations are delivered to activities via heartbeat. Activities that don't heartbeat won't know they've been cancelled. +2. **Resume progress after failure** - Heartbeat details persist across retries, allowing activities to resume where they left off. +3. **Detect stuck activities** - If an activity stops heartbeating, Temporal can time it out and retry. + +**How Cancellation Works**: +``` +Workflow requests activity cancellation + │ + ▼ +Temporal Service marks activity for cancellation + │ + ▼ +Activity calls heartbeat() + │ + ├── Not cancelled: heartbeat succeeds, continues + │ + └── Cancelled: heartbeat raises exception + Activity can catch this to perform cleanup +``` + +**Key Point**: If an activity never heartbeats, it will run to completion even if cancelled—it has no way to learn about the cancellation. + ## Local Activities **Purpose**: Reduce latency for short, lightweight operations by skipping the task queue. ONLY use these when necessary for performance. Do NOT use these by default, as they are not durable and distributed. diff --git a/references/python/patterns.md b/references/python/patterns.md index 9a09dd2..762977b 100644 --- a/references/python/patterns.md +++ b/references/python/patterns.md @@ -313,31 +313,47 @@ class MyWorkflow: return "done" ``` -## Activity Heartbeat Details - Updatable side-data usable in long-running activities +## Activity Heartbeat Details + +### WHY: +- **Support activity cancellation** - Cancellations are delivered via heartbeat; activities that don't heartbeat won't know they've been cancelled +- **Resume progress after worker failure** - Heartbeat details persist across retries + +**Cancellation exceptions:** +- Async activities: `asyncio.CancelledError` +- Sync threaded activities: `temporalio.exceptions.CancelledError` -### WHY: Resume activity progress after worker failure ### WHEN: +- **Cancellable activities** - Any activity that should respond to cancellation - **Long-running activities** - Track progress for resumability - **Checkpointing** - Save progress periodically ```python +from temporalio.exceptions import CancelledError + @activity.defn def process_large_file(file_path: str) -> str: # Get heartbeat details from previous attempt (if any) heartbeat_details = activity.info().heartbeat_details start_line = heartbeat_details[0] if heartbeat_details else 0 - with open(file_path) as f: - for i, line in enumerate(f): - if i < start_line: - continue # Skip already processed lines + try: + with open(file_path) as f: + for i, line in enumerate(f): + if i < start_line: + continue # Skip already processed lines - process_line(line) + process_line(line) - # Heartbeat with progress - activity.heartbeat(i + 1) + # Heartbeat with progress + # If cancelled, heartbeat() raises CancelledError + activity.heartbeat(i + 1) - return "completed" + return "completed" + except CancelledError: + # Perform cleanup on cancellation + cleanup() + raise ``` ## Timers diff --git a/references/typescript/patterns.md b/references/typescript/patterns.md index e134147..878f9f0 100644 --- a/references/typescript/patterns.md +++ b/references/typescript/patterns.md @@ -345,13 +345,17 @@ export async function handlerAwareWorkflow(): Promise { ## Activity Heartbeat Details -### WHY: Resume activity progress after worker failure +### WHY: +- **Support activity cancellation** - Cancellations are delivered via heartbeat; activities that don't heartbeat won't know they've been cancelled +- **Resume progress after worker failure** - Heartbeat details persist across retries + ### WHEN: +- **Cancellable activities** - Any activity that should respond to cancellation - **Long-running activities** - Track progress for resumability - **Checkpointing** - Save progress periodically ```typescript -import { heartbeat, activityInfo } from '@temporalio/activity'; +import { heartbeat, activityInfo, CancelledFailure } from '@temporalio/activity'; export async function processLargeFile(filePath: string): Promise { const info = activityInfo(); @@ -360,13 +364,21 @@ export async function processLargeFile(filePath: string): Promise { const lines = await readFileLines(filePath); - for (let i = startLine; i < lines.length; i++) { - await processLine(lines[i]); - // Heartbeat with progress - heartbeat(i + 1); + try { + for (let i = startLine; i < lines.length; i++) { + await processLine(lines[i]); + // Heartbeat with progress + // If activity is cancelled, heartbeat() throws CancelledFailure + heartbeat(i + 1); + } + return 'completed'; + } catch (e) { + if (e instanceof CancelledFailure) { + // Perform cleanup on cancellation + await cleanup(); + } + throw e; } - - return 'completed'; } ``` From b72f18824ce743494fd2568566444781ea27ec97 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Thu, 5 Mar 2026 18:27:23 -0500 Subject: [PATCH 49/50] Improve activity cancellation in gotchas --- references/core/gotchas.md | 28 ++++++++++++ references/python/gotchas.md | 75 ++++++++++++++++++++++++++++++++ references/typescript/gotchas.md | 70 ++++++++++++++++++++++++++++- 3 files changed, 172 insertions(+), 1 deletion(-) diff --git a/references/core/gotchas.md b/references/core/gotchas.md index 96f8ed1..55b6ddb 100644 --- a/references/core/gotchas.md +++ b/references/core/gotchas.md @@ -151,6 +151,34 @@ See language-specific gotchas for details. - **Retryable**: Network errors, timeouts, rate limits, temporary unavailability - **Non-retryable**: Invalid input, authentication failures, business rule violations, resource not found +## Cancellation Handling + +### Not Handling Workflow Cancellation + +**The Problem**: When a workflow is cancelled, cleanup code after the cancellation point doesn't run unless explicitly protected. + +**Symptoms**: +- Resources not released after cancellation +- Incomplete compensation/rollback +- Leaked state + +**The Fix**: Use language-specific cancellation scopes or try/finally blocks to ensure cleanup runs even on cancellation. See language-specific gotchas for implementation details. + +### Not Handling Activity Cancellation + +**The Problem**: Activities must opt in to receive cancellation. Without proper handling, a cancelled activity continues running to completion, wasting resources. + +**Requirements for activity cancellation**: +1. **Heartbeating** - Cancellation is delivered via heartbeat. Activities that don't heartbeat won't know they've been cancelled. +2. **Checking for cancellation** - Activity must explicitly check for cancellation or await a cancellation signal. + +**Symptoms**: +- Cancelled activities running to completion +- Wasted compute on work that will be discarded +- Delayed workflow cancellation + +**The Fix**: Heartbeat regularly and check for cancellation. See language-specific gotchas for implementation patterns. + ## Payload Size Limits **The Problem**: Temporal has built-in limits on payload sizes. Exceeding them causes workflows to fail. diff --git a/references/python/gotchas.md b/references/python/gotchas.md index 2d65763..95ebe8a 100644 --- a/references/python/gotchas.md +++ b/references/python/gotchas.md @@ -163,6 +163,81 @@ await workflow.execute_activity( Set heartbeat timeout as high as acceptable for your use case — each heartbeat counts as an action. +## Cancellation + +### Not Handling Workflow Cancellation + +```python +# BAD - Cleanup doesn't run on cancellation +@workflow.defn +class BadWorkflow: + @workflow.run + async def run(self) -> None: + await workflow.execute_activity( + acquire_resource, + start_to_close_timeout=timedelta(minutes=5), + ) + await workflow.execute_activity( + do_work, + start_to_close_timeout=timedelta(minutes=5), + ) + await workflow.execute_activity( + release_resource, # Never runs if cancelled! + start_to_close_timeout=timedelta(minutes=5), + ) + +# GOOD - Use try/finally for cleanup +@workflow.defn +class GoodWorkflow: + @workflow.run + async def run(self) -> None: + await workflow.execute_activity( + acquire_resource, + start_to_close_timeout=timedelta(minutes=5), + ) + try: + await workflow.execute_activity( + do_work, + start_to_close_timeout=timedelta(minutes=5), + ) + finally: + # Runs even on cancellation + await workflow.execute_activity( + release_resource, + start_to_close_timeout=timedelta(minutes=5), + ) +``` + +### Not Handling Activity Cancellation + +Activities must **opt in** to receive cancellation. This requires: +1. **Heartbeating** - Cancellation is delivered via heartbeat +2. **Catching the cancellation exception** - Exception is raised when heartbeat detects cancellation + +**Cancellation exceptions:** +- Async activities: `asyncio.CancelledError` +- Sync threaded activities: `temporalio.exceptions.CancelledError` + +```python +# BAD - Activity ignores cancellation +@activity.defn +async def long_activity() -> None: + await do_expensive_work() # Runs to completion even if cancelled +``` + +```python +# GOOD - Heartbeat and catch cancellation +@activity.defn +async def long_activity() -> None: + try: + for item in items: + activity.heartbeat() + await process(item) + except asyncio.CancelledError: + await cleanup() + raise +``` + ## Testing ### Not Testing Failures diff --git a/references/typescript/gotchas.md b/references/typescript/gotchas.md index efd2534..d234f74 100644 --- a/references/typescript/gotchas.md +++ b/references/typescript/gotchas.md @@ -116,7 +116,7 @@ For detailed guidance on error classification and retry policies, see `error-han ## Cancellation -### Not Handling Cancellation +### Not Handling Workflow Cancellation ```typescript // BAD - Cleanup doesn't run on cancellation @@ -142,6 +142,74 @@ export async function workflowWithCleanup(): Promise { } ``` +### Not Handling Activity Cancellation + +Activities must **opt in** to receive cancellation. This requires: +1. **Heartbeating** - Cancellation is delivered via heartbeat +2. **Checking for cancellation** - Either await `Context.current().cancelled` or use `cancellationSignal()` + +```typescript +// BAD - Activity ignores cancellation +export async function longActivity(): Promise { + await doExpensiveWork(); // Runs to completion even if cancelled +} +``` + +```typescript +// GOOD - Heartbeat in background and race work against cancellation promise +import { Context, CancelledFailure } from '@temporalio/activity'; + +export async function longActivity(): Promise { + // Heartbeat in background so cancellation can be delivered + let heartbeatEnabled = true; + (async () => { + while (heartbeatEnabled) { + await Context.current().sleep(5000); + Context.current().heartbeat(); + } + })().catch(() => {}); + + try { + await Promise.race([ + Context.current().cancelled, // Rejects with CancelledFailure + doExpensiveWork(), + ]); + } catch (err) { + if (err instanceof CancelledFailure) { + await cleanup(); + } + throw err; + } finally { + heartbeatEnabled = false; + } +} +``` + +```typescript +// GOOD - Use AbortSignal with libraries that support it +import fetch from 'node-fetch'; +import { cancellationSignal, heartbeat } from '@temporalio/activity'; +import type { AbortSignal as FetchAbortSignal } from 'node-fetch/externals'; + +export async function cancellableFetch(url: string): Promise { + const response = await fetch(url, { signal: cancellationSignal() as FetchAbortSignal }); + + const contentLength = parseInt(response.headers.get('Content-Length')!); + let bytesRead = 0; + const chunks: Buffer[] = []; + + for await (const chunk of response.body) { + if (!(chunk instanceof Buffer)) throw new TypeError('Expected Buffer'); + bytesRead += chunk.length; + chunks.push(chunk); + heartbeat(bytesRead / contentLength); // Heartbeat to keep cancellation delivery alive + } + return Buffer.concat(chunks); +} +``` + +**Note:** `Promise.race` doesn't stop the losing promise—it continues running. Use `cancellationSignal()` or explicitly abort sub-operations when cleanup requires stopping in-flight work. + ## Heartbeating ### Forgetting to Heartbeat Long Activities From 1ebcf2b36d9623199d260ce458cf85ea4ae76736 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Thu, 5 Mar 2026 21:36:09 -0500 Subject: [PATCH 50/50] fix async completion client --- references/typescript/advanced-features.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/references/typescript/advanced-features.md b/references/typescript/advanced-features.md index ae51dd4..17b7e61 100644 --- a/references/typescript/advanced-features.md +++ b/references/typescript/advanced-features.md @@ -51,12 +51,12 @@ export async function doSomethingAsync(): Promise { **External completion (from another process, machine, etc.):** ```typescript -import { AsyncCompletionClient } from '@temporalio/client'; +import { Client } from '@temporalio/client'; async function doSomeWork(taskToken: Uint8Array): Promise { - const client = new AsyncCompletionClient(); + const client = new Client(); // does some work... - await client.complete(taskToken, "Job's done!"); + await client.activity.complete(taskToken, "Job's done!"); } ```