# Temporal TypeScript SDK Temporal is a distributed, scalable, durable, and highly available orchestration engine used to execute asynchronous, long-running business logic in a scalable and resilient way. The Temporal TypeScript SDK provides the framework for authoring Workflows and Activities using TypeScript or JavaScript, enabling developers to build fault-tolerant distributed applications that automatically handle failures, retries, and state persistence. The SDK consists of several core packages: `@temporalio/client` for starting and interacting with Workflows, `@temporalio/worker` for running Workflows and Activities, `@temporalio/workflow` for authoring Workflow code, `@temporalio/activity` for Activity context and utilities, and `@temporalio/testing` for testing Workflows. The SDK requires Node.js 20, 22, or 24 and connects to a Temporal Server to orchestrate durable executions. ## Installation ```bash # Client-level features (starting/interacting with Workflows) npm install @temporalio/client @temporalio/common # Worker-level features (running Workflows, Activities, Nexus Operations) npm install @temporalio/worker @temporalio/workflow @temporalio/activity @temporalio/common ``` --- ## Client - Starting and Managing Workflows The Client class provides the primary interface for starting Workflow executions, sending signals, queries, and updates to running Workflows, and managing Workflow lifecycle. ```typescript import { Client, Connection } from '@temporalio/client'; import { myWorkflow } from './workflows'; async function main() { // Connect to Temporal Server (defaults to localhost:7233) const connection = await Connection.connect({ address: 'localhost:7233' }); const client = new Client({ connection, namespace: 'default', }); // Start a workflow and get a handle const handle = await client.workflow.start(myWorkflow, { taskQueue: 'my-task-queue', workflowId: 'workflow-' + Date.now(), args: ['argument1', 42], }); console.log(`Started workflow ${handle.workflowId}`); // Wait for the workflow result const result = await handle.result(); console.log('Workflow result:', result); // Or execute workflow and wait for result in one call const directResult = await client.workflow.execute(myWorkflow, { taskQueue: 'my-task-queue', workflowId: 'workflow-direct-' + Date.now(), args: ['arg1'], }); } main().catch(console.error); ``` --- ## Worker - Running Workflows and Activities The Worker connects to Temporal Server and polls for tasks, executing Workflows and Activities. Workers must register both Workflow code (via path or bundle) and Activity functions. ```typescript import { NativeConnection, Worker } from '@temporalio/worker'; import * as activities from './activities'; async function run() { // Create a connection to Temporal Server const connection = await NativeConnection.connect({ address: 'localhost:7233', }); try { const worker = await Worker.create({ connection, namespace: 'default', taskQueue: 'my-task-queue', // Path to workflow file(s) - will be bundled automatically workflowsPath: require.resolve('./workflows'), // Register activities directly activities, }); console.log('Worker started, polling for tasks...'); // Run until shutdown signal received await worker.run(); } finally { await connection.close(); } } run().catch((err) => { console.error('Worker failed:', err); process.exit(1); }); ``` --- ## Defining Workflows Workflows are deterministic functions that orchestrate Activities and other Workflows. They use special APIs for timers, Activities, signals, queries, and updates that maintain determinism during replay. ```typescript import { proxyActivities, sleep, defineSignal, defineQuery, defineUpdate, setHandler, condition, workflowInfo, } from '@temporalio/workflow'; import type * as activities from './activities'; // Proxy activities with timeout configuration const { sendEmail, processPayment, updateInventory } = proxyActivities({ startToCloseTimeout: '5 minutes', retry: { maximumAttempts: 3, }, }); // Define signals, queries, and updates export const cancelOrderSignal = defineSignal<[string]>('cancelOrder'); export const getStatusQuery = defineQuery('getStatus'); export const updateQuantityUpdate = defineUpdate('updateQuantity'); export async function orderWorkflow(orderId: string, items: string[]): Promise { let status = 'processing'; let quantity = items.length; let cancelled = false; // Set up signal handler setHandler(cancelOrderSignal, (reason: string) => { status = `cancelled: ${reason}`; cancelled = true; }); // Set up query handler setHandler(getStatusQuery, () => status); // Set up update handler with validator setHandler( updateQuantityUpdate, (newQuantity: number) => { quantity = newQuantity; return quantity; }, { validator: (newQuantity: number) => { if (newQuantity < 0) throw new Error('Quantity cannot be negative'); }, } ); // Wait for either cancellation or 30 seconds const shouldCancel = await condition(() => cancelled, '30 seconds'); if (shouldCancel) { return `Order ${orderId} was cancelled`; } // Process order steps status = 'validating payment'; await processPayment(orderId, quantity * 10); status = 'updating inventory'; await updateInventory(items); status = 'sending confirmation'; await sendEmail(`order-${orderId}@example.com`, 'Order confirmed!'); // Sleep for demonstration await sleep('5 seconds'); status = 'completed'; return `Order ${orderId} completed with ${quantity} items`; } ``` --- ## Defining Activities Activities are regular async functions that perform side effects like API calls, database operations, or file I/O. They run outside the Workflow sandbox and can use any Node.js APIs. ```typescript import { Context, heartbeat, log, sleep, cancellationSignal } from '@temporalio/activity'; // Simple activity export async function sendEmail(to: string, body: string): Promise { log.info('Sending email', { to }); // Actual email sending logic here await someEmailService.send({ to, body }); } // Activity with heartbeating for long-running operations export async function processLargeFile(fileId: string): Promise { const info = Context.current().info; log.info('Processing file', { fileId, attempt: info.attempt }); // Resume from last heartbeat if retrying let processedBytes = info.heartbeatDetails ?? 0; const totalBytes = 1000000; while (processedBytes < totalBytes) { // Process chunk processedBytes += 10000; // Report progress - enables cancellation detection heartbeat(processedBytes); // Simulate work await sleep(100); } return processedBytes; } // Activity with cancellation support using AbortSignal export async function fetchWithCancellation(url: string): Promise { const response = await fetch(url, { signal: cancellationSignal(), }); return response.text(); } // Activity that accesses workflow context export async function getWorkflowContext(): Promise { const info = Context.current().info; return { activityId: info.activityId, workflowId: info.workflowExecution.workflowId, runId: info.workflowExecution.runId, attempt: info.attempt, taskQueue: info.taskQueue, }; } ``` --- ## Signals, Queries, and Updates Workflows can receive external input through Signals (fire-and-forget), Queries (read-only), and Updates (synchronous mutations with return values). ```typescript // In workflow file import { defineSignal, defineQuery, defineUpdate, setHandler, condition } from '@temporalio/workflow'; export const addItemSignal = defineSignal<[string]>('addItem'); export const getItemsQuery = defineQuery('getItems'); export const removeItemUpdate = defineUpdate('removeItem'); export async function shoppingCartWorkflow(): Promise { const items: string[] = []; setHandler(addItemSignal, (item: string) => { items.push(item); }); setHandler(getItemsQuery, () => [...items]); setHandler(removeItemUpdate, (item: string) => { const index = items.indexOf(item); if (index > -1) { items.splice(index, 1); return true; } return false; }); // Wait until checkout signal or timeout await condition(() => false, '1 hour'); return items; } // In client code import { Client } from '@temporalio/client'; import { addItemSignal, getItemsQuery, removeItemUpdate } from './workflows'; async function interactWithWorkflow(client: Client, workflowId: string) { const handle = client.workflow.getHandle(workflowId); // Send a signal (fire-and-forget) await handle.signal(addItemSignal, 'laptop'); await handle.signal(addItemSignal, 'mouse'); // Query current state (read-only, immediate response) const items = await handle.query(getItemsQuery); console.log('Current items:', items); // ['laptop', 'mouse'] // Execute an update (synchronous mutation with return value) const removed = await handle.executeUpdate(removeItemUpdate, { args: ['laptop'] }); console.log('Item removed:', removed); // true // Start update and wait for acceptance only const updateHandle = await handle.startUpdate(removeItemUpdate, { args: ['mouse'], waitForStage: 'ACCEPTED', }); const result = await updateHandle.result(); console.log('Update result:', result); } ``` --- ## Child Workflows Workflows can start and manage child Workflows for decomposing complex business logic or implementing fan-out patterns. ```typescript import { executeChild, startChild, ParentClosePolicy } from '@temporalio/workflow'; // Child workflow definition export async function processItemWorkflow(itemId: string): Promise { // Process individual item return `Processed ${itemId}`; } // Parent workflow that orchestrates children export async function batchProcessWorkflow(itemIds: string[]): Promise { const results: string[] = []; // Sequential execution with executeChild for (const itemId of itemIds.slice(0, 3)) { const result = await executeChild(processItemWorkflow, { args: [itemId], workflowId: `process-${itemId}`, }); results.push(result); } // Parallel execution with startChild const handles = await Promise.all( itemIds.slice(3).map((itemId) => startChild(processItemWorkflow, { args: [itemId], workflowId: `process-${itemId}`, // Child continues if parent is terminated parentClosePolicy: ParentClosePolicy.ABANDON, }) ) ); // Wait for all parallel children to complete const parallelResults = await Promise.all(handles.map((h) => h.result())); results.push(...parallelResults); return results; } ``` --- ## Continue-As-New For long-running Workflows that accumulate history, use Continue-As-New to start a fresh execution with the same Workflow ID. ```typescript import { continueAsNew, workflowInfo, sleep } from '@temporalio/workflow'; export async function longRunningWorkflow(iteration: number = 0): Promise { const info = workflowInfo(); // Check if history is getting too large if (info.historyLength > 10000 || info.continueAsNewSuggested) { // Continue as new with current state await continueAsNew(iteration); } // Do some work console.log(`Iteration ${iteration}`); await sleep('1 minute'); // Continue with next iteration await continueAsNew(iteration + 1); } ``` --- ## Schedules Schedules allow you to run Workflows on a recurring basis with cron-like specifications. ```typescript import { Client } from '@temporalio/client'; async function createSchedule(client: Client) { // Create a schedule that runs a workflow every hour const handle = await client.schedule.create({ scheduleId: 'hourly-report-schedule', spec: { // Run at minute 0 of every hour calendars: [{ hour: { start: 0, end: 23 }, minute: 0 }], // Or use cron syntax // cronExpressions: ['0 * * * *'], }, action: { type: 'startWorkflow', workflowType: 'generateReportWorkflow', taskQueue: 'reports', args: ['daily-metrics'], }, policies: { overlap: 'SKIP', // Skip if previous run still executing catchupWindow: '1 hour', }, }); console.log(`Created schedule: ${handle.scheduleId}`); // Describe the schedule const description = await handle.describe(); console.log('Next run:', description.info.nextActionTimes[0]); // Trigger manually await handle.trigger(); // Pause/unpause await handle.pause('Maintenance window'); await handle.unpause(); // Update schedule await handle.update((schedule) => ({ ...schedule, spec: { calendars: [{ hour: 9, minute: 0 }], // Change to 9 AM daily }, })); // List all schedules for await (const schedule of client.schedule.list()) { console.log(`Schedule: ${schedule.scheduleId}, paused: ${schedule.state.paused}`); } // Delete when done await handle.delete(); } ``` --- ## Testing Workflows The testing package provides utilities for unit testing Workflows with time manipulation and Activity mocking. ```typescript import { TestWorkflowEnvironment } from '@temporalio/testing'; import { Worker } from '@temporalio/worker'; import { myWorkflow } from './workflows'; describe('Workflow Tests', () => { let testEnv: TestWorkflowEnvironment; beforeAll(async () => { // Create test environment with time-skipping server testEnv = await TestWorkflowEnvironment.createTimeSkipping(); }); afterAll(async () => { await testEnv.teardown(); }); it('should complete workflow', async () => { const { client, nativeConnection } = testEnv; // Create worker with mocked activities const worker = await Worker.create({ connection: nativeConnection, taskQueue: 'test-queue', workflowsPath: require.resolve('./workflows'), activities: { // Mock activities for testing sendEmail: async () => { /* mock */ }, processPayment: async () => true, }, }); // Run worker and workflow together const result = await worker.runUntil( client.workflow.execute(myWorkflow, { taskQueue: 'test-queue', workflowId: 'test-workflow-1', args: ['test-order', ['item1']], }) ); expect(result).toContain('completed'); }); it('should handle time-based logic', async () => { const { client } = testEnv; const handle = await client.workflow.start(myWorkflow, { taskQueue: 'test-queue', workflowId: 'test-workflow-2', args: ['order-1', []], }); // Skip time forward await testEnv.sleep('1 hour'); const result = await handle.result(); expect(result).toBeDefined(); }); }); ``` --- ## Error Handling and Retries Configure retry policies for Activities and handle failures appropriately in Workflows. ```typescript import { proxyActivities, ApplicationFailure } from '@temporalio/workflow'; import type * as activities from './activities'; const { riskyActivity } = proxyActivities({ startToCloseTimeout: '30 seconds', retry: { initialInterval: '1 second', backoffCoefficient: 2, maximumInterval: '1 minute', maximumAttempts: 5, nonRetryableErrorTypes: ['InvalidInputError', 'AuthenticationError'], }, }); export async function workflowWithErrorHandling(input: string): Promise { try { return await riskyActivity(input); } catch (error) { if (error instanceof ApplicationFailure) { if (error.type === 'InvalidInputError') { // Handle specific error type return 'Invalid input provided'; } } // Re-throw unknown errors throw error; } } // In activity file - throwing retryable vs non-retryable errors import { ApplicationFailure } from '@temporalio/activity'; export async function riskyActivity(input: string): Promise { if (!input) { // Non-retryable error throw ApplicationFailure.create({ type: 'InvalidInputError', message: 'Input cannot be empty', nonRetryable: true, }); } if (Math.random() < 0.3) { // Retryable error (default) throw ApplicationFailure.create({ type: 'TransientError', message: 'Temporary failure, please retry', }); } return `Processed: ${input}`; } ``` --- ## Workflow Versioning with Patching Use patching to safely update Workflow code while maintaining compatibility with running executions. ```typescript import { patched, deprecatePatch } from '@temporalio/workflow'; export async function myWorkflow(input: string): Promise { // Phase 1: Add new code path with patch if (patched('my-patch-v2')) { // New code path return await newImplementation(input); } else { // Old code path for existing executions return await oldImplementation(input); } } // Phase 2: After all old executions complete, deprecate the patch export async function myWorkflowV2(input: string): Promise { deprecatePatch('my-patch-v2'); // Only new implementation return await newImplementation(input); } // Phase 3: Remove patch entirely when safe export async function myWorkflowV3(input: string): Promise { return await newImplementation(input); } ``` --- ## Search Attributes and Visibility Add searchable metadata to Workflows for filtering and querying. ```typescript import { upsertSearchAttributes, workflowInfo, defineSearchAttributeKey } from '@temporalio/workflow'; import { Client } from '@temporalio/client'; // Define typed search attribute keys const orderStatusKey = defineSearchAttributeKey('OrderStatus'); const orderAmountKey = defineSearchAttributeKey('OrderAmount'); export async function orderWorkflow(orderId: string, amount: number): Promise { // Set initial search attributes upsertSearchAttributes([ { key: orderStatusKey, value: 'pending' }, { key: orderAmountKey, value: amount }, ]); // Process order... // Update status upsertSearchAttributes([ { key: orderStatusKey, value: 'completed' }, ]); } // Query workflows by search attributes async function queryWorkflows(client: Client) { // List workflows with specific status for await (const workflow of client.workflow.list({ query: 'OrderStatus = "pending" AND OrderAmount > 100', })) { console.log(`Pending order: ${workflow.workflowId}`); } // Count workflows const count = await client.workflow.count('OrderStatus = "completed"'); console.log(`Completed orders: ${count.count}`); } ``` --- The Temporal TypeScript SDK enables building resilient distributed applications by handling the complexity of state management, retries, and failure recovery automatically. Workflows define business logic as deterministic code, while Activities perform actual work with built-in retry capabilities. The separation allows for clean, testable code that survives process restarts, infrastructure failures, and long-running operations. Common integration patterns include: using Signals for external event handling and workflow coordination, Queries for exposing workflow state without side effects, Updates for synchronous state mutations, Child Workflows for decomposing complex logic, Continue-As-New for infinite workflows, and Schedules for recurring tasks. The SDK's replay-based execution model ensures that workflows can be safely updated and will recover their exact state after any failure, making it ideal for critical business processes like order processing, payment handling, and multi-step orchestrations.