new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Temporal · Concepts · all subjects

design-patterns/typescript

57 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

TypeScript circuit breaker example with dependency injection

// activities.ts — opossum import CircuitBreaker from 'opossum'; export const createActivities = (breaker: CircuitBreaker) => ({ async chargeCustomer(orderId: string, amount: number): Promise<string> { // Rejects with an EOPENBREAKER error when the breaker is open. return breaker.fire(orderId, amount) as Promise<string>; }, }); // worker.ts import { Worker } from '@temporalio/worker'; import CircuitBreaker from 'opossum'; import { createActivities } from './activities'; import { PaymentAPI } from './payment-api'; async function run() { // Construct the breaker once at Worker startup so its failure // counters are shared across all Activity executions. const paymentApi = new PaymentAPI('https://api.example.com'); const breaker = new CircuitBreaker( (orderId: string, amount: number) => paymentApi.charge(orderId, amount), { errorThresholdPercentage: 50, resetTimeout: 30000 }, // cool-down in ms ); const worker = await Worker.create({ taskQueue: 'payment', workflowsPath: require.resolve('./workflows'), activities: createActivities(breaker), }); await worker.run(); } run().catch((err) => { console.error(err); process.exit(1); });

TypeScript Activity Dependency Injection implementation

In TypeScript, use a factory function that closes over dependencies and returns an object of Activity functions. Call proxyActivities with ReturnType<typeof createActivities> to infer the Activity types from the factory function's return type. Register the result of calling the factory function with real dependencies as the activities option when creating the Worker.

Pitfall: Forgetting to bind methods in TypeScript

When using a class instead of a factory function, class methods must be defined as arrow functions or explicitly bound in the constructor. Otherwise, this is undefined when Temporal invokes the Activity.

TypeScript Activity Dependency Injection code example

// activities.ts export interface DB { processPayment(orderId: string, amount: number): Promise<string>; } export interface EmailClient { send(to: string, subject: string, body: string): Promise<void>; } export const createActivities = (db: DB, emailClient: EmailClient) => ({ async chargeCustomer(orderId: string, amount: number): Promise<string> { const receiptId = await db.processPayment(orderId, amount); return receiptId; }, async sendReceipt(email: string, receiptId: string): Promise<void> { await emailClient.send(email, 'Payment Receipt', receiptId); }, }); // workflows.ts import { proxyActivities } from '@temporalio/workflow'; import type { createActivities } from './activities'; // Use ReturnType to extract the Activity types from the factory function const { chargeCustomer, sendReceipt } = proxyActivities< ReturnType<typeof createActivities> >({ startToCloseTimeout: '30s', }); export async function paymentWorkflow( orderId: string, amount: number, email: string ): Promise<void> { const receiptId = await chargeCustomer(orderId, amount); await sendReceipt(email, receiptId); } // worker.ts import { Worker } from '@temporalio/worker'; import { createActivities } from './activities'; async function run() { // Initialize dependencies at Worker startup const db = new PostgresClient('postgres://localhost:5432/payments'); const emailClient = new SMTPClient('smtp://mail.example.com'); const worker = await Worker.create({ taskQueue: 'payment', workflowsPath: require.resolve('./workflows'), // Inject dependencies through the factory function activities: createActivities(db, emailClient), }); await worker.run(); } run().catch((err) => { console.error(err); process.exit(1); });

Batch Iterator TypeScript implementation

import { continueAsNew, log, proxyActivities } from "@temporalio/workflow"; import type * as activities from "./activities"; import { PAGE_SIZE } from "./shared"; const { fetchPage, processRecord } = proxyActivities<typeof activities>({ startToCloseTimeout: "10 seconds", }); export async function batchIteratorWorkflow( offset: number = 0, totalProcessed: number = 0 ): Promise<number> { const page = await fetchPage(offset, PAGE_SIZE); for (const record of page) { await processRecord(record); totalProcessed++; } log.info(`Processed page at offset ${offset} (${page.length} records, running total: ${totalProcessed})`); if (page.length === PAGE_SIZE) { await continueAsNew<typeof batchIteratorWorkflow>(offset + PAGE_SIZE, totalProcessed); } return totalProcessed; }

Asynchronous child workflow execution in TypeScript

To start a child workflow asynchronously in TypeScript, use startChild(childWorkflow, { args: [input], parentClosePolicy: ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON }). This returns a handle once the child has started without waiting for completion.

Parallel child workflows in TypeScript

To start multiple child workflows in parallel in TypeScript, use Promise.all() with an array of executeChild() calls. This starts all children concurrently and awaits all results.

Synchronous child workflow execution in TypeScript

To execute a child workflow synchronously in TypeScript, use executeChild(childWorkflow, { args: [input] }). This starts the child and awaits its completion.

TypeScript Continue-As-New implementation

In TypeScript, call `await continueAsNew<typeof dataProcessorWorkflow>(cursor, totalProcessed)` to trigger a Continue-As-New transition. The `continueAsNew()` function throws a special error that the runtime intercepts. Use `workflowInfo().continueAsNewSuggested` to check if history is approaching the limit.

TypeScript: Workflow configuration with retry options for Delayed Retry pattern

Example showing how to configure a Workflow with retry options when using the Delayed Retry pattern. The nextRetryDelay set in the Activity overrides the interval only for the retry following that specific failure: ```typescript // workflows.ts import * as wf from '@temporalio/workflow'; import type * as activities from './activities'; const { callApi } = wf.proxyActivities<typeof activities>({ startToCloseTimeout: '10s', retry: { initialInterval: '1s', backoffCoefficient: 2, maximumAttempts: 10, }, }); export async function apiWorkflow(endpoint: string): Promise<string> { return await callApi(endpoint); } ```

TypeScript: ApplicationFailure with nextRetryDelay from HTTP 429 response

Example showing how to extract the Retry-After header from an HTTP 429 response and pass it to ApplicationFailure.create() to override the retry delay: ```typescript // activities.ts import { ApplicationFailure } from '@temporalio/activity'; export async function callApi(endpoint: string): Promise<string> { const response = await fetch(endpoint); if (response.status === 429) { const retryAfterHeader = response.headers.get('Retry-After'); const retryAfterSeconds = retryAfterHeader != null ? parseInt(retryAfterHeader, 10) : undefined; throw ApplicationFailure.create({ message: retryAfterSeconds != null ? `Rate limited — retrying after ${retryAfterSeconds}s` : 'Rate limited — retrying per RetryPolicy', type: 'RateLimitError', // Only override the interval when the header is present; fall back to RetryPolicy otherwise nextRetryDelay: retryAfterSeconds != null ? `${retryAfterSeconds}s` : undefined, }); } return response.text(); } ```

TypeScript: Attempt-proportional delay using nextRetryDelay

Example showing how to set nextRetryDelay dynamically based on the attempt number to implement a custom backoff: ```typescript // activities.ts import { ApplicationFailure, activityInfo } from '@temporalio/activity'; export async function process(input: string): Promise<string> { const { attempt } = activityInfo(); try { return await downstreamService.call(input); } catch (e) { // Custom delay: 3 seconds × attempt number (3s, 6s, 9s, …) throw ApplicationFailure.create({ message: `Service unavailable on attempt ${attempt}`, type: 'ServiceUnavailable', cause: e as Error, nextRetryDelay: `${3 * attempt}s`, }); } } ```

Delayed Start TypeScript API

In TypeScript, use `client.workflow.start()` with an options object containing a `startDelay` field as a string: ```typescript const handle = await client.workflow.start(delayedStartWorkflow, { workflowId: WORKFLOW_ID, taskQueue: TASK_QUEUE, startDelay: '30 seconds', }); ```

Early Return TypeScript implementation example

Example showing Early Return pattern in TypeScript using Update-with-Start: ```typescript // workflow.ts import { defineUpdate, setHandler, condition } from '@temporalio/workflow'; import * as activities from './activities'; const { initTransaction, completeTransaction, cancelTransaction } = proxyLocalActivities<typeof activities>({ scheduleToCloseTimeout: '5s', }); export const returnInitResultUpdate = defineUpdate<Transaction>('returnInitResult'); export async function transactionWorkflow(txRequest: TransactionRequest): Promise<Transaction> { let tx: Transaction | undefined; let initDone = false; let initError: Error | undefined; // Register update handler that waits for initialization setHandler(returnInitResultUpdate, async () => { await condition(() => initDone); if (initError) { throw initError; } return tx!; }); // Phase 1: Fast synchronous initialization (local activity) try { tx = await initTransaction(txRequest); } catch (err) { initError = err as Error; } finally { initDone = true; // Signal update handler } // Phase 2: Slow asynchronous completion if (initError) { await cancelTransaction(tx!); throw initError; } await completeTransaction(tx); return tx; } // client.ts const startWorkflowOperation = new WithStartWorkflowOperation( transactionWorkflow, { workflowId: 'transaction-123', args: [txRequest], taskQueue: 'transactions', workflowIdConflictPolicy: 'FAIL', }, ); const tx = await client.workflow.executeUpdateWithStart( returnInitResultUpdate, { startWorkflowOperation }, ); const wfHandle = await startWorkflowOperation.workflowHandle(); // Use transaction ID immediately while workflow continues console.log(`Transaction initialized: ${tx.id}`); // Optionally wait for the workflow to complete const finalResult = await wfHandle.result(); ```

Entity Workflow TypeScript implementation

import { condition, allHandlersFinished, defineUpdate, defineSignal, defineQuery, setHandler, continueAsNew, workflowInfo, proxyActivities } from '@temporalio/workflow'; import type * as activities from './activities'; const { validateProfile } = proxyActivities<typeof activities>({ startToCloseTimeout: '30s', }); interface UserState { status: string; profile?: ProfileData; pendingEmail?: string; createdAt: number; updatedAt: number; } interface UserAccountInput { userId: string; state?: UserState; } export const updateProfileUpdate = defineUpdate<void, [ProfileData]>('updateProfile'); export const suspendSignal = defineSignal('suspend'); export const deleteSignal = defineSignal('delete'); export const getStateQuery = defineQuery<UserState>('getState'); export async function userAccountWorkflow(input: UserAccountInput): Promise<void> { const state: UserState = input.state ?? { status: 'ACTIVE', createdAt: Date.now(), updatedAt: Date.now(), }; let deleted = false; let operationCount = 0; setHandler(updateProfileUpdate, async (data: ProfileData) => { if (deleted) { throw new Error('User account is deleted'); } await validateProfile(data); state.profile = data; state.updatedAt = Date.now(); operationCount++; }); setHandler(suspendSignal, () => { if (!deleted && state.status !== 'SUSPENDED') { state.status = 'SUSPENDED'; state.updatedAt = Date.now(); operationCount++; } }); setHandler(deleteSignal, () => { deleted = true; }); setHandler(getStateQuery, () => state); await condition(() => deleted || workflowInfo().continueAsNewSuggested); if (!deleted && workflowInfo().continueAsNewSuggested) { await condition(allHandlersFinished); await continueAsNew<typeof userAccountWorkflow>({ userId: input.userId, state }); } state.status = 'DELETED'; }

Fan-Out TypeScript implementation

```typescript // workflows.ts import { executeChild, proxyActivities, workflowInfo, } from "@temporalio/workflow"; import type * as activities from "./activities"; import { TASK_QUEUE, CHUNK_SIZE } from "./shared"; const { processRecord } = proxyActivities<typeof activities>({ startToCloseTimeout: "10 seconds", }); export async function fanOutWorkflow( totalRecords: number, chunkSize: number = CHUNK_SIZE ): Promise<number> { const children: Promise<number>[] = []; for (let offset = 0; offset < totalRecords; offset += chunkSize) { const length = Math.min(chunkSize, totalRecords - offset); children.push( executeChild(recordBatchWorkflow, { args: [offset, length], taskQueue: TASK_QUEUE, workflowId: `${workflowInfo().workflowId}/batch-${offset}`, }) ); } const results = await Promise.all(children); return results.reduce((sum, n) => sum + n, 0); } export async function recordBatchWorkflow( offset: number, length: number ): Promise<number> { let processed = 0; for (let i = offset; i < offset + length; i++) { await processRecord(i); processed++; } return processed; } ```

Fast/Slow Retries TypeScript example

```typescript import * as wf from '@temporalio/workflow'; import type * as activities from './activities'; const fastDownstream = wf.proxyActivities<typeof activities>({ startToCloseTimeout: '30s', retry: { initialInterval: '1s', backoffCoefficient: 1.5, maximumInterval: '30s', maximumAttempts: 10, }, }); const slowDownstream = wf.proxyActivities<typeof activities>({ startToCloseTimeout: '30s', retry: { initialInterval: '5m', backoffCoefficient: 1, // maximumAttempts defaults to unlimited }, }); export async function fastSlowRetryWorkflow(request: string): Promise<string> { // Phase 1: fast retries try { return await fastDownstream.callDownstream(request); } catch { wf.log.warn('Fast retries exhausted — switching to slow retry phase', { request }); // Phase 2: slow retries return await slowDownstream.callDownstream(request); } } ``` This example shows a Workflow implementing fast retries with 1-second initial interval and 10 max attempts, transitioning to slow retries with 5-minute interval and unlimited attempts when fast phase is exhausted.

TypeScript Fixed Count Retries example

import * as wf from '@temporalio/workflow'; import type * as activities from './activities'; const { chargePaymentApi } = wf.proxyActivities<typeof activities>({ startToCloseTimeout: '10s', retry: { maximumAttempts: 3 }, }); export async function paymentWorkflow(orderId: string): Promise<string> { try { return await chargePaymentApi(orderId); } catch (err) { if (err instanceof wf.ActivityFailure && err.retryState === wf.RetryState.MAXIMUM_ATTEMPTS_REACHED) { wf.log.error('Payment failed: all 3 attempts exhausted', { orderId }); } throw err; } } This example shows how to set maximumAttempts=3 on the retry policy for a TypeScript workflow and catch ActivityFailure when retries are exhausted.

Short SLA without per-attempt timeout TypeScript

For a 30-second authorization window, omit StartToCloseTimeout and let ScheduleToCloseTimeout act as the only bound: ```typescript const { authorizeTransaction } = wf.proxyActivities<typeof activities>({ scheduleToCloseTimeout: '30s', retry: { initialInterval: '3s', backoffCoefficient: 1.5, }, }); ```

Fixed Wall-Time Retries TypeScript implementation

Example of enforcing a 2-minute SLA with per-attempt 30-second timeout: ```typescript import * as wf from '@temporalio/workflow'; import type * as activities from './activities'; const { authorizeTransaction } = wf.proxyActivities<typeof activities>({ scheduleToCloseTimeout: '2m', // total budget startToCloseTimeout: '30s', // per attempt retry: { initialInterval: '5s', backoffCoefficient: 1.5, maximumInterval: '30s', }, }); export async function paymentAuthWorkflow(transactionId: string): Promise<string> { try { return await authorizeTransaction(transactionId); } catch (err) { if (err instanceof wf.ActivityFailure) { const cause = err.cause; if (cause instanceof wf.TimeoutFailure && cause.type === wf.TimeoutType.SCHEDULE_TO_CLOSE) { wf.log.error('Authorization failed — 2-minute SLA breached', { transactionId }); } } throw err; } } ```

Local Activity TypeScript example

import { proxyLocalActivities } from "@temporalio/workflow"; import type * as activities from "./activities"; const { validateTransaction, reserveFunds, settleTransaction } = proxyLocalActivities<typeof activities>({ scheduleToCloseTimeout: "10s" }); export async function transactionWorkflow( req: TransactionRequest, ): Promise<Transaction> { // All three activities run in-process — no server round-trips. let tx = await validateTransaction(req); tx = await reserveFunds(tx); return settleTransaction(tx); }

Local Activity TypeScript API

In TypeScript, use `proxyLocalActivities()` to create a proxy for in-process Activity execution. Pass a `scheduleToCloseTimeout` option (format: "10s") to the proxy.

TypeScript Activity Heartbeat - basic progress tracking

Example in TypeScript showing file processing with heartbeating every 100 lines. Uses activityInfo().heartbeatDetails to retrieve the last checkpoint (defaulting to 0 if none), and heartbeat(currentLine) to store progress.

TypeScript Activity Heartbeat - cancellation handling

In TypeScript, cancellation is delivered as a CancelledFailure thrown from sleep() or Context.current().cancelled. The Activity should catch CancelledFailure, perform cleanup via cleanupResources(), and re-throw the error.

TypeScript retry policy nonRetryableErrorTypes field

In TypeScript, pass a retry object to wf.proxyActivities() with nonRetryableErrorTypes set to an array of error type name strings. Example: retry: {nonRetryableErrorTypes: ['OrderNotFoundError', 'ValidationError']}

TypeScript ApplicationFailure non-retryable method

In TypeScript, use @temporalio/activity.ApplicationFailure.nonRetryable() to mark an error as non-retryable at the throw site. Pass the error message and type name as parameters. Example: throw ApplicationFailure.nonRetryable(`Order ${orderId} not found`, 'OrderNotFoundError')

Catching and handling ActivityError in TypeScript workflows

In TypeScript, catch wf.ActivityFailure in a try-catch block. Check if err.cause instanceof wf.ApplicationFailure. Inspect the type and message properties to route to the appropriate compensation or escalation path.

TypeScript Eager Workflow Start limitation

Eager Workflow Start is not available in the TypeScript SDK, but the latency gap is small at ~30–50 ms per Workflow start. Local Activities and Early Return + Local Activities are fully supported in TypeScript and achieve ~275 ms total latency and ~160 ms first-response latency respectively.

Pattern selection for TypeScript implementations

For TypeScript SDK implementations, use Local Activities and Early Return + Local Activities. Eager Workflow Start is not available in the TypeScript SDK.

Periodic sequence polling example in TypeScript

```typescript // workflows.ts import { proxyActivities, sleep, continueAsNew } from '@temporalio/workflow'; import type * as activities from './activities'; const { doPoll } = proxyActivities<typeof activities>({ startToCloseTimeout: '10s', }); export async function pollingChildWorkflow( pollingIntervalSeconds: number ): Promise<string> { const maxAttempts = 10; for (let i = 0; i < maxAttempts; i++) { const result = await doPoll(); if (result === 'COMPLETED') { return result; } await sleep(`${pollingIntervalSeconds}s`); } // Continue-as-new to prevent unbounded history await continueAsNew<typeof pollingChildWorkflow>(pollingIntervalSeconds); return ''; // unreachable } export async function periodicPollingWorkflow(): Promise<string> { return await executeChild(pollingChildWorkflow, { args: [5], workflowId: 'ChildWorkflowPoll', }); } ``` This example shows a Child Workflow that polls up to 10 times with a configurable interval between attempts, then calls Continue-As-New to start a fresh execution. The parent Workflow executes the Child Workflow with a specific workflow ID.

Frequent polling example in TypeScript

```typescript // activities.ts import { heartbeat, sleep } from '@temporalio/activity'; export async function doPoll(): Promise<string> { while (true) { heartbeat(); const result = await externalService.checkStatus(); if (result === 'COMPLETED') { return result; } await sleep('1s'); } } // workflows.ts import { proxyActivities } from '@temporalio/workflow'; import type * as activities from './activities'; const { doPoll } = proxyActivities<typeof activities>({ startToCloseTimeout: '60s', heartbeatTimeout: '2s', }); export async function frequentPollingWorkflow(): Promise<string> { return await doPoll(); } ``` This example shows a frequent polling Activity that loops indefinitely with heartbeats every iteration, and a Workflow that executes the Activity with a 60-second start-to-close timeout and 2-second heartbeat timeout.

Infrequent polling example in TypeScript

```typescript // activities.ts import { ApplicationFailure } from '@temporalio/activity'; export async function doPoll(): Promise<string> { const result = await externalService.checkStatus(); if (result !== 'COMPLETED') { throw ApplicationFailure.retryable('Service not ready, will retry'); } return result; } // workflows.ts import { proxyActivities } from '@temporalio/workflow'; import type * as activities from './activities'; const { doPoll } = proxyActivities<typeof activities>({ startToCloseTimeout: '2s', retry: { backoffCoefficient: 1, initialInterval: '60s', }, }); export async function infrequentPollingWorkflow(): Promise<string> { return await doPoll(); } ``` This example shows an infrequent polling Activity that performs a single poll and throws a retryable error if the service is not ready, and a Workflow that executes the Activity with a fixed 60-second retry interval (backoff_coefficient=1).

TypeScript task assignment workflow with Updates

```typescript // workflow.ts import * as wf from '@temporalio/workflow'; interface AssignmentResult { assignmentId: string; taskName: string; totalTasks: number; } export const assignTaskUpdate = wf.defineUpdate<AssignmentResult, [string]>('assignTask'); export const getTasksQuery = wf.defineQuery<string[]>('getTasks'); const MAX_TASKS = 10; export async function taskWorkflow(): Promise<void> { const tasks: string[] = []; wf.setHandler( assignTaskUpdate, (taskName: string): AssignmentResult => { const assignmentId = wf.uuid4(); tasks.push(taskName); return { assignmentId, taskName, totalTasks: tasks.length }; }, { validator: (taskName: string): void => { if (tasks.length >= MAX_TASKS) { throw new Error('Task limit reached'); } }, } ); wf.setHandler(getTasksQuery, (): string[] => tasks); await wf.condition(() => false); } ```

Resumable Activity TypeScript implementation

TypeScript Resumable Activity Workflow implementation: ```typescript import * as wf from '@temporalio/workflow'; import type * as activities from './activities'; export interface TransferInput { fromAccount: string; toAccount: string; amount: number; } export const retryWithCorrectionSignal = wf.defineSignal<[string]>('retryWithCorrection'); export const approveSignal = wf.defineSignal<[boolean]>('approve'); export const getStatusQuery = wf.defineQuery<string>('getStatus'); const { executeTransfer } = wf.proxyActivities<typeof activities>({ startToCloseTimeout: '30s', retry: { maximumAttempts: 3 }, }); export async function transferWorkflow(input: TransferInput): Promise<string> { let status = 'PENDING'; let correctedAccount: string | undefined; let approval: boolean | undefined; wf.setHandler(retryWithCorrectionSignal, (account: string) => { correctedAccount = account; }); wf.setHandler(approveSignal, (decision: boolean) => { approval = decision; }); wf.setHandler(getStatusQuery, () => status); let account = input.toAccount; let correctionCount = 0; while (true) { status = 'TRANSFERRING'; try { await executeTransfer({ ...input, toAccount: account }); break; } catch (err) { correctionCount++; if (correctionCount > 5) { status = 'FAILED'; wf.upsertSearchAttributes({ TransferStatus: [status] }); throw err; } status = 'AWAITING_CORRECTION'; wf.upsertSearchAttributes({ TransferStatus: [status] }); wf.log.warn('Transfer failed — waiting for account correction', { account }); await wf.condition(() => correctedAccount !== undefined); account = correctedAccount!; correctedAccount = undefined; } } status = 'AWAITING_APPROVAL'; await wf.condition(() => approval !== undefined); if (approval) { status = 'COMPLETED'; return `Transfer of ${input.amount} to ${account} completed`; } status = 'REJECTED'; return 'Transfer rejected by client'; } ```

Resumable Activity: TypeScript activity with non-retryable error

TypeScript Activity implementation that distinguishes permanent from transient failures: ```typescript import { ApplicationFailure } from '@temporalio/activity'; import type { TransferInput } from './workflows'; export async function executeTransfer(transfer: TransferInput): Promise<string> { // Non-retryable: bad account number requires a human correction, not a retry. const accountExists = await accountService.exists(transfer.toAccount); if (!accountExists) { throw ApplicationFailure.nonRetryable( `Account ${transfer.toAccount} not found`, 'AccountNotFoundError', ); } // Other exceptions propagate as retryable so the RetryPolicy handles them. return paymentService.transfer(transfer.fromAccount, transfer.toAccount, transfer.amount); } ```

Signal with Start TypeScript implementation

In TypeScript, use client.workflow.signalWithStart() with parameters: the workflow function, and an options object containing workflowId, taskQueue, signal (the signal name as a string), and signalArgs (array of signal arguments). Example: client.workflow.signalWithStart(shoppingCartWorkflow, { workflowId: `cart-${cartId}`, taskQueue: 'carts', signal: 'addItem', signalArgs: [itemId, productId, quantity] }).

Saga pattern pitfall: TypeScript ContinueAsNew exception handling

In TypeScript, continueAsNew works by throwing a special exception. A catch block that does not re-throw it, or a finally block that returns a value, silently prevents Continue-As-New.

TypeScript Saga pattern implementation

type Compensation = () => Promise<void>; export async function openAccount(req: OpenAccountRequest): Promise<string> { const compensations: Compensation[] = []; try { await acts.createAccount(req); compensations.unshift(() => acts.clearPostalAddresses(req)); await acts.addAddress(req); compensations.unshift(() => acts.removeClient(req)); await acts.addClient(req); compensations.unshift(() => acts.disconnectBankAccounts(req)); await acts.addBankAccount(req); return `Account ${req.accountId} opened`; } catch (err) { for (const compensate of compensations) { await compensate(); } throw err; } } This example shows how to implement the Saga pattern in TypeScript using an array with unshift() to maintain LIFO order and manual iteration on error.

Sliding Window TypeScript implementation example

Example TypeScript implementation of Sliding Window pattern: ```typescript import { ApplicationFailure, ParentClosePolicy, condition, continueAsNew, defineSignal, getExternalWorkflowHandle, proxyActivities, setHandler, startChild, workflowInfo, } from "@temporalio/workflow"; const { processRecord } = proxyActivities<typeof activities>({ startToCloseTimeout: "30 seconds", }); export const completionSignal = defineSignal<[string]>(COMPLETION_SIGNAL); // Child Workflow: processes one record and signals the parent on completion. export async function recordProcessorWorkflow(recordId: string): Promise<void> { await processRecord(recordId); try { const parent = getExternalWorkflowHandle(workflowInfo().parent!.workflowId); await parent.signal(completionSignal, recordId); } catch (err) { if (!(err instanceof ApplicationFailure && err.type === "NOT_FOUND")) throw err; } } // Parent Workflow: maintains a fixed window of concurrent Child Workflows. export async function slidingWindowWorkflow(input: SlidingWindowInput): Promise<number> { const { recordIds, windowSize = WINDOW_SIZE, startIndex = 0 } = input; const parentId = workflowInfo().workflowId; let totalProcessed = input.totalProcessed ?? 0; let dispatched = 0; let active = input.active ?? 0; setHandler(completionSignal, () => { active--; totalProcessed++; }); let nextIndex = startIndex; while (nextIndex < recordIds.length) { await condition(() => active < windowSize); await startChild(recordProcessorWorkflow, { args: [recordIds[nextIndex]], workflowId: `${parentId}/record-${recordIds[nextIndex]}`, taskQueue: TASK_QUEUE, parentClosePolicy: ParentClosePolicy.ABANDON, }); nextIndex++; dispatched++; active++; if (dispatched >= windowSize) { await continueAsNew<typeof slidingWindowWorkflow>({ recordIds, windowSize, startIndex: nextIndex, totalProcessed, active }); } } await condition(() => active === 0); return totalProcessed; } ```

Updatable Timer TypeScript implementation

```typescript // updatable-timer.ts import * as wf from '@temporalio/workflow'; export class UpdatableTimer implements PromiseLike<void> { deadlineUpdated = false; #deadline: number; constructor(deadline: number) { this.#deadline = deadline; } private async run(): Promise<void> { while (true) { this.deadlineUpdated = false; if ( !(await wf.condition( () => this.deadlineUpdated, this.#deadline - Date.now(), )) ) { break; // Timer expired } // Timer was updated, loop to recalculate } } then<TResult1 = void, TResult2 = never>( onfulfilled?: (value: void) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>, ): PromiseLike<TResult1 | TResult2> { return this.run().then(onfulfilled, onrejected); } set deadline(value: number) { this.#deadline = value; this.deadlineUpdated = true; } get deadline(): number { return this.#deadline; } } ```

TypeScript Worker-Specific Task Queues example workflow

```typescript // workflows.ts import { proxyActivities } from '@temporalio/workflow'; import type * as activities from './activities'; const { download } = proxyActivities<typeof activities>({ startToCloseTimeout: '20s', }); export async function fileProcessingWorkflow( source: string, destination: string, ): Promise<void> { const downloaded = await download(source); const hostSpecificActivities = proxyActivities<typeof activities>({ taskQueue: downloaded.hostTaskQueue, scheduleToStartTimeout: '10s', startToCloseTimeout: '20s', }); const processed = await hostSpecificActivities.process(downloaded.fileName); await hostSpecificActivities.upload(processed, destination); } ``` This example shows a workflow that downloads a file on any worker, then processes and uploads it on the same host-specific worker.

TypeScript Worker-Specific Task Queues example activity

```typescript // activities.ts export interface TaskQueueFileNamePair { hostTaskQueue: string; fileName: string; } let hostSpecificTaskQueue: string; export function initActivities(taskQueue: string) { hostSpecificTaskQueue = taskQueue; } function getHostSpecificTaskQueue(): string { return hostSpecificTaskQueue; } export async function download(source: string): Promise<TaskQueueFileNamePair> { const localFile = await downloadToLocalDisk(source); return { hostTaskQueue: getHostSpecificTaskQueue(), fileName: localFile, }; } export async function process(fileName: string): Promise<string> { return await processLocalFile(fileName); } export async function upload(fileName: string, destination: string): Promise<void> { await uploadFromLocalDisk(fileName, destination); } ``` The host-specific Task Queue name is captured via closure when defining the activity functions. The download function returns both the file path and the host-specific queue name.

TypeScript Worker-Specific Task Queues example worker setup

```typescript // worker.ts import { Worker, NativeConnection } from '@temporalio/worker'; import * as activities from './activities'; import { v4 as uuid } from 'uuid'; import os from 'os'; async function run() { const defaultTaskQueue = 'FileProcessing'; const hostTaskQueue = `FileProcessing-${os.hostname()}-${uuid()}`; activities.initActivities(hostTaskQueue); const defaultWorker = await Worker.create({ workflowsPath: require.resolve('./workflows'), activities, taskQueue: defaultTaskQueue, }); const hostWorker = await Worker.create({ activities, taskQueue: hostTaskQueue, }); await Promise.all([defaultWorker.run(), hostWorker.run()]); } run().catch((err) => { console.error(err); process.exit(1); }); ``` Each Worker registers with both the default Task Queue and its own host-specific Task Queue. The host-specific queue name includes hostname and UUID for uniqueness.

Task Queue constant definition - TypeScript example

In TypeScript, define a Task Queue name constant: const TASK_QUEUE_NAME = 'my-taskqueue-name';. Export from a shared module and import in both the workflow client code (in client.workflow.start() taskQueue option) and worker configuration (in Worker.create() taskQueue option).

Workflow Definition syntax in TypeScript

A Workflow Definition in TypeScript is an async function that takes typed arguments and returns a Promise. Example: export async function WorkflowExample(args: BasicWorkflowArgs): Promise<{ result: string }> { ... }

TypeScript SDK does not support Eager Workflow Start

The TypeScript SDK does not currently support Eager Workflow Start. For latency-sensitive TypeScript workflows, use Local Activities or Early Return + Local Activities instead.

Early Return + Local Activities TypeScript example

```typescript // workflows.ts import { proxyLocalActivities, proxyActivities, defineUpdate, setHandler, condition } from "@temporalio/workflow"; import type * as activities from "./activities"; import type { TransactionRequest, Transaction } from "./shared"; // Phase 1: local activities — no server round-trips on the hot path. const { validateTransaction, initTransaction } = proxyLocalActivities<typeof activities>({ scheduleToCloseTimeout: "5s" }); // Phase 2: regular activities — background settlement. const { completeTransaction, cancelTransaction } = proxyActivities<typeof activities>({ startToCloseTimeout: "30s" }); export const getResultUpdate = defineUpdate<Transaction, [TransactionRequest]>("getResult"); export async function transactionWorkflow(req: TransactionRequest): Promise<void> { let tx: Transaction | undefined; let phase1Done = false; let phase1Error: unknown; setHandler(getResultUpdate, async () => { // The Update handler waits for Phase 1 before returning. await condition(() => phase1Done); if (phase1Error) throw phase1Error; return tx!; }); try { // Phase 1: Local Activities run in-process. tx = await validateTransaction(req); tx = await initTransaction(tx); } catch (err) { phase1Error = err; } finally { phase1Done = true; } if (phase1Error) { if (tx !== undefined) { await cancelTransaction(tx); } return; } // Phase 2: Regular Activity runs in the background. await completeTransaction(tx!); } ```

Event Accumulator TypeScript implementation

TypeScript implementation uses setHandler to register signal handlers and condition() with a string duration for the sliding window. continueAsNew<typeof accumulatorWorkflow>() carries accumulated state to the next run.

Event Accumulator TypeScript example workflow

export async function accumulatorWorkflow( bucketKey: string, accumulated: OrderItem[] = [], seenKeys: string[] = [], ): Promise<string> { const seenSet = new Set(seenKeys); const items: OrderItem[] = [...accumulated]; const unprocessed: OrderItem[] = []; let flushRequested = false; setHandler(addItemSignal, (item: OrderItem) => { unprocessed.push(item); }); setHandler(flushSignal, () => { flushRequested = true; }); do { // Sliding window: wait for a signal or let the inactivity timer fire const timedOut = !(await condition( () => unprocessed.length > 0 || flushRequested, "10 seconds", )); // Drain and deduplicate incoming signals while (unprocessed.length > 0) { const item = unprocessed.shift()!; if (item.orderId === bucketKey && !seenSet.has(item.itemId)) { seenSet.add(item.itemId); items.push(item); } } if (timedOut || flushRequested) { const result = await processItems(bucketKey, items); if (unprocessed.length === 0) return result; // More signals arrived after timeout/flush — loop to process them } } while (unprocessed.length > 0 || !workflowInfo().continueAsNewSuggested); // History growing large — continue as new, carrying accumulated state forward await continueAsNew<typeof accumulatorWorkflow>(bucketKey, items, [...seenSet]); return ""; // unreachable }

MapReduce Tree TypeScript implementation

TypeScript implementation using leafWorkflow and nodeWorkflow functions. The leafWorkflow calls processLeaf Activity and signals result back to parent using getExternalWorkflowHandle(parentWorkflowId).signal(). The nodeWorkflow checks record count against LEAF_THRESHOLD; if below threshold starts one leaf per record, otherwise splits into N chunks and starts N child node workflows recursively. Uses condition() to await all expected signals, then signals aggregated results up to parent if not root.

Pick First pattern implementation in TypeScript

In TypeScript, the Pick First pattern uses `Promise.race()` within a `CancellationScope.cancellable()` block. Activities are defined with `proxyActivities()`. Multiple Activities are started concurrently, and `Promise.race()` waits for the first to complete. After capturing the first result, `CancellationScope.current().cancel()` cancels all Activities started in that scope. To wait for cancellation cleanup, set `cancellationType: ActivityCancellationType.WAIT_CANCELLATION_COMPLETED` in Activity options and await all promises while catching cancellation errors with `isCancellation()`.

Retry Alerting via Metrics implementation in TypeScript

```typescript import { Context } from '@temporalio/activity'; const ALERT_THRESHOLD = 5; export async function callDownstreamService(endpoint: string): Promise<string> { const ctx = Context.current(); if (ctx.info.attempt > ALERT_THRESHOLD) { ctx.metricMeter .createCounter('high_activity_error_count') .add(1); } const response = await downstream.call(endpoint); return response.data; } ``` This example shows accessing the current attempt number via Context and using metricMeter to emit a counter.

Retry Alerting with dimension tags in TypeScript

```typescript if (ctx.info.attempt > ALERT_THRESHOLD) { ctx.metricMeter .createCounter('high_activity_error_count') .add(1, { activity_type: ctx.info.activityType, endpoint }); } ``` Add tags to the metric to identify which Activity type and endpoint are producing high attempt counts.

Parallel Execution in TypeScript

In TypeScript, Activity proxy functions return native Promise objects. Use Promise.all() to wait for all of them to complete.

TypeScript parallel Activities example

import { proxyActivities } from '@temporalio/workflow'; import type * as activities from './activities'; const { process } = proxyActivities<typeof activities>({ startToCloseTimeout: '30s', }); export async function processInParallel(items: string[]): Promise<string[]> { const promises = items.map((item) => process(item)); return await Promise.all(promises); } This example starts one Activity per item in a list and waits for all of them to complete.

TypeScript batch processing example

import { proxyActivities } from '@temporalio/workflow'; import type * as activities from './activities'; const { process } = proxyActivities<typeof activities>({ startToCloseTimeout: '30s', }); export async function processBatch( items: string[], maxParallel: number ): Promise<string[]> { const results: string[] = []; for (let i = 0; i < items.length; i += maxParallel) { const batch = items.slice(i, i + maxParallel); const batchResults = await Promise.all(batch.map((item) => process(item))); results.push(...batchResults); } return results; } This example implements controlled parallelism by processing items in batches.

Error handling in parallel execution - TypeScript

import { proxyActivities } from '@temporalio/workflow'; import type * as activities from './activities'; const { process } = proxyActivities<typeof activities>({ startToCloseTimeout: '30s', }); interface Result { item: string; output?: string; error?: string; } export async function processWithErrorHandling(items: string[]): Promise<Result[]> { const settled = await Promise.allSettled(items.map((item) => process(item))); return settled.map((outcome, i) => { if (outcome.status === 'fulfilled') { return { item: items[i], output: outcome.value }; } return { item: items[i], error: String(outcome.reason) }; }); } This example uses Promise.allSettled() so that individual failures do not prevent other Activities from completing.

Give your agent this brain