diff --git a/docs/cloud/get-started/api-keys.mdx b/docs/cloud/get-started/api-keys.mdx index a86b25ff99..caa323edf9 100644 --- a/docs/cloud/get-started/api-keys.mdx +++ b/docs/cloud/get-started/api-keys.mdx @@ -413,7 +413,7 @@ To use your API key with a Temporal SDK, see the instructions in each SDK sectio [How to connect to Temporal Cloud using an API Key with the Python SDK](/develop/python/temporal-client#connect-to-temporal-cloud) -[How to connect to Temporal Cloud using an API Key with the TypeScript SDK](/develop/typescript/temporal-client#connect-to-temporal-cloud) +[How to connect to Temporal Cloud using an API Key with the TypeScript SDK](/develop/typescript/client/temporal-client#connect-to-temporal-cloud) [How to connect to Temporal Cloud using an API Key with the .NET SDK](/develop/dotnet/temporal-client#connect-to-temporal-cloud) diff --git a/docs/cloud/get-started/certificates.mdx b/docs/cloud/get-started/certificates.mdx index dce0cb246a..a1485bf3e1 100644 --- a/docs/cloud/get-started/certificates.mdx +++ b/docs/cloud/get-started/certificates.mdx @@ -485,7 +485,7 @@ To view the current certificate filters, use the - [Java SDK](/develop/java/temporal-client#connect-to-temporal-cloud) - [PHP SDK](/develop/php/temporal-client#connect-to-a-dev-cluster) - [Python SDK](/develop/python/temporal-client#connect-to-temporal-cloud) -- [TypeScript SDK](/develop/typescript/temporal-client#connect-to-temporal-cloud) +- [TypeScript SDK](/develop/typescript/client/temporal-client#connect-to-temporal-cloud) - [.NET SDK](/develop/dotnet/temporal-client#connect-to-temporal-cloud) ### Configure Temporal CLI {#configure-temporal-cli} diff --git a/docs/cloud/get-started/index.mdx b/docs/cloud/get-started/index.mdx index 382983e2c3..d7c5b314bc 100644 --- a/docs/cloud/get-started/index.mdx +++ b/docs/cloud/get-started/index.mdx @@ -16,7 +16,7 @@ tags: - API --- -import { DiscoverableDisclosure } from "@site/src/components"; +import { DiscoverableDisclosure } from '@site/src/components'; Getting started with Temporal Cloud involves a few key steps: @@ -55,7 +55,7 @@ See our guides for connecting each SDK to your Temporal Cloud Namespace: - [Connect to Temporal Cloud in Go](/develop/go/temporal-client#connect-to-temporal-cloud) - [Connect to Temporal Cloud in Java](/develop/java/temporal-client#connect-to-temporal-cloud) - [Connect to Temporal Cloud in Python](/develop/python/temporal-client#connect-to-temporal-cloud) -- [Connect to Temporal Cloud in TypeScript](/develop/typescript/core-application#connect-to-temporal-cloud) +- [Connect to Temporal Cloud in TypeScript](/develop/typescript/client/temporal-client#connect-to-temporal-cloud) - [Connect to Temporal Cloud in .NET](/develop/dotnet/temporal-client#connect-to-temporal-cloud) - [Connect to Temporal Cloud in PHP](/develop/php/temporal-client#connect-to-temporal-cloud) - [Connect to Temporal Cloud in Ruby](/develop/ruby/temporal-client#connect-to-temporal-cloud) @@ -67,7 +67,7 @@ See our guides for starting a workflow using each SDK: - [Start a workflow in Go](/develop/go/temporal-client#start-workflow-execution) - [Start a workflow in Java](/develop/java/temporal-client#start-workflow-execution) - [Start a workflow in Python](/develop/python/temporal-client#start-workflow-execution) -- [Start a workflow in TypeScript](/develop/typescript/core-application#start-workflow-execution) +- [Start a workflow in TypeScript](/develop/typescript/client/temporal-client#start-workflow-execution) - [Start a workflow in .NET](/develop/dotnet/temporal-client#start-workflow) - [Start a workflow in PHP](/develop/php/temporal-client#start-workflow-execution) - [Start a workflow in Ruby](/develop/ruby/temporal-client#start-workflow) diff --git a/docs/cloud/metrics/prometheus-grafana.mdx b/docs/cloud/metrics/prometheus-grafana.mdx index 71ffda59da..f7bed1ba23 100644 --- a/docs/cloud/metrics/prometheus-grafana.mdx +++ b/docs/cloud/metrics/prometheus-grafana.mdx @@ -56,7 +56,7 @@ If you're following through with the examples provided here, ensure that you hav - [Java](/develop/java/temporal-client#connect-to-temporal-cloud) - [PHP](/develop/php/temporal-client#connect-to-a-dev-cluster) - [Python](/develop/python/temporal-client#connect-to-temporal-cloud) - - [TypeScript](/develop/typescript/core-application#connect-to-temporal-cloud) + - [TypeScript](/develop/typescript/client/temporal-client#connect-to-temporal-cloud) - [.NET](/develop/dotnet/temporal-client#connect-to-temporal-cloud) - Prometheus and Grafana installed. @@ -94,7 +94,7 @@ Each language development guide has details on how to set this up. - [Go SDK](/develop/go/observability#metrics) - [Java SDK](/develop/java/observability#metrics) -- [TypeScript SDK](/develop/typescript/observability#metrics) +- [TypeScript SDK](/develop/typescript/workers/observability#metrics) - [Python](/develop/python/observability#metrics) - [.NET](/develop/dotnet/observability#metrics) diff --git a/docs/develop/go/versioning.mdx b/docs/develop/go/versioning.mdx index 0535ab585c..7642edf63a 100644 --- a/docs/develop/go/versioning.mdx +++ b/docs/develop/go/versioning.mdx @@ -29,7 +29,7 @@ There are two primary Versioning methods that you can use: - [Versioning with Patching](#patching). This method works by adding branches to your code tied to specific revisions. It applies a code change to new Workflow Executions while avoiding disruptive changes to in-progress Workflow Executions. :::danger -Support for the experimental Worker Versioning method before 2025 will be removed from Temporal Server in March 2026. Refer to the [latest Worker Versioning docs](/worker-versioning) for guidance. You can still refer to the [Worker Versioning Legacy](worker-versioning-legacy) docs if needed. +Support for the experimental Worker Versioning method before 2025 will be removed from Temporal Server in March 2026. Refer to the [latest Worker Versioning docs](/worker-versioning) for guidance. You can still refer to the [Worker Versioning Legacy](/develop/typescript/worker-versioning-legacy) docs if needed. ::: ## Worker Versioning diff --git a/docs/develop/run-a-development-server.mdx b/docs/develop/run-a-development-server.mdx new file mode 100644 index 0000000000..336ef0b7d2 --- /dev/null +++ b/docs/develop/run-a-development-server.mdx @@ -0,0 +1,110 @@ +--- +id: run-a-development-server +title: Run a development server +description: Shows how to run a development Temporal Service +sidebar_label: Run a development server +toc_max_heading_level: 3 +tags: + - Temporal Client + - Temporal SDKs +--- + +## How to install the Temporal CLI and run a development server {#run-a-development-server} + +This page describes how to install the [Temporal CLI](/cli) and run a development Temporal Service. The local +development Temporal Service comes packaged with the [Temporal Web UI](/web-ui). + +For information on deploying and running a self-hosted production Temporal Service, see the +[Self-hosted guide](/self-hosted-guide), or sign up for [Temporal Cloud](/cloud) and let us run your production Temporal +Service for you. + +Temporal CLI is a tool for interacting with a Temporal Service from the command line and it includes a distribution of +the Temporal Server and Web UI. This local development Temporal Service runs as a single process with zero runtime +dependencies and it supports persistence to disk and in-memory mode through SQLite. + +**Install the Temporal CLI** + +The Temporal CLI is available on macOS, Windows, and Linux. + +### macOS + +**How to install the Temporal CLI on macOS** + +Choose one of the following install methods to install the Temporal CLI on macOS: + +**Install the Temporal CLI with Homebrew** + +```bash +brew install temporal +``` + +**Install the Temporal CLI from CDN** + +1. Select the platform and architecture needed. + +- Download for Darwin amd64: https://temporal.download/cli/archive/latest?platform=darwin&arch=amd64 +- Download for Darwin arm64: https://temporal.download/cli/archive/latest?platform=darwin&arch=arm64 + +2. Extract the downloaded archive. + +3. Add the `temporal` binary to your PATH. + +### Linux + +**How to install the Temporal CLI on Linux** + +Choose one of the following install methods to install the Temporal CLI on Linux: + +**Install the Temporal CLI with Homebrew** + +```bash +brew install temporal +``` + +**Install the Temporal CLI from CDN** + +1. Select the platform and architecture needed. + +- Download for Linux amd64: https://temporal.download/cli/archive/latest?platform=linux&arch=amd64 +- Download for Linux arm64: https://temporal.download/cli/archive/latest?platform=linux&arch=arm64 + +2. Extract the downloaded archive. + +3. Add the `temporal` binary to your PATH. + +### Windows + +**How to install the Temporal CLI on Windows** + +Follow these instructions to install the Temporal CLI on Windows: + +**Install the Temporal CLI from CDN** + +1. Select the platform and architecture needed and download the binary. + +- Download for Windows amd64: https://temporal.download/cli/archive/latest?platform=windows&arch=amd64 +- Download for Windows arm64: https://temporal.download/cli/archive/latest?platform=windows&arch=arm64 + +2. Extract the downloaded archive. + +3. Add the `temporal.exe` binary to your PATH. + +### Start the Temporal Development Server + +Start the Temporal Development Server by using the `server start-dev` command. + +```bash +temporal server start-dev +``` + +This command automatically starts the Web UI, creates the default [Namespace](/namespaces), and uses an in-memory +database. + +The Temporal Server should be available on `localhost:7233` and the Temporal Web UI should be accessible at +[`http://localhost:8233`](http://localhost:8233/). + +The server's startup configuration can be customized using command line options. For a full list of options, run: + +```bash +temporal server start-dev --help +``` diff --git a/docs/develop/typescript/asynchronous-activity-completion.mdx b/docs/develop/typescript/activities/asynchronous-activity-completion.mdx similarity index 97% rename from docs/develop/typescript/asynchronous-activity-completion.mdx rename to docs/develop/typescript/activities/asynchronous-activity-completion.mdx index 082e3a4add..25d289efb4 100644 --- a/docs/develop/typescript/asynchronous-activity-completion.mdx +++ b/docs/develop/typescript/activities/asynchronous-activity-completion.mdx @@ -2,7 +2,7 @@ id: asynchronous-activity-completion title: Asynchronous Activity Completion - TypeScript SDK sidebar_label: Asynchronous Activity Completion -slug: /develop/typescript/asynchronous-activity-completion +slug: /develop/typescript/activities/asynchronous-activity-completion toc_max_heading_level: 2 keywords: - asynchronous-activity-completion diff --git a/docs/develop/typescript/activities/basic-activity.mdx b/docs/develop/typescript/activities/basic-activity.mdx new file mode 100644 index 0000000000..7b2ea4af6d --- /dev/null +++ b/docs/develop/typescript/activities/basic-activity.mdx @@ -0,0 +1,252 @@ +--- +id: basic-activity +title: Basic Activity - TypeScript SDK +description: Shows how to create a basic Activity with the TypeScript SDK +sidebar_label: Basic Activity +slug: /develop/typescript/activities/basic-activity +toc_max_heading_level: 3 +tags: + - TypeScript SDK + - Temporal SDKs + - Activity +--- + +## How to develop a basic Activity {#develop-activities} + +One of the primary things that Workflows do is orchestrate the execution of Activities. An Activity is a normal function +or method execution that's intended to execute a single, well-defined action (either short or long-running), such as +querying a database, calling a third-party API, or transcoding a media file. An Activity can interact with world outside +the Temporal Platform or use a Temporal Client to interact with a Temporal Service. For the Workflow to be able to +execute the Activity, we must define the [Activity Definition](/activity-definition). + +- Activities execute in the standard Node.js environment. +- Activities cannot be in the same file as Workflows and must be separately registered. +- Activities may be retried repeatedly, so you may need to use idempotency keys for critical side effects. + +Activities are _just functions_. The following is an Activity that accepts a string parameter and returns a string. + + + +[snippets/src/activities.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/activities.ts) + +```ts +export async function greet(name: string): Promise { + return `👋 Hello, ${name}!`; +} +``` + + + +### How to develop Activity Parameters {#activity-parameters} + +There is no explicit limit to the total number of parameters that an [Activity Definition](/activity-definition) may +support. However, there is a limit to the total size of the data that ends up encoded into a gRPC message Payload. + +A single argument is limited to a maximum size of 2 MB. And the total size of a gRPC message, which includes all the +arguments, is limited to a maximum of 4 MB. + +Also, keep in mind that all Payload data is recorded in the +[Workflow Execution Event History](/workflow-execution/event#event-history) and large Event Histories can affect Worker +performance. This is because the entire Event History could be transferred to a Worker Process with a +[Workflow Task](/tasks#workflow-task). + +{/* TODO link to gRPC limit section when available */} + +Some SDKs require that you pass context objects, others do not. When it comes to your application data—that is, data +that is serialized and encoded into a Payload—we recommend that you use a single object as an argument that wraps the +application data passed to Activities. This is so that you can change what data is passed to the Activity without +breaking a function or method signature. + +This Activity takes a single `name` parameter of type `string`. + + + +[snippets/src/activities.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/activities.ts) + +```ts +export async function greet(name: string): Promise { + return `👋 Hello, ${name}!`; +} +``` + + + +### How to define Activity return values {#activity-return-values} + +All data returned from an Activity must be serializable. + +Activity return values are subject to payload size limits in Temporal. The default payload size limit is 2MB, and there +is a hard limit of 4MB for any gRPC message size in the Event History transaction +([see Cloud limits here](https://docs.temporal.io/cloud/limits#per-message-grpc-limit)). Keep in mind that all return +values are recorded in a [Workflow Execution Event History](/workflow-execution/event#event-history). + +In TypeScript, the return value is always a Promise. + +In the following example, `Promise` is the return value. + +```typescript +export async function greet(name: string): Promise { + return `👋 Hello, ${name}!`; +} +``` + +### How to customize your Activity Type {#activity-type} + +Activities have a Type that are referred to as the Activity name. The following examples demonstrate how to set a custom +name for your Activity Type. + +You can customize the name of the Activity when you register it with the Worker. In the following example, the Activity +Name is `activityFoo`. + + + +[snippets/src/worker-activity-type-custom.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/worker-activity-type-custom.ts) + +```ts +import { Worker } from '@temporalio/worker'; +import { greet } from './activities'; + +async function run() { + const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + taskQueue: 'snippets', + activities: { + activityFoo: greet, + }, + }); + + await worker.run(); +} +``` + + + +### Important design patterns for Activities {#activity-design-patterns} + +The following are some important (and frequently requested) patterns for using our Activities APIs. These patterns +address common needs and use cases. + +#### Share dependencies in Activity functions (dependency injection) + +Because Activities are "just functions," you can also create functions that create Activities. This is a helpful pattern +for using closures to do the following: + +- Store expensive dependencies for sharing, such as database connections. +- Inject secret keys (such as environment variables) from the Worker to the Activity. + + + +[activities-dependency-injection/src/activities.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-dependency-injection/src/activities.ts) + +```ts +export interface DB { + get(key: string): Promise; +} + +export const createActivities = (db: DB) => ({ + async greet(msg: string): Promise { + const name = await db.get('name'); // simulate read from db + return `${msg}: ${name}`; + }, + async greet_es(mensaje: string): Promise { + const name = await db.get('name'); // simulate read from db + return `${mensaje}: ${name}`; + }, +}); +``` + + + +
+ See full example + +When you register these in the Worker, pass your shared dependencies accordingly: + + + +```ts +import { createActivities } from './activities'; + +async function run() { + // Mock DB connection initialization in Worker + const db = { + async get(_key: string) { + return 'Temporal'; + }, + }; + + const worker = await Worker.create({ + taskQueue: 'dependency-injection', + workflowsPath: require.resolve('./workflows'), + activities: createActivities(db), + }); + + await worker.run(); +} + +run().catch((err) => { + console.error(err); + process.exit(1); +}); +``` + + + +Because Activities are always referenced by name, inside the Workflow they can be proxied as normal, although the types +need some adjustment: + + + +[activities-dependency-injection/src/workflows.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-dependency-injection/src/workflows.ts) + +```ts +import type { createActivities } from './activities'; + +// Note usage of ReturnType<> generic since createActivities is a factory function +const { greet, greet_es } = proxyActivities>({ + startToCloseTimeout: '30 seconds', +}); +``` + + + +
+ +#### Import multiple Activities simultaneously + +You can proxy multiple Activities from the same `proxyActivities` call if you want them to share the same timeouts, +retries, and options: + +```ts +export async function Workflow(name: string): Promise { + // destructuring multiple activities with the same options + const { act1, act2, act3 } = proxyActivities(); + /* activityOptions */ + await act1(); + await Promise.all([act2, act3]); +} +``` + +#### Dynamically reference Activities + +Because Activities are referenced only by their string names, you can reference them dynamically if needed: + +```js +export async function DynamicWorkflow(activityName, ...args) { + const acts = proxyActivities(/* activityOptions */); + + // these are equivalent + await acts.activity1(); + await acts['activity1'](); + + // dynamic reference to activities using activityName + let result = await acts[activityName](...args); +} +``` + +Type safety is still supported here, but we encourage you to validate and handle mismatches in Activity names. An +invalid Activity name leads to a `NotFoundError` with a message that looks like this: + +``` +ApplicationFailure: Activity function actC is not registered on this Worker, available activities: ["actA", "actB"] +``` \ No newline at end of file diff --git a/docs/develop/typescript/benign-exceptions.mdx b/docs/develop/typescript/activities/benign-exceptions.mdx similarity index 100% rename from docs/develop/typescript/benign-exceptions.mdx rename to docs/develop/typescript/activities/benign-exceptions.mdx diff --git a/docs/develop/typescript/activities/execution.mdx b/docs/develop/typescript/activities/execution.mdx new file mode 100644 index 0000000000..4e2227bf4f --- /dev/null +++ b/docs/develop/typescript/activities/execution.mdx @@ -0,0 +1,89 @@ +--- +id: execution +title: Activity execution - TypeScript SDK +description: Shows how to perform Activity execution with the TypeScript SDK +sidebar_label: Activity Execution +slug: /develop/typescript/activities/execution +toc_max_heading_level: 3 +tags: + - TypeScript SDK + - Temporal SDKs + - Activity +--- + +## How to start an Activity Execution {#activity-execution} + +Calls to spawn [Activity Executions](/activity-execution) are written within a +[Workflow Definition](/workflow-definition). In TypeScript, you never call an Activity function directly. Instead, you +pass in the _types_ of your Activities and Activity options to the `proxyActivities` function. This will give you an +_Activity Handle_, a type-safe proxy object with the same function names and signatures as your real activities. From +the Activity Handle, you can call your Activities as if they were normal async functions. + +```typescript +import { proxyActivities } from '@temporalio/workflow'; +// Only import the activity types, not the functions themselves +import type * as activities from './activities'; + +// Retrieve the Activity Handle by passing in the Activity types and options +const activityHandle = proxyActivities({ + startToCloseTimeout: '1 minute', +}); + +// Deconstruct the individual Activity functions from the Activity Handle +const { greet } = activityHandle; + +// A workflow that calls an activity +export async function example(name: string): Promise { + return await greet(name); +} +``` + +When you call a proxied function, the Workflow does not execute the Activity code directly. Instead, it schedules an +Activity Task. After the Activity Task is scheduled, it becomes available for a Worker to pick up and execute. This +results in the set of three [Activity Task](/tasks#activity-task) related Events: +[ActivityTaskScheduled](/references/events#activitytaskscheduled), +[ActivityTaskStarted](/references/events#activitytaskstarted), and +[ActivityTaskCompleted](/references/events#activitytaskcompleted) in your Workflow Execution Event History. + +The Worker may run many Activity executions at the same time, all using the same Activity function code. Temporal can +also retry an Activity if it fails or times out. For this reason, you should write Activities to be +[idempotent](/encyclopedia/activities/activity-definition.mdx#idempotency): calling them multiple times with the +same input should have the same effect as calling them once. + +:::tip Every Activity call you make is recorded in the Workflow’s execution history, including the parameters you pass +in and the value that comes back. This history is what allows Temporal to recover a Workflow after a failure. Because +the entire history must be stored and replayed, avoid passing large objects as Activity inputs or return values. Keeping +payloads small will help your Workflows replay and recover efficiently. ::: + +### How to set the required Activity Timeouts {#required-timeout} + +Activity Execution semantics rely on several parameters. The only required value that needs to be set is either a +[Schedule-To-Close Timeout](/encyclopedia/detecting-activity-failures#schedule-to-close-timeout) or a +[Start-To-Close Timeout](/encyclopedia/detecting-activity-failures#start-to-close-timeout). These values are set in the +Activity Options. + +### How to get the results of an Activity Execution {#get-activity-results} + +The call to spawn an [Activity Execution](/activity-execution) generates the +[ScheduleActivityTask](/references/commands#scheduleactivitytask) Command and provides the Workflow with an Awaitable. +Workflow Executions can either block progress until the result is available through the Awaitable or continue +progressing, making use of the result when it becomes available. + +Since Activities are referenced by their string name, you can reference them dynamically to get the result of an +Activity Execution. + +```typescript +export async function DynamicWorkflow(activityName, ...args) { + const acts = proxyActivities(/* activityOptions */); + + // these are equivalent + await acts.activity1(); + await acts['activity1'](); + + let result = await acts[activityName](...args); + return result; +} +``` + +The `proxyActivities()` returns an object that calls the Activities in the function. `acts[activityName]()` references +the Activity using the Activity name, then it returns the results. \ No newline at end of file diff --git a/docs/develop/typescript/activities/index.mdx b/docs/develop/typescript/activities/index.mdx new file mode 100644 index 0000000000..4b5ee977da --- /dev/null +++ b/docs/develop/typescript/activities/index.mdx @@ -0,0 +1,25 @@ +--- +id: index +title: Workflows - TypeScript SDK +sidebar_label: Workflows +description: + This section explains how to implement Workers with the TypeScript SDK +toc_max_heading_level: 4 +keywords: + - typescript +tags: + - TypeScript SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![TypeScript SDK Banner](/img/assets/banner-typescript-temporal.png) + +## Activities + +- [Basic Activity](/develop/typescript/activities/basic-activity) +- [Activity Execution](/develop/typescript/activities/execution) +- [Timeouts](/develop/typescript/activities/timeouts) +- [Asynchronous Activity Completion](/develop/typescript/activities/asynchronous-activity-completion) +- [Benign exceptions](/develop/typescript/activities/benign-exceptions) diff --git a/docs/develop/typescript/failure-detection.mdx b/docs/develop/typescript/activities/timeouts.mdx similarity index 54% rename from docs/develop/typescript/failure-detection.mdx rename to docs/develop/typescript/activities/timeouts.mdx index 0b85362554..9062bee304 100644 --- a/docs/develop/typescript/failure-detection.mdx +++ b/docs/develop/typescript/activities/timeouts.mdx @@ -1,12 +1,13 @@ --- -id: failure-detection -title: Failure detection - TypeScript SDK feature guide -sidebar_label: Failure detection -description: Optimize Workflow Execution with Temporal TypeScript SDK - Set Timeouts, Retry Policies, and manage Activity Heartbeats efficiently. +id: timeouts +title: Activity Timeouts - TypeScript SDK +sidebar_label: Timeouts +description: Optimize Workflow Execution with Temporal TypeScript SDK - Set Activity Timeouts and Retry Policies efficiently. toc_max_heading_level: 4 keywords: - typescript - failure detection + - timeouts tags: - Activities - Workflows @@ -18,151 +19,12 @@ tags: This page shows how to do the following: -- [Raise and Handle Exceptions](#exception-handling) -- [Deliberately Fail Workflows](#workflow-failure) -- [Workflow Timeouts](#workflow-timeouts) -- [Workflow retries](#workflow-retries) - [Activity Timeouts](#activity-timeouts) - [Activity Retry Policy](#activity-retries) - [Activity next Retry delay](#activity-next-retry-delay) - [Heartbeat an Activity](#activity-heartbeats) - [Activity Heartbeat Timeout](#activity-heartbeat-timeout) -## Raise and Handle Exceptions {#exception-handling} - -In each Temporal SDK, error handling is implemented idiomatically, following the conventions of the language. -Temporal uses several different error classes internally — for example, [`CancelledFailure`](https://typescript.temporal.io/api/classes/common.CancelledFailure) in the Typescript SDK, to handle a Workflow cancellation. -You should not raise or otherwise implement these manually, as they are tied to Temporal platform logic. - -The one Temporal error class that you will typically raise deliberately is [`ApplicationFailure`](https://typescript.temporal.io/api/classes/common.ApplicationFailure). -In fact, *any* other exceptions that are raised from your Typescript code in a Temporal Activity will be converted to an `ApplicationError` internally. -This way, an error's type, severity, and any additional details can be sent to the Temporal Service, indexed by the Web UI, and even serialized across language boundaries. - -In other words, these two code samples do the same thing: - -```typescript -class InvalidChargeError extends Error { - constructor(message: string) { - super(message); - this.name = "InvalidChargeError"; - Object.setPrototypeOf(this, CustomError.prototype); - } -} - -if (chargeAmount < 0) { - throw new InvalidChargeError(`Invalid charge amount: ${chargeAmount} (must be above zero)`); -} -``` - -```typescript -if (chargeAmount < 0) { - throw ApplicationFailure.create({ - message: `Invalid charge amount: ${chargeAmount} (must be above zero)`, - type: 'InvalidChargeError', - }); -} -``` - -Depending on your implementation, you may decide to use either method. -One reason to use the Temporal `ApplicationFailure` class is because it allows you to set an additional `non_retryable` parameter. -This way, you can decide whether an error should not be retried automatically by Temporal. -This can be useful for deliberately failing a Workflow due to bad input data, rather than waiting for a timeout to elapse: - -```typescript -if (chargeAmount < 0) { - throw ApplicationFailure.create({ - message: `Invalid charge amount: ${chargeAmount} (must be above zero)`, - nonRetryable: true - }); -} -``` - -You can alternately specify a list of errors that are non-retryable in your Activity [Retry Policy](#activity-retries). - -## Failing Workflows {#workflow-failure} - -One of the core design principles of Temporal is that an Activity Failure will never directly cause a Workflow Failure — a Workflow should never return as Failed unless deliberately. -The default retry policy associated with Temporal Activities is to retry them until reaching a certain timeout threshold. -Activities will not actually *return* a failure to your Workflow until this condition or another non-retryable condition is met. -At this point, you can decide how to handle an error returned by your Activity the way you would in any other program. -For example, you could implement a [Saga Pattern](https://github.com/temporalio/samples-typescript/tree/main/saga) that uses `try` and `catch` blocks to "unwind" some of the steps your Workflow has performed up to the point of Activity Failure. - -**You will only fail a Workflow by manually raising an `ApplicationFailure` from the Workflow code.** -You could do this in response to an Activity Failure, if the failure of that Activity means that your Workflow should not continue: - -```typescript -try { - await addAddress(); -} catch (err) { - if (err instanceof ActivityFailure && err.cause instanceof ApplicationFailure) { - log.error(err.cause.message); - throw err; - } -} -``` - -This works differently in a Workflow than raising exceptions from Activities. -In an Activity, any Typescript exceptions or custom exceptions are converted to a Temporal `ApplicationFailure`. -In a Workflow, any exceptions that are raised other than an explicit Temporal `ApplicationFailure` will only fail that particular [Workflow Task](https://docs.temporal.io/tasks#workflow-task-execution) and be retried. -This includes any typical Typescript runtime errors like an `undefined` error that are raised automatically. -These errors are treated as bugs that can be corrected with a fixed deployment, rather than a reason for a Temporal Workflow Execution to return unexpectedly. - -## Workflow Timeouts {#workflow-timeouts} - -**How to set Workflow Timeouts using the Temporal TypeScript SDK** - -Each Workflow timeout controls the maximum duration of a different aspect of a Workflow Execution. - -Before we continue, we want to note that we generally do not recommend setting Workflow Timeouts, because Workflows are designed to be long-running and resilient. -Instead, setting a Timeout can limit its ability to handle unexpected delays or long-running processes. -If you need to perform an action inside your Workflow after a specific period of time, we recommend using a Timer. - -Workflow Timeouts are set when starting a Workflow using either the Client or Workflow API. - -- **[Workflow Execution Timeout](/encyclopedia/detecting-workflow-failures#workflow-execution-timeout)** - restricts the maximum amount of time that a single Workflow Execution can be executed -- **[Workflow Run Timeout](/encyclopedia/detecting-workflow-failures#workflow-run-timeout):** restricts the maximum amount of time that a single Workflow Run can last -- **[Workflow Task Timeout](/encyclopedia/detecting-workflow-failures#workflow-task-timeout):** restricts the maximum amount of time that a Worker can execute a Workflow Task - -The following properties can be set on the [`WorkflowOptions`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/) when starting a Workflow using either the Client or Workflow API: - -- [`workflowExecutionTimeout​`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/#workflowexecutiontimeout) -- [`workflowRunTimeout`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/#workflowruntimeout) -- [`workflowTaskTimeout`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/#workflowtasktimeout) - -```typescript -await client.workflow.start(example, { - taskQueue, - workflowId, - // Set Workflow Timeout duration - workflowExecutionTimeout: '1 day', - // workflowRunTimeout: '1 minute', - // workflowTaskTimeout: '30 seconds', -}); -``` - -## Workflow retries {#workflow-retries} - -**How to set Workflow retries using the Temporal TypeScript SDK** - -A Retry Policy can work in cooperation with the timeouts to provide fine controls to optimize the execution experience. - -Use a [Retry Policy](/encyclopedia/retry-policies) to retry a Workflow Execution in the event of a failure. - -Workflow Executions do not retry by default, and Retry Policies should be used with Workflow Executions only in certain situations. - -The Retry Policy can be set through the [`WorkflowOptions.retry`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/#retry) property when starting a Workflow using either the Client or Workflow API. - -```typescript -const handle = await client.workflow.start(example, { - taskQueue, - workflowId, - retry: { - maximumAttempts: 3, - maximumInterval: '30 seconds', - }, -}); -``` - ## Activity Timeouts {#activity-timeouts} **How to set Activity Timeouts using the Temporal TypeScript SDK** diff --git a/docs/develop/typescript/converters-and-encryption.mdx b/docs/develop/typescript/best-practices/converters-and-encryption.mdx similarity index 100% rename from docs/develop/typescript/converters-and-encryption.mdx rename to docs/develop/typescript/best-practices/converters-and-encryption.mdx diff --git a/docs/develop/typescript/debugging.mdx b/docs/develop/typescript/best-practices/debugging.mdx similarity index 96% rename from docs/develop/typescript/debugging.mdx rename to docs/develop/typescript/best-practices/debugging.mdx index 6f5368ff33..a7d945fc74 100644 --- a/docs/develop/typescript/debugging.mdx +++ b/docs/develop/typescript/best-practices/debugging.mdx @@ -2,7 +2,7 @@ id: debugging title: Debugging - TypeScript SDK sidebar_label: Debugging -slug: /develop/typescript/debugging +slug: /develop/typescript/best-practices/debugging description: The Temporal TypeScript SDK Debugging guide provides tools and tips for debugging Workflows and Workers in development and production environments. Troubleshoot common issues using the Web UI, Temporal CLI, and more. toc_max_heading_level: 4 keywords: @@ -27,12 +27,12 @@ You can debug production Workflows using: - [Web UI](/web-ui) - [Temporal CLI](/cli) -- [Replay](/develop/typescript/testing-suite#replay) -- [Tracing](/develop/typescript/observability#tracing) -- [Logging](/develop/typescript/observability#logging) +- [Replay](/develop/typescript/best-practices/testing-suite#replay) +- [Tracing](/develop/typescript/workers/observability#tracing) +- [Logging](/develop/typescript/workers/observability#logging) You can debug and tune Worker performance with metrics and the [Worker performance guide](/develop/worker-performance). -For information on setting up SDK metrics, see [Metrics](/develop/typescript/observability#metrics) in the Observability section of the TypeScript SDK developer's guide. +For information on setting up SDK metrics, see [Metrics](/develop/typescript/workers/observability#metrics) in the Observability section of the TypeScript SDK developer's guide. Debug Server performance with [Cloud metrics](/cloud/metrics/) or [self-hosted Server metrics](/self-hosted-guide/production-checklist#scaling-and-metrics). @@ -93,13 +93,13 @@ When you pass a `workflowsPath`, our Webpack config expects to find `node_module Temporal Workflow Bundles need to [export a set of methods that fit the compiled `worker-interface.ts` from `@temporalio/workflow`](https://github.com/temporalio/sdk-typescript/blob/eaa2d205c9bc5ff4a3b17c0b34f2dcf6b1e0264a/packages/worker/src/workflow/bundler.ts#L81) as an entry point. We do offer a `bundleWorkflowCode` method to assist you with this, though it uses our Webpack settings. -For more information, see the [Register types](/develop/typescript/core-application#register-types) section. +For more information, see the [Register types](/develop/typescript/workers/run-worker-process#register-types) section. ### Webpack errors The TypeScript SDK's Worker bundles Workflows based on `workflowsPath` with [Webpack](https://webpack.js.org/) and run them inside v8 isolates. -If Webpack fails to create the bundle, the SDK will throw an error and emit webpack logs using the SDK's [logger](/develop/typescript/observability#logging). +If Webpack fails to create the bundle, the SDK will throw an error and emit webpack logs using the SDK's [logger](/develop/typescript/workers/observability#logging). If you do not see Webpack output in your terminal make sure that you have not disabled SDK logging (see reference to `Runtime.install()` in the link above). diff --git a/docs/develop/typescript/entity-pattern.mdx b/docs/develop/typescript/best-practices/entity-pattern.mdx similarity index 97% rename from docs/develop/typescript/entity-pattern.mdx rename to docs/develop/typescript/best-practices/entity-pattern.mdx index 1bcdf1c4da..5948efe43e 100644 --- a/docs/develop/typescript/entity-pattern.mdx +++ b/docs/develop/typescript/best-practices/entity-pattern.mdx @@ -2,7 +2,7 @@ id: entity-pattern title: Entity pattern - TypeScript SDK sidebar_label: Entity pattern -slug: /develop/typescript/entity-pattern +slug: /develop/typescript/best-practices/entity-pattern toc_max_heading_level: 4 keywords: - entity-pattern diff --git a/docs/develop/typescript/best-practices/index.mdx b/docs/develop/typescript/best-practices/index.mdx new file mode 100644 index 0000000000..34d1798813 --- /dev/null +++ b/docs/develop/typescript/best-practices/index.mdx @@ -0,0 +1,24 @@ +--- +id: index +title: Best Practices - TypeScript SDK +sidebar_label: Best practices +description: + This section explains how to implement Temporal best practices with the TypeScript SDK +toc_max_heading_level: 4 +keywords: + - typescript +tags: + - TypeScript SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![TypeScript SDK Banner](/img/assets/banner-typescript-temporal.png) + +## Best practices + +- [Testing](/develop/typescript/best-practices/testing-suite) +- [Debugging](/develop/typescript/best-practices/debugging) +- [Converters and encryption](/develop/typescript/converters-and-encryption) +- [Entity pattern](/develop/typescript/best-practices/entity-pattern) diff --git a/docs/develop/typescript/testing-suite.mdx b/docs/develop/typescript/best-practices/testing-suite.mdx similarity index 100% rename from docs/develop/typescript/testing-suite.mdx rename to docs/develop/typescript/best-practices/testing-suite.mdx diff --git a/docs/develop/typescript/client/index.mdx b/docs/develop/typescript/client/index.mdx new file mode 100644 index 0000000000..eb38d67fad --- /dev/null +++ b/docs/develop/typescript/client/index.mdx @@ -0,0 +1,22 @@ +--- +id: index +title: Client - TypeScript SDK +sidebar_label: Client +description: + This section explains how to implement the Client with the TypeScript SDK +toc_max_heading_level: 4 +keywords: + - typescript +tags: + - TypeScript SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![TypeScript SDK Banner](/img/assets/banner-typescript-temporal.png) + +## Temporal Client + +- [Temporal Client](/develop/typescript/client/temporal-client) +- [Namespaces](/develop/typescript/namespaces) \ No newline at end of file diff --git a/docs/develop/typescript/namespaces.mdx b/docs/develop/typescript/client/namespaces.mdx similarity index 100% rename from docs/develop/typescript/namespaces.mdx rename to docs/develop/typescript/client/namespaces.mdx diff --git a/docs/develop/typescript/temporal-client.mdx b/docs/develop/typescript/client/temporal-client.mdx similarity index 98% rename from docs/develop/typescript/temporal-client.mdx rename to docs/develop/typescript/client/temporal-client.mdx index 84f3c2d85b..d4afaebdc9 100644 --- a/docs/develop/typescript/temporal-client.mdx +++ b/docs/develop/typescript/client/temporal-client.mdx @@ -61,8 +61,16 @@ import { Connection, Client } from '@temporalio/client'; async function run() { const connection = await Connection.connect(); + + // your code goes here + const client = new Client({ connection }); } + +run().catch((err) => { + console.error(err); + process.exit(1); +}); ``` If you need to connect to a Temporal Service with custom options, you can provide connection options directly in code, @@ -80,7 +88,7 @@ creating the Temporal Client. You can use the environment variable `TEMPORAL_CONFIG_FILE` to specify the location of the TOML file or provide the path to the file directly in code. If you don't provide the configuration file path, the SDK looks for it at the path `~/.config/temporalio/temporal.toml` or the equivalent on your OS. Refer to -[Environment Configuration](../environment-configuration.mdx) for more details about configuration files and profiles. +[Environment Configuration](/develop/environment-configuration) for more details about configuration files and profiles. :::info @@ -128,6 +136,7 @@ You can create a Temporal Client using a profile from the configuration file as {/* SNIPSTART typescript-env-config-load-default-profile {"highlightedLines": "17-19,28-29"} */} [env-config/src/load-from-file.ts](https://github.com/temporalio/samples-typescript/blob/main/env-config/src/load-from-file.ts) + ```ts {17-19,28-29} import { Connection, Client } from '@temporalio/client'; import { loadClientConnectConfig } from '@temporalio/envconfig'; @@ -170,6 +179,7 @@ main().catch((err) => { process.exit(1); }); ``` + {/* SNIPEND */} @@ -201,17 +211,18 @@ variables take precedence, they will override any values set in the configuratio {/* SNIPSTART typescript-env-config-load-default-profile {"highlightedLines": "7,17-18", "selectedLines": ["1-5","17","19","22-40"]} */} [env-config/src/load-from-file.ts](https://github.com/temporalio/samples-typescript/blob/main/env-config/src/load-from-file.ts) + ```ts {7,17-18} import { Connection, Client } from '@temporalio/client'; import { loadClientConnectConfig } from '@temporalio/envconfig'; import { resolve } from 'path'; async function main() { -// ... + // ... const config = loadClientConnectConfig({ -// ... + // ... }); -// ... + // ... console.log(` Address: ${config.connectionOptions.address}`); console.log(` Namespace: ${config.namespace}`); console.log(` gRPC Metadata: ${JSON.stringify(config.connectionOptions.metadata)}`); @@ -232,6 +243,7 @@ main().catch((err) => { process.exit(1); }); ``` + {/* SNIPEND */} @@ -267,7 +279,8 @@ local development instance: - Your credentials for authentication. - If you are using an API key, provide the API key value. - If you are using mTLS, provide the mTLS CA certificate and mTLS private key. -- Your _Namespace and Account ID_ combination, which follows the format `.`. +- Your [Temporal Cloud Namespace Id](/cloud/namespaces#temporal-cloud-namespace-id) and Account Id combination, which + follows the format `.`. - The _endpoint_ may vary. The most common endpoint used is the gRPC regional endpoint, which follows the format: `..api.temporal.io:7233`. - For Namespaces with High Availability features with API key authentication enabled, use the gRPC Namespace endpoint: @@ -331,6 +344,7 @@ you can also programmatically override specific connection options before creati {/* SNIPSTART typescript-env-config-load-profile-with-overrides {"highlightedLines": "15-18,30-31"} */} [env-config/src/load-profile.ts](https://github.com/temporalio/samples-typescript/blob/main/env-config/src/load-profile.ts) + ```ts {15-18,30-31} import { Connection, Client } from '@temporalio/client'; import { loadClientConnectConfig } from '@temporalio/envconfig'; @@ -375,6 +389,7 @@ main().catch((err) => { process.exit(1); }); ``` + {/* SNIPEND */} @@ -406,17 +421,18 @@ variables will take precedence. {/* SNIPSTART typescript-env-config-load-default-profile {"highlightedLines": "17-19,28-29", "selectedLines": ["1-5","17","19","22-40"]} */} [env-config/src/load-from-file.ts](https://github.com/temporalio/samples-typescript/blob/main/env-config/src/load-from-file.ts) + ```ts {17-19,28-29} import { Connection, Client } from '@temporalio/client'; import { loadClientConnectConfig } from '@temporalio/envconfig'; import { resolve } from 'path'; async function main() { -// ... + // ... const config = loadClientConnectConfig({ -// ... + // ... }); -// ... + // ... console.log(` Address: ${config.connectionOptions.address}`); console.log(` Namespace: ${config.namespace}`); console.log(` gRPC Metadata: ${JSON.stringify(config.connectionOptions.metadata)}`); @@ -437,6 +453,7 @@ main().catch((err) => { process.exit(1); }); ``` + {/* SNIPEND */} @@ -628,7 +645,7 @@ language-specific contextual data, and Workflow Function parameters. In the examples below, all Workflow Executions are started using a Temporal Client. To spawn Workflow Executions from within another Workflow Execution, use either the Child Workflow or External Workflow APIs. -See the [Customize Workflow Type](/develop/typescript/core-application#workflow-type) section to see how to customize +See the [Customize Workflow Type](/develop/typescript/workflows/basic-workflow#workflow-type) section to see how to customize the name of the Workflow Type. A request to spawn a Workflow Execution causes the Temporal Service to create the first Event diff --git a/docs/develop/typescript/core-application.mdx b/docs/develop/typescript/core-application.mdx deleted file mode 100644 index d5a513538f..0000000000 --- a/docs/develop/typescript/core-application.mdx +++ /dev/null @@ -1,1440 +0,0 @@ ---- -id: core-application -title: Core application - TypeScript SDK -sidebar_label: Core application -slug: /develop/typescript/core-application -toc_max_heading_level: 2 -keywords: - - core-application -tags: - - Activities - - Temporal Client - - Task Queues - - Workers - - Workflows - - TypeScript SDK - - Temporal SDKs -description: The Foundations section of the Temporal Developer's guide explains essential concepts needed for building and running a Temporal Application, from installing the Temporal CLI to starting Workflow and Activity Executions. ---- - -import * as Components from '@site/src/components'; - -The Foundations section of the Temporal Developer's guide covers the minimum set of concepts and implementation details needed to build and run a [Temporal Application](/temporal#temporal-application)—that is, all the relevant steps to start a [Workflow Execution](#develop-workflows) that executes an [Activity](#develop-activities). - -In this section you can find the following: - -- [Run a development Temporal Service](#run-a-development-server) -- [Connect to a development Temporal Service](#connect-to-a-dev-cluster) -- [Connect to Temporal Cloud](#connect-to-temporal-cloud) -- [Develop a Workflow](#develop-workflows) -- [Develop an Activity](#develop-activities) -- [Start an Activity Execution](#activity-execution) -- [Run a dev Worker](#run-a-dev-worker) -- [Run a Worker on Docker](#run-a-worker-on-docker) -- [Run a Temporal Cloud Worker](#run-a-dev-worker) -- [Start a Workflow Execution](#start-workflow-execution) - -## How to install the Temporal CLI and run a development server {#run-a-development-server} - -This section describes how to install the [Temporal CLI](/cli) and run a development Temporal Service. -The local development Temporal Service comes packaged with the [Temporal Web UI](/web-ui). - -For information on deploying and running a self-hosted production Temporal Service, see the [Self-hosted guide](/self-hosted-guide), or sign up for [Temporal Cloud](/cloud) and let us run your production Temporal Service for you. - -Temporal CLI is a tool for interacting with a Temporal Service from the command line and it includes a distribution of the Temporal Server and Web UI. -This local development Temporal Service runs as a single process with zero runtime dependencies and it supports persistence to disk and in-memory mode through SQLite. - -**Install the Temporal CLI** - -The Temporal CLI is available on macOS, Windows, and Linux. - -### macOS - -**How to install the Temporal CLI on macOS** - -Choose one of the following install methods to install the Temporal CLI on macOS: - -**Install the Temporal CLI with Homebrew** - -```bash -brew install temporal -``` - -**Install the Temporal CLI from CDN** - -1. Select the platform and architecture needed. - -- Download for Darwin amd64: https://temporal.download/cli/archive/latest?platform=darwin&arch=amd64 -- Download for Darwin arm64: https://temporal.download/cli/archive/latest?platform=darwin&arch=arm64 - -2. Extract the downloaded archive. - -3. Add the `temporal` binary to your PATH. - -### Linux - -**How to install the Temporal CLI on Linux** - -Choose one of the following install methods to install the Temporal CLI on Linux: - -**Install the Temporal CLI with Homebrew** - -```bash -brew install temporal -``` - -**Install the Temporal CLI from CDN** - -1. Select the platform and architecture needed. - -- Download for Linux amd64: https://temporal.download/cli/archive/latest?platform=linux&arch=amd64 -- Download for Linux arm64: https://temporal.download/cli/archive/latest?platform=linux&arch=arm64 - -2. Extract the downloaded archive. - -3. Add the `temporal` binary to your PATH. - -### Windows - -**How to install the Temporal CLI on Windows** - -Follow these instructions to install the Temporal CLI on Windows: - -**Install the Temporal CLI from CDN** - -1. Select the platform and architecture needed and download the binary. - -- Download for Windows amd64: https://temporal.download/cli/archive/latest?platform=windows&arch=amd64 -- Download for Windows arm64: https://temporal.download/cli/archive/latest?platform=windows&arch=arm64 - -2. Extract the downloaded archive. - -3. Add the `temporal.exe` binary to your PATH. - -### Start the Temporal Development Server - -Start the Temporal Development Server by using the `server start-dev` command. - -```bash -temporal server start-dev -``` - -This command automatically starts the Web UI, creates the default [Namespace](/namespaces), and uses an in-memory database. - -The Temporal Server should be available on `localhost:7233` and the Temporal Web UI should be accessible at [`http://localhost:8233`](http://localhost:8233/). - -The server's startup configuration can be customized using command line options. -For a full list of options, run: - -```bash -temporal server start-dev --help -``` - -## How to install a Temporal SDK {#install-a-temporal-sdk} - -A [Temporal SDK](/encyclopedia/temporal-sdks) provides a framework for [Temporal Application](/temporal#temporal-application) development. - -An SDK provides you with the following: - -- A [Temporal Client](/encyclopedia/temporal-sdks#temporal-client) to communicate with a [Temporal Service](/temporal-service). -- APIs to develop [Workflows](/workflows). -- APIs to create and manage [Worker Processes](/workers#worker). -- APIs to author [Activities](/activity-definition). - -[![NPM](https://img.shields.io/npm/v/temporalio.svg?style=for-the-badge)](https://www.npmjs.com/search?q=author%3Atemporal-sdk-team) - -This project requires Node.js 18 or later. - -**Create a project** - -```bash -npx @temporalio/create@latest ./your-app -``` - -**Add to an existing project** - -```bash -npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity @temporalio/common -``` - -:::note - -The TypeScript SDK is designed with TypeScript-first developer experience in mind, but it works equally well with JavaScript. - -::: - -### How to find the TypeScript SDK API reference {#api-reference} - -The Temporal TypeScript SDK API reference is published to [typescript.temporal.io](https://typescript.temporal.io). - -### Where are SDK-specific code examples? {#code-samples} - -You can find a complete list of executable code samples in [Temporal's GitHub repository](https://github.com/temporalio?q=samples-&type=all&language=&sort=). - -Additionally, several of the [Tutorials](https://learn.temporal.io) are backed by a fully executable template application. - -Use the [TypeScript samples library](https://github.com/temporalio/samples-typescript) stored on GitHub to demonstrate various capabilities of Temporal. - -**Where can I find video demos?** - -[Temporal TypeScript YouTube playlist](https://www.youtube.com/playlist?list=PLl9kRkvFJrlTavecydpk9r6cF7qBmQJvb). - -### How to import an ECMAScript module {#ecmascript-modules} - -The JavaScript ecosystem is quickly moving toward publishing ECMAScript modules (ESM) instead of CommonJS modules. -For example, `node-fetch@3` is ESM, but `node-fetch@2` is CommonJS. - -For more information about importing a pure ESM dependency, see our [Fetch ESM](https://github.com/temporalio/samples-typescript/tree/main/fetch-esm) sample for the necessary configuration changes: - -- `package.json` must have include the `"type": "module"` attribute. -- `tsconfig.json` should output in `esnext` format. -- Imports must include the `.js` file extension. - -## Linting and types in TypeScript {#linting-and-types} - -If you started your project with `@temporalio/create`, you already have our recommended TypeScript and ESLint configurations. - -If you incrementally added Temporal to an existing app, we do recommend setting up linting and types because they help catch bugs well before you ship them to production, and they improve your development feedback loop. -Take a look at our recommended [.eslintrc](https://github.com/temporalio/samples-typescript/blob/main/.shared/.eslintrc.js) file and tweak to suit your needs. - -## How to connect a Temporal Client to a Temporal Service {#connect-to-a-dev-cluster} - -A [Temporal Client](/encyclopedia/temporal-sdks#temporal-client) enables you to communicate with the [Temporal Service](/temporal-service). -Communication with a Temporal Service includes, but isn't limited to, the following: - -- Starting Workflow Executions. -- Sending Signals to Workflow Executions. -- Sending Queries to Workflow Executions. -- Getting the results of a Workflow Execution. -- Providing an Activity Task Token. - -:::caution - -A Temporal Client cannot be initialized and used inside a Workflow. -However, it is acceptable and common to use a Temporal Client inside an Activity to communicate with a Temporal Service. - -::: - -When you are running a Temporal Service locally (such as the [Temporal CLI](https://docs.temporal.io/cli/server#start-dev)), the number of connection options you must provide is minimal. -Many SDKs default to the local host or IP address and port that Temporalite and [Docker Compose](https://github.com/temporalio/docker-compose) serve (`127.0.0.1:7233`). - -Creating a [Connection](https://typescript.temporal.io/api/classes/client.Connection) connects to the Temporal Service, and you can pass the `Connection` instance when creating the [Client](https://typescript.temporal.io/api/classes/client.Client#connection). - -If you omit the `Connection` and just create a `new Client()`, it will connect to `localhost:7233`. - -```ts -import { Client } from '@temporalio/client'; - -async function run() { - const client = new Client(); - - // . . . - - await client.connection.close(); -} - -run().catch((err) => { - console.error(err); - process.exit(1); -}); -``` - -## How to connect to Temporal Cloud {#connect-to-temporal-cloud} - -When you connect to [Temporal Cloud](/cloud), you need to provide additional connection and client options that include the following: - -- The [Temporal Cloud Namespace Id](/cloud/namespaces#temporal-cloud-namespace-id). -- The [Namespace's gRPC endpoint](/cloud/namespaces#temporal-cloud-grpc-endpoint). - An endpoint listing is available at the [Temporal Cloud Website](https://cloud.temporal.io/namespaces) on each Namespace detail page. - The endpoint contains the Namespace Id and port. -- mTLS CA certificate. -- mTLS private key. - -For more information about managing and generating client certificates for Temporal Cloud, see [How to manage certificates in Temporal Cloud](/cloud/certificates). - -For more information about configuring TLS to secure inter- and intra-network communication for a Temporal Service, see [Temporal Customization Samples](https://github.com/temporalio/samples-server). - -Create a [`Connection`](https://typescript.temporal.io/api/classes/client.Connection) with a [`connectionOptions`](https://typescript.temporal.io/api/interfaces/client.ConnectionOptions) object that has your Cloud namespace and client certificate. - -```ts -import { Client, Connection } from '@temporalio/client'; -import fs from 'fs-extra'; - -const { NODE_ENV = 'development' } = process.env; -const isDeployed = ['production', 'staging'].includes(NODE_ENV); - -async function run() { - const cert = await fs.readFile('./path-to/your.pem'); - const key = await fs.readFile('./path-to/your.key'); - - let connectionOptions = {}; - if (isDeployed) { - connectionOptions = { - address: 'your-namespace.tmprl.cloud:7233', - tls: { - clientCertPair: { - crt: cert, - key, - }, - }, - }; - - const connection = await Connection.connect(connectionOptions); - - const client = new Client({ - connection, - namespace: 'your-namespace', - }); - - // . . . - - await client.connection.close(); - } -} - -run().catch((err) => { - console.error(err); - process.exit(1); -}); -``` - -## How to develop a basic Workflow {#develop-workflows} - -Workflows are the fundamental unit of a Temporal Application, and it all starts with the development of a [Workflow Definition](/workflow-definition). - -In the Temporal TypeScript SDK programming model, Workflow Definitions are _just functions_, which can store state and orchestrate Activity Functions. -The following code snippet uses `proxyActivities` to schedule a `greet` Activity in the system to say hello. - -A Workflow Definition can have multiple parameters; however, we recommend using a single object parameter. - -```typescript -type ExampleArgs = { - name: string; -}; - -export async function example( - args: ExampleArgs, -): Promise<{ greeting: string }> { - const greeting = await greet(args.name); - return { greeting }; -} -``` - -### How to define Workflow parameters {#workflow-parameters} - -Temporal Workflows may have any number of custom parameters. -However, we strongly recommend that objects are used as parameters, so that the object's individual fields may be altered without breaking the signature of the Workflow. -All Workflow Definition parameters must be serializable. - -You can define and pass parameters in your Workflow. In this example, you define your arguments in your `client.ts` file and pass those parameters to `workflow.ts` through your Workflow function. - -Start a Workflow with the parameters that are in the `client.ts` file. In this example we set the `name` parameter to `Temporal` and `born` to `2019`. Then set the Task Queue and Workflow Id. - -`client.ts` - -```typescript -import { example } from './workflows'; - -... -await client.workflow.start(example, { - args: [{ name: 'Temporal', born: 2019 }], - taskQueue: 'your-queue', - workflowId: 'business-meaningful-id', -}); -``` - -In `workflows.ts` define the type of the parameter that the Workflow function takes in. The interface `ExampleParam` is a name we can now use to describe the requirement in the previous example. It still represents having the two properties called `name` and `born` that is of the type `string`. Then define a function that takes in a parameter of the type `ExampleParam` and return a `Promise`. The `Promise` object represents the eventual completion, or failure, of `await client.workflow.start()` and its resulting value. - -```ts -interface ExampleParam { - name: string; - born: number; -} -export async function example({ name, born }: ExampleParam): Promise { - return `Hello ${name}, you were born in ${born}.`; -} -``` - -### How to define Workflow return parameters {#workflow-return-values} - -Workflow return values must also be serializable. -Returning results, returning errors, or throwing exceptions is fairly idiomatic in each language that is supported. -However, Temporal APIs that must be used to get the result of a Workflow Execution will only ever receive one of either the result or the error. - -To return a value of the Workflow function, use `Promise`. The `Promise` is used to make asynchronous calls and comes with guarantees. - -The following example uses a `Promise` to eventually return a `name` and `born` parameter. - -```typescript -interface ExampleParam { - name: string; - born: number; -} -export async function example({ name, born }: ExampleParam): Promise { - return `Hello ${name}, you were born in ${born}.`; -} -``` - -### How to customize your Workflow Type {#workflow-type} - -Workflows have a Type that are referred to as the Workflow name. - -The following examples demonstrate how to set a custom name for your Workflow Type. - -In TypeScript, the Workflow Type is the Workflow function name and there isn't a mechanism to customize the Workflow Type. - -In the following example, the Workflow Type is the name of the function, `helloWorld`. - - -[snippets/src/workflows.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/workflows.ts) -```ts -export async function helloWorld(): Promise { - return '👋 Hello World!'; -} -``` - - -### How to develop Workflow logic {#workflow-logic-requirements} - -Workflow logic is constrained by [deterministic execution requirements](/workflow-definition#deterministic-constraints). -Therefore, each language is limited to the use of certain idiomatic techniques. -However, each Temporal SDK provides a set of APIs that can be used inside your Workflow to interact with external (to the Workflow) application code. - -In the Temporal TypeScript SDK, Workflows run in a deterministic sandboxed environment. -The code is bundled on Worker creation using Webpack, and 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](https://typescript.temporal.io/api/interfaces/worker.BundleOptions#ignoremodules) list. - -::: - -The Workflow sandbox can run only deterministic code, so side effects and access to external state must be done through Activities because Activity outputs are recorded in the Event History and can read deterministically by the Workflow. - -This limitation also means that Workflow code cannot directly import the [Activity Definition](/activity-definition). -[Activity Types](/activity-definition#activity-type) can be imported, so they can be invoked in a type-safe manner. - -To make the Workflow runtime deterministic, functions like `Math.random()`, `Date`, and `setTimeout()` are replaced by deterministic versions. - -[FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry) and [WeakRef](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef) are removed because v8's garbage collector is not deterministic. - -
- - - Expand to see the implications of the deterministic Date API - - -```typescript -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()); -} -``` - -
- -## How to develop a basic Activity {#develop-activities} - -One of the primary things that Workflows do is orchestrate the execution of Activities. -An Activity is a normal function or method execution that's intended to execute a single, well-defined action (either short or long-running), such as querying a database, calling a third-party API, or transcoding a media file. -An Activity can interact with world outside the Temporal Platform or use a Temporal Client to interact with a Temporal Service. -For the Workflow to be able to execute the Activity, we must define the [Activity Definition](/activity-definition). - -- Activities execute in the standard Node.js environment. -- Activities cannot be in the same file as Workflows and must be separately registered. -- Activities may be retried repeatedly, so you may need to use idempotency keys for critical side effects. - -Activities are _just functions_. The following is an Activity that accepts a string parameter and returns a string. - - -[snippets/src/activities.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/activities.ts) -```ts -export async function greet(name: string): Promise { - return `👋 Hello, ${name}!`; -} -``` - - -### How to develop Activity Parameters {#activity-parameters} - -There is no explicit limit to the total number of parameters that an [Activity Definition](/activity-definition) may support. -However, there is a limit to the total size of the data that ends up encoded into a gRPC message Payload. - -A single argument is limited to a maximum size of 2 MB. -And the total size of a gRPC message, which includes all the arguments, is limited to a maximum of 4 MB. - -Also, keep in mind that all Payload data is recorded in the [Workflow Execution Event History](/workflow-execution/event#event-history) and large Event Histories can affect Worker performance. -This is because the entire Event History could be transferred to a Worker Process with a [Workflow Task](/tasks#workflow-task). - -{/* TODO link to gRPC limit section when available */} - -Some SDKs require that you pass context objects, others do not. -When it comes to your application data—that is, data that is serialized and encoded into a Payload—we recommend that you use a single object as an argument that wraps the application data passed to Activities. -This is so that you can change what data is passed to the Activity without breaking a function or method signature. - -This Activity takes a single `name` parameter of type `string`. - - -[snippets/src/activities.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/activities.ts) -```ts -export async function greet(name: string): Promise { - return `👋 Hello, ${name}!`; -} -``` - - -### How to define Activity return values {#activity-return-values} - -All data returned from an Activity must be serializable. - -Activity return values are subject to payload size limits in Temporal. The default payload size limit is 2MB, and there is a hard limit of 4MB for any gRPC message size in the Event History transaction ([see Cloud limits here](https://docs.temporal.io/cloud/limits#per-message-grpc-limit)). Keep in mind that all return values are recorded in a [Workflow Execution Event History](/workflow-execution/event#event-history). - -In TypeScript, the return value is always a Promise. - -In the following example, `Promise` is the return value. - -```typescript -export async function greet(name: string): Promise { - return `👋 Hello, ${name}!`; -} -``` - -### How to customize your Activity Type {#activity-type} - -Activities have a Type that are referred to as the Activity name. -The following examples demonstrate how to set a custom name for your Activity Type. - -You can customize the name of the Activity when you register it with the Worker. -In the following example, the Activity Name is `activityFoo`. - - -[snippets/src/worker-activity-type-custom.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/worker-activity-type-custom.ts) -```ts -import { Worker } from '@temporalio/worker'; -import { greet } from './activities'; - -async function run() { - const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), - taskQueue: 'snippets', - activities: { - activityFoo: greet, - }, - }); - - await worker.run(); -} -``` - - -### Important design patterns for Activities {#activity-design-patterns} - -The following are some important (and frequently requested) patterns for using our Activities APIs. -These patterns address common needs and use cases. - -#### Share dependencies in Activity functions (dependency injection) - -Because Activities are "just functions," you can also create functions that create Activities. -This is a helpful pattern for using closures to do the following: - -- Store expensive dependencies for sharing, such as database connections. -- Inject secret keys (such as environment variables) from the Worker to the Activity. - - -[activities-dependency-injection/src/activities.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-dependency-injection/src/activities.ts) -```ts -export interface DB { - get(key: string): Promise; -} - -export const createActivities = (db: DB) => ({ - async greet(msg: string): Promise { - const name = await db.get('name'); // simulate read from db - return `${msg}: ${name}`; - }, - async greet_es(mensaje: string): Promise { - const name = await db.get('name'); // simulate read from db - return `${mensaje}: ${name}`; - }, -}); -``` - - -
- See full example - -When you register these in the Worker, pass your shared dependencies accordingly: - - -```ts -import { createActivities } from './activities'; - -async function run() { - // Mock DB connection initialization in Worker - const db = { - async get(_key: string) { - return 'Temporal'; - }, - }; - - const worker = await Worker.create({ - taskQueue: 'dependency-injection', - workflowsPath: require.resolve('./workflows'), - activities: createActivities(db), - }); - - await worker.run(); -} - -run().catch((err) => { - console.error(err); - process.exit(1); -}); -``` - - -Because Activities are always referenced by name, inside the Workflow they can be proxied as normal, although the types need some adjustment: - - -[activities-dependency-injection/src/workflows.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-dependency-injection/src/workflows.ts) -```ts -import type { createActivities } from './activities'; - -// Note usage of ReturnType<> generic since createActivities is a factory function -const { greet, greet_es } = proxyActivities>({ - startToCloseTimeout: '30 seconds', -}); -``` - - -
- -#### Import multiple Activities simultaneously - -You can proxy multiple Activities from the same `proxyActivities` call if you want them to share the same timeouts, retries, and options: - -```ts -export async function Workflow(name: string): Promise { - // destructuring multiple activities with the same options - const { act1, act2, act3 } = proxyActivities(); - /* activityOptions */ - await act1(); - await Promise.all([act2, act3]); -} -``` - -#### Dynamically reference Activities - -Because Activities are referenced only by their string names, you can reference them dynamically if needed: - -```js -export async function DynamicWorkflow(activityName, ...args) { - const acts = proxyActivities(/* activityOptions */); - - // these are equivalent - await acts.activity1(); - await acts['activity1'](); - - // dynamic reference to activities using activityName - let result = await acts[activityName](...args); -} -``` - -Type safety is still supported here, but we encourage you to validate and handle mismatches in Activity names. -An invalid Activity name leads to a `NotFoundError` with a message that looks like this: - -``` -ApplicationFailure: Activity function actC is not registered on this Worker, available activities: ["actA", "actB"] -``` - -## How to start an Activity Execution {#activity-execution} - -Calls to spawn [Activity Executions](/activity-execution) are written within a [Workflow Definition](/workflow-definition). -In TypeScript, you never call an Activity function directly. Instead, you pass in the _types_ of your Activities and Activity options to the `proxyActivities` function. This will give you an _Activity Handle_, a type-safe proxy object with the same function names and signatures as your real activities. From the Activity Handle, you can call your Activities as if they were normal async functions. - -```typescript -import { proxyActivities } from '@temporalio/workflow'; -// Only import the activity types, not the functions themselves -import type * as activities from './activities'; - -// Retrieve the Activity Handle by passing in the Activity types and options -const activityHandle = proxyActivities({ - startToCloseTimeout: '1 minute', -}); - -// Deconstruct the individual Activity functions from the Activity Handle -const { greet } = activityHandle; - -// A workflow that calls an activity -export async function example(name: string): Promise { - return await greet(name); -} -``` - -When you call a proxied function, the Workflow does not execute the Activity code directly. Instead, it schedules an Activity Task. After the Activity Task is scheduled, it becomes available for a Worker to pick up and execute. -This results in the set of three [Activity Task](/tasks#activity-task) related Events: [ActivityTaskScheduled](/references/events#activitytaskscheduled), [ActivityTaskStarted](/references/events#activitytaskstarted), and [ActivityTaskCompleted](/references/events#activitytaskcompleted) in your Workflow Execution Event History. - -The Worker may run many Activity executions at the same time, all using the same Activity function code. Temporal can also retry an Activity if it fails or times out. -For this reason, you should write Activities to be [idempotent](../../encyclopedia/activities/activity-definition.mdx#idempotency): calling them multiple times with the same input should have the same effect as calling them once. - -:::tip -Every Activity call you make is recorded in the Workflow’s execution history, including the parameters you pass in and the value that comes back. This history is what allows Temporal to recover a Workflow after a failure. -Because the entire history must be stored and replayed, avoid passing large objects as Activity inputs or return values. Keeping payloads small will help your Workflows replay and recover efficiently. -::: - -### How to set the required Activity Timeouts {#required-timeout} - -Activity Execution semantics rely on several parameters. -The only required value that needs to be set is either a [Schedule-To-Close Timeout](/encyclopedia/detecting-activity-failures#schedule-to-close-timeout) or a [Start-To-Close Timeout](/encyclopedia/detecting-activity-failures#start-to-close-timeout). -These values are set in the Activity Options. - -### How to get the results of an Activity Execution {#get-activity-results} - -The call to spawn an [Activity Execution](/activity-execution) generates the [ScheduleActivityTask](/references/commands#scheduleactivitytask) Command and provides the Workflow with an Awaitable. -Workflow Executions can either block progress until the result is available through the Awaitable or continue progressing, making use of the result when it becomes available. - -Since Activities are referenced by their string name, you can reference them dynamically to get the result of an Activity Execution. - -```typescript -export async function DynamicWorkflow(activityName, ...args) { - const acts = proxyActivities(/* activityOptions */); - - // these are equivalent - await acts.activity1(); - await acts['activity1'](); - - let result = await acts[activityName](...args); - return result; -} -``` - -The `proxyActivities()` returns an object that calls the Activities in the function. `acts[activityName]()` references the Activity using the Activity name, then it returns the results. - -## How to run Worker Processes {#run-a-dev-worker} - -The [Worker Process](/workers#worker-process) is where Workflow Functions and Activity Functions are executed. - -- Each [Worker Entity](/workers#worker-entity) in the Worker Process must register the exact Workflow Types and Activity Types it may execute. -- Each Worker Entity must also associate itself with exactly one [Task Queue](/task-queue). -- Each Worker Entity polling the same Task Queue must be registered with the same Workflow Types and Activity Types. - -A [Worker Entity](/workers#worker-entity) is the component within a Worker Process that listens to a specific Task Queue. - -Although multiple Worker Entities can be in a single Worker Process, a single Worker Entity Worker Process may be perfectly sufficient. -For more information, see the [Worker tuning guide](/develop/worker-performance). - -A Worker Entity contains a Workflow Worker and/or an Activity Worker, which makes progress on Workflow Executions and Activity Executions, respectively. - -## How to run a Worker on Docker in TypeScript {#run-a-worker-on-docker} - -:::note - -To improve worker startup time, we recommend preparing workflow bundles ahead-of-time. See our [productionsample](https://github.com/temporalio/samples-typescript/tree/main/production) for details. - -::: - -Workers based on the TypeScript SDK can be deployed and run as Docker containers. - -We recommend an LTS Node.js release such as 18, 20, 22, or 24. -Both `amd64` and `arm64` architectures are supported. -A glibc-based image is required; musl-based images are _not_ supported (see below). - -The easiest way to deploy a TypeScript SDK Worker on Docker is to start with the `node:20-bullseye` image. -For example: - -```dockerfile -FROM node:20-bullseye - -# For better cache utilization, copy package.json and lock file first and install the dependencies before copying the -# rest of the application and building. -COPY . /app -WORKDIR /app - -# Alternatively, run npm ci, which installs only dependencies specified in the lock file and is generally faster. -RUN npm install --only=production \ - && npm run build - -CMD ["npm", "start"] -``` - -For smaller images and/or more secure deployments, it is also possible to use `-slim` Docker image variants (like `node:20-bullseye-slim`) or `distroless/nodejs` Docker images (like `gcr.io/distroless/nodejs20-debian11`) with the following caveats. - -### Using `node:slim` images - -`node:slim` images do not contain some of the common packages found in regular images. This results in significantly smaller images. - -However, TypeScript SDK requires the presence of root TLS certificates (the `ca-certificates` package), which are not included in `slim` images. -The `ca-certificates` package is required even when connecting to a local Temporal Server or when using a server connection config that doesn't explicitly use TLS. - -For this reason, the `ca-certificates` package must be installed during the construction of the Docker image. -For example: - -```dockerfile -FROM node:20-bullseye-slim - -RUN apt-get update \ - && apt-get install -y ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -# ... same as with regular image -``` - -Failure to install this dependency results in a `[TransportError: transport error]` runtime error, because the certificates cannot be verified. - -### Using `distroless/nodejs` images - -`distroless/nodejs` images include only the files that are strictly required to execute `node`. -This results in even smaller images (approximately half the size of `node:slim` images). -It also significantly reduces the surface of potential security issues that could be exploited by a hacker in the resulting Docker images. - -It is generally possible and safe to execute TypeScript SDK Workers using `distroless/nodejs` images (unless your code itself requires dependencies that are not included in `distroless/nodejs`). - -However, some tools required for the build process (notably the `npm` command) are _not_ included in the `distroless/nodejs` image. -This might result in various error messages during the Docker build. - -The recommended solution is to use a multi-step Dockerfile. -For example: - -```dockerfile -# -- BUILD STEP -- - -FROM node:20-bullseye AS builder - -COPY . /app -WORKDIR /app - -RUN npm install --only=production \ - && npm run build - -# -- RESULTING IMAGE -- - -FROM gcr.io/distroless/nodejs20-debian11 - -COPY --from=builder /app /app -WORKDIR /app - -CMD ["node", "build/worker.js"] -``` - -### Properly configure Node.js memory in Docker - -By default, `node` configures its maximum old-gen memory to 25% of the _physical memory_ of the machine on which it is executing, with a maximum of 4 GB. -This is likely inappropriate when running Node.js in a Docker environment and can result in either underusage of available memory (`node` only uses a fraction of the memory allocated to the container) or overusage (`node` tries to use more memory than what is allocated to the container, which will eventually lead to the process being killed by the operating system). - -Therefore we recommended that you always explicitly set the `--max-old-space-size` `node` argument to approximately 80% of the maximum size (in megabytes) that you want to allocate the `node` process. -You might need some experimentation and adjustment to find the most appropriate value based on your specific application. - -In practice, it is generally easier to provide this argument through the [`NODE_OPTIONS` environment variable](https://nodejs.org/api/cli.html#node_optionsoptions). - -### Do not use Alpine - -Alpine replaces glibc with musl, which is incompatible with the Rust core of the TypeScript SDK. -If you receive errors like the following, it's probably because you are using Alpine. - -```sh -Error: Error loading shared library ld-linux-x86-64.so.2: No such file or directory (needed by /opt/app/node_modules/@temporalio/core-bridge/index.node) -``` - -Or like this: - -```sh -Error: Error relocating /opt/app/node_modules/@temporalio/core-bridge/index.node: __register_atfork: symbol not found -``` - -## How to run a Temporal Cloud Worker {#run-a-temporal-cloud-worker} - -To run a Worker that uses [Temporal Cloud](/cloud), you need to provide additional connection and client options that include the following: - -- An address that includes your [Cloud Namespace Name](/namespaces) and a port number: `..tmprl.cloud:`. -- mTLS CA certificate. -- mTLS private key. - -For more information about managing and generating client certificates for Temporal Cloud, see [How to manage certificates in Temporal Cloud](/cloud/certificates). - -For more information about configuring TLS to secure inter- and intra-network communication for a Temporal Service, see [Temporal Customization Samples](https://github.com/temporalio/samples-server). - -### How to register types {#register-types} - -All Workers listening to the same Task Queue name must be registered to handle the exact same Workflows Types and Activity Types. - -If a Worker polls a Task for a Workflow Type or Activity Type it does not know about, it fails that Task. -However, the failure of the Task does not cause the associated Workflow Execution to fail. - -In development, use [`workflowsPath`](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions/#workflowspath): - - -[snippets/src/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/worker.ts) -```ts -import { Worker } from '@temporalio/worker'; -import * as activities from './activities'; - -async function run() { - const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), - taskQueue: 'snippets', - activities, - }); - - await worker.run(); -} -``` - - -In this snippet, the Worker bundles the Workflow code at runtime. - -In production, you can improve your Worker's startup time by bundling in advance: as part of your production build, call `bundleWorkflowCode`: - - -[production/src/scripts/build-workflow-bundle.ts](https://github.com/temporalio/samples-typescript/blob/main/production/src/scripts/build-workflow-bundle.ts) -```ts -import { bundleWorkflowCode } from '@temporalio/worker'; -import { writeFile } from 'fs/promises'; -import path from 'path'; - -async function bundle() { - const { code } = await bundleWorkflowCode({ - workflowsPath: require.resolve('../workflows'), - }); - const codePath = path.join(__dirname, '../../workflow-bundle.js'); - - await writeFile(codePath, code); - console.log(`Bundle written to ${codePath}`); -} -``` - - -Then the bundle can be passed to the Worker: - - -[production/src/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/production/src/worker.ts) -```ts -const workflowOption = () => - process.env.NODE_ENV === 'production' - ? { - workflowBundle: { - codePath: require.resolve('../workflow-bundle.js'), - }, - } - : { workflowsPath: require.resolve('./workflows') }; - -async function run() { - const worker = await Worker.create({ - ...workflowOption(), - activities, - taskQueue: 'production-sample', - }); - - await worker.run(); -} -``` - - -## How to shut down a Worker and track its state {#shut-down-a-worker} - -Workers shut down if they receive any of the Signals enumerated in [shutdownSignals](https://typescript.temporal.io/api/interfaces/worker.RuntimeOptions#shutdownsignals): `'SIGINT'`, `'SIGTERM'`, `'SIGQUIT'`, and `'SIGUSR2'`. - -In development, we shut down Workers with `Ctrl+C` (`SIGINT`) or [nodemon](https://github.com/temporalio/samples-typescript/blob/c37bae3ea235d1b6956fcbe805478aa46af973ce/hello-world/package.json#L10) (`SIGUSR2`). In production, you usually want to give Workers time to finish any in-progress Activities by setting [shutdownGraceTime](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#shutdowngracetime). - -As soon as a Worker receives a shutdown Signal or request, the Worker stops polling for new Tasks and allows in-flight Tasks to complete until `shutdownGraceTime` is reached. -Any Activities that are still running at that time will stop running and will be rescheduled by Temporal Server when an Activity timeout occurs. - -If you must guarantee that the Worker eventually shuts down, you can set [shutdownForceTime](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#shutdownforcetime). - -You might want to programmatically shut down Workers (with [Worker.shutdown()](https://typescript.temporal.io/api/classes/worker.Worker#shutdown)) in integration tests or when automating a fleet of Workers. - -### Worker states - -At any time, you can Query Worker state with [Worker.getState()](https://typescript.temporal.io/api/classes/worker.Worker#getstate). -A Worker is always in one of seven states: - -- `INITIALIZED`: The initial state of the Worker after calling [Worker.create()](https://typescript.temporal.io/api/classes/worker.Worker#create) and successfully connecting to the server. -- `RUNNING`: [Worker.run()](https://typescript.temporal.io/api/classes/worker.Worker#run) was called and the Worker is polling Task Queues. -- `FAILED`: The Worker encountered an unrecoverable error; `Worker.run()` should reject with the error. -- The last four states are related to the Worker shutdown process: - - `STOPPING`: The Worker received a shutdown Signal or `Worker.shutdown()` was called. - The Worker will forcefully shut down after `shutdownGraceTime` expires. - - `DRAINING`: All Workflow Tasks have been drained; waiting for Activities and cached Workflows eviction. - - `DRAINED`: All Activities and Workflows have completed; ready to shut down. - - `STOPPED`: Shutdown complete; `worker.run()` resolves. - -If you need more visibility into internal Worker state, see the [Worker class](https://typescript.temporal.io/api/classes/worker.Worker) in the API reference. - -## How to start a Workflow Execution {#start-workflow-execution} - -[Workflow Execution](/workflow-execution) semantics rely on several parameters—that is, to start a Workflow Execution you must supply a Task Queue that will be used for the Tasks (one that a Worker is polling), the Workflow Type, language-specific contextual data, and Workflow Function parameters. - -In the examples below, all Workflow Executions are started using a Temporal Client. -To spawn Workflow Executions from within another Workflow Execution, use either the [Child Workflow](/develop/typescript/child-workflows) or External Workflow APIs. - -See the [Customize Workflow Type](#workflow-type) section to see how to customize the name of the Workflow Type. - -A request to spawn a Workflow Execution causes the Temporal Service to create the first Event ([WorkflowExecutionStarted](/references/events#workflowexecutionstarted)) in the Workflow Execution Event History. -The Temporal Service then creates the first Workflow Task, resulting in the first [WorkflowTaskScheduled](/references/events#workflowtaskscheduled) Event. - -When you have a Client, you can schedule the start of a Workflow with `client.workflow.start()`, specifying `workflowId`, `taskQueue`, and `args` and returning a Workflow handle immediately after the Server acknowledges the receipt. - -```typescript -const handle = await client.workflow.start(example, { - workflowId: 'your-workflow-id', - taskQueue: 'your-task-queue', - args: ['argument01', 'argument02', 'argument03'], // this is typechecked against workflowFn's args -}); -const handle = client.getHandle(workflowId); -const result = await handle.result(); -``` - -Calling `client.workflow.start()` and `client.workflow.execute()` send a command to Temporal Server to schedule a new Workflow Execution on the specified Task Queue. It does not actually start until a Worker that has a matching Workflow Type, polling that Task Queue, picks it up. - -You can test this by executing a Client command without a matching Worker. Temporal Server records the command in Event History, but does not make progress with the Workflow Execution until a Worker starts polling with a matching Task Queue and Workflow Definition. - -Workflow Execution run in a separate V8 isolate context in order to provide a [deterministic runtime](/workflow-definition#deterministic-constraints). - -### How to set a Workflow's Task Queue {#set-task-queue} - -In most SDKs, the only Workflow Option that must be set is the name of the [Task Queue](/task-queue). - -For any code to execute, a Worker Process must be running that contains a Worker Entity that is polling the same Task Queue name. - -A Task Queue is a dynamic queue in Temporal polled by one or more Workers. - -Workers bundle Workflow code and node modules using Webpack v5 and execute them inside V8 isolates. -Activities are directly required and run by Workers in the Node.js environment. - -Workers are flexible. -You can host any or all of your Workflows and Activities on a Worker, and you can host multiple Workers on a single machine. - -The Worker need three main things: - -- `taskQueue`: The Task Queue to poll. This is the only required argument. -- `activities`: Optional. Imported and supplied directly to the Worker. -- Workflow bundle. Choose one of the following options: - - Specify `workflowsPath` pointing to your `workflows.ts` file to pass to Webpack; for example, `require.resolve('./workflows')`. - Workflows are bundled with their dependencies. - - If you prefer to handle the bundling yourself, pass a prebuilt bundle to `workflowBundle`. - -```ts -import { Worker } from '@temporalio/worker'; -import * as activities from './activities'; - -async function run() { - // Step 1: Register Workflows and Activities with the Worker and connect to - // the Temporal server. - const worker = await Worker.create({ - workflowsPath: require.resolve('./workflows'), - activities, - taskQueue: 'hello-world', - }); - // Worker connects to localhost by default and uses console.error for logging. - // Customize the Worker by passing more options to create(): - // https://typescript.temporal.io/api/classes/worker.Worker - // If you need to configure server connection parameters, see docs: - // /typescript/security#encryption-in-transit-with-mtls - - // Step 2: Start accepting tasks on the `tutorial` queue - await worker.run(); -} - -run().catch((err) => { - console.error(err); - process.exit(1); -}); -``` - -`taskQueue` is the only required option; however, use `workflowsPath` and `activities` to register Workflows and Activities with the Worker. - -When scheduling a Workflow, you must specify `taskQueue`. - -```ts -import { Client, Connection } from '@temporalio/client'; -// This is the code that is used to start a Workflow. -const connection = await Connection.create(); -const client = new Client({ connection }); -const result = await client.workflow.execute(yourWorkflow, { - // required - taskQueue: 'your-task-queue', - // required - workflowId: 'your-workflow-id', -}); -``` - -When creating a Worker, you must pass the `taskQueue` option to the `Worker.create()` function. - -```ts -const worker = await Worker.create({ - // imported elsewhere - activities, - taskQueue: 'your-task-queue', -}); -``` - -Optionally, in Workflow code, when calling an Activity, you can specify the Task Queue by passing the `taskQueue` option to `proxyActivities()`, `startChild()`, or `executeChild()`. -If you do not specify `taskQueue`, the TypeScript SDK places Activity and Child Workflow Tasks in the same Task Queue as the Workflow Task Queue. - -### How to set a Workflow Id {#workflow-id} - -Although it is not required, we recommend providing your own [Workflow Id](/workflow-execution/workflowid-runid#workflow-id)that maps to a business process or business entity identifier, such as an order identifier or customer identifier. - -Connect to a Client with `client.workflow.start()` and any arguments. Then specify your `taskQueue` and set your `workflowId` to a meaningful business identifier. - -```typescript -const handle = await client.workflow.start(example, { - workflowId: 'yourWorkflowId', - taskQueue: 'yourTaskQueue', - args: ['your', 'arg', 'uments'], -}); -``` - -This starts a new Client with the given Workflow Id, Task Queue name, and an argument. - -### How to get the results of a Workflow Execution {#get-workflow-results} - -If the call to start a Workflow Execution is successful, you will gain access to the Workflow Execution's Run Id. - -The Workflow Id, Run Id, and Namespace may be used to uniquely identify a Workflow Execution in the system and get its result. - -It's possible to both block progress on the result (synchronous execution) or get the result at some other point in time (asynchronous execution). - -In the Temporal Platform, it's also acceptable to use Queries as the preferred method for accessing the state and results of Workflow Executions. - -To return the results of a Workflow Execution: - -```typescript -return ( - 'Completed ' - + wf.workflowInfo().workflowId - + ', Total Charged: ' - + totalCharged -); -``` - -`totalCharged` is just a function declared in your code. For a full example, see [subscription-workflow-project-template-typescript/src/workflows.ts](https://github.com/temporalio/subscription-workflow-project-template-typescript/blob/main/src/workflows.ts). - -A Workflow function may return a result. If it doesn't (in which case the return type is `Promise`), the result will be `undefined`. - -If you started a Workflow with `client.workflow.start()`, you can choose to wait for the result anytime with `handle.result()`. - -```typescript -const handle = client.getHandle(workflowId); -const result = await handle.result(); -``` - -Using a Workflow Handle isn't necessary with `client.workflow.execute()`. - -Workflows that prematurely end will throw a `WorkflowFailedError` if you call `result()`. - -If you call `result()` on a Workflow that prematurely ended for some reason, it throws a [`WorkflowFailedError` error](https://typescript.temporal.io/api/classes/client.WorkflowFailedError/) that reflects the reason. For that reason, it is recommended to catch that error. - -```typescript -const handle = client.getHandle(workflowId); -try { - const result = await handle.result(); -} catch (err) { - if (err instanceof WorkflowFailedError) { - throw new Error('Temporal workflow failed: ' + workflowId, { - cause: err, - }); - } else { - throw new Error('error from Temporal workflow ' + workflowId, { - cause: err, - }); - } -} -``` - -## Cancellation scopes in Typescript {#cancellation-scopes} - -In the TypeScript SDK, Workflows are represented internally by a tree of cancellation scopes, each with cancellation behaviors you can specify. -By default, everything runs in the "root" scope. - -Scopes are created using the [CancellationScope](https://typescript.temporal.io/api/classes/workflow.CancellationScope) constructor or one of three static helpers: - -- [cancellable(fn)](https://typescript.temporal.io/api/classes/workflow.CancellationScope#cancellable-1): Children are automatically cancelled when their containing scope is cancelled. - - Equivalent to `new CancellationScope().run(fn)`. -- [nonCancellable(fn)](https://typescript.temporal.io/api/classes/workflow.CancellationScope#noncancellable): Cancellation does not propagate to children. - - Equivalent to `new CancellationScope({ cancellable: false }).run(fn)`. -- [withTimeout(timeoutMs, fn)](https://typescript.temporal.io/api/classes/workflow.CancellationScope#withtimeout): If a timeout triggers before `fn` resolves, the scope is cancelled, triggering cancellation of any enclosed operations, such as Activities and Timers. - - Equivalent to `new CancellationScope({ cancellable: true, timeout: timeoutMs }).run(fn)`. - -Cancellations are applied to cancellation scopes, which can encompass an entire Workflow or just part of one. -Scopes can be nested, and cancellation propagates from outer scopes to inner ones. -A Workflow's `main` function runs in the outermost scope. -Cancellations are handled by catching `CancelledFailure`s thrown by cancelable operations. - -`CancellationScope.run()` and the static helpers mentioned earlier return native JavaScript promises, so you can use the familiar Promise APIs like `Promise.all` and `Promise.race` to model your asynchronous logic. -You can also use the following APIs: - -- `CancellationScope.current()`: Get the current scope. -- `scope.cancel()`: Cancel all operations inside a `scope`. -- `scope.run(fn)`: Run an async function within a `scope` and return the result of `fn`. -- `scope.cancelRequested`: A promise that resolves when a scope cancellation is requested, such as when Workflow code calls `cancel()` or the entire Workflow is cancelled by an external client. - -When a `CancellationScope` is cancelled, it propagates cancellation in any child scopes and of any cancelable operations created within it, such as the following: - -- Activities -- Timers (created with the [sleep](https://typescript.temporal.io/api/namespaces/workflow#sleep) function) -- [Triggers](https://typescript.temporal.io/api/classes/workflow.Trigger) - -### CancelledFailure - -Timers and triggers throw [CancelledFailure](https://typescript.temporal.io/api/classes/common.CancelledFailure) when cancelled; Activities and Child Workflows throw `ActivityFailure` and `ChildWorkflowFailure` with cause set to `CancelledFailure`. -One exception is when an Activity or Child Workflow is scheduled in an already cancelled scope (or Workflow). -In this case, they propagate the `CancelledFailure` that was thrown to cancel the scope. - -To simplify checking for cancellation, use the [isCancellation(err)](https://typescript.temporal.io/api/namespaces/workflow#iscancellation) function. - -### Internal cancellation example - - -[packages/test/src/workflows/cancel-timer-immediately.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/cancel-timer-immediately.ts) -```ts -import { CancelledFailure, CancellationScope, sleep } from '@temporalio/workflow'; - -export async function cancelTimer(): Promise { - // Timers and Activities are automatically cancelled when their containing scope is cancelled. - try { - await CancellationScope.cancellable(async () => { - const promise = sleep(1); // <-- Will be cancelled because it is attached to this closure's scope - CancellationScope.current().cancel(); - await promise; // <-- Promise must be awaited in order for `cancellable` to throw - }); - } catch (e) { - if (e instanceof CancelledFailure) { - console.log('Timer cancelled 👍'); - } else { - throw e; // <-- Fail the workflow - } - } -} -``` - - -Alternatively, the preceding can be written as the following. - - -[packages/test/src/workflows/cancel-timer-immediately-alternative-impl.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/cancel-timer-immediately-alternative-impl.ts) -```ts -import { CancelledFailure, CancellationScope, sleep } from '@temporalio/workflow'; - -export async function cancelTimerAltImpl(): Promise { - try { - const scope = new CancellationScope(); - const promise = scope.run(() => sleep(1)); - scope.cancel(); // <-- Cancel the timer created in scope - await promise; // <-- Throws CancelledFailure - } catch (e) { - if (e instanceof CancelledFailure) { - console.log('Timer cancelled 👍'); - } else { - throw e; // <-- Fail the workflow - } - } -} -``` - - -### External cancellation example - -The following code shows how to handle Workflow cancellation by an external client while an Activity is running. - -{/* TODO: add a sample here of how this Workflow could be cancelled using a WorkflowHandle */} - - -[packages/test/src/workflows/handle-external-workflow-cancellation-while-activity-running.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/handle-external-workflow-cancellation-while-activity-running.ts) -```ts -import { CancellationScope, proxyActivities, isCancellation } from '@temporalio/workflow'; -import type * as activities from '../activities'; - -const { httpPostJSON, cleanup } = proxyActivities({ - startToCloseTimeout: '10m', -}); - -export async function handleExternalWorkflowCancellationWhileActivityRunning(url: string, data: any): Promise { - try { - await httpPostJSON(url, data); - } catch (err) { - if (isCancellation(err)) { - console.log('Workflow cancelled'); - // Cleanup logic must be in a nonCancellable scope - // If we'd run cleanup outside of a nonCancellable scope it would've been cancelled - // before being started because the Workflow's root scope is cancelled. - await CancellationScope.nonCancellable(() => cleanup(url)); - } - throw err; // <-- Fail the Workflow - } -} -``` - - -### nonCancellable example - -`CancellationScope.nonCancellable` prevents cancellation from propagating to children. - - -[activities-cancellation-heartbeating/src/cancellation-scopes.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-cancellation-heartbeating/src/cancellation-scopes.ts) -```ts -export async function nonCancellable(url: string): Promise { - // Prevent Activity from being cancelled and await completion. - // Note that the Workflow is completely oblivious and impervious to cancellation in this example. - return CancellationScope.nonCancellable(() => httpGetJSON(url)); -} -``` - - -### withTimeout example - -A common operation is to cancel one or more Activities if a deadline elapses. -`withTimeout` creates a `CancellationScope` that is automatically cancelled after a timeout. - - -[packages/test/src/workflows/multiple-activities-single-timeout.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/multiple-activities-single-timeout.ts) -```ts -import { CancellationScope, proxyActivities } from '@temporalio/workflow'; -import type * as activities from '../activities'; - -export function multipleActivitiesSingleTimeout(urls: string[], timeoutMs: number): Promise { - const { httpGetJSON } = proxyActivities({ - startToCloseTimeout: timeoutMs, - }); - - // If timeout triggers before all activities complete - // the Workflow will fail with a CancelledError. - return CancellationScope.withTimeout(timeoutMs, () => Promise.all(urls.map((url) => httpGetJSON(url)))); -} -``` - - -### scope.cancelRequested - -You can await `cancelRequested` to make a Workflow aware of cancellation while waiting on `nonCancellable` scopes. - - -[packages/test/src/workflows/cancel-requested-with-non-cancellable.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/cancel-requested-with-non-cancellable.ts) -```ts -import { CancellationScope, CancelledFailure, proxyActivities } from '@temporalio/workflow'; -import type * as activities from '../activities'; - -const { httpGetJSON } = proxyActivities({ - startToCloseTimeout: '10m', -}); - -export async function resumeAfterCancellation(url: string): Promise { - let result: any = undefined; - const scope = new CancellationScope({ cancellable: false }); - const promise = scope.run(() => httpGetJSON(url)); - try { - result = await Promise.race([scope.cancelRequested, promise]); - } catch (err) { - if (!(err instanceof CancelledFailure)) { - throw err; - } - // Prevent Workflow from completing so Activity can complete - result = await promise; - } - return result; -} -``` - - -### Cancellation scopes and callbacks - -Callbacks are not particularly useful in Workflows because all meaningful asynchronous operations return promises. -In the rare case that code uses callbacks and needs to handle cancellation, a callback can consume the `CancellationScope.cancelRequested` promise. - - -[packages/test/src/workflows/cancellation-scopes-with-callbacks.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/cancellation-scopes-with-callbacks.ts) -```ts -import { CancellationScope } from '@temporalio/workflow'; - -function doSomething(callback: () => any) { - setTimeout(callback, 10); -} - -export async function cancellationScopesWithCallbacks(): Promise { - await new Promise((resolve, reject) => { - doSomething(resolve); - CancellationScope.current().cancelRequested.catch(reject); - }); -} -``` - - -### Nesting cancellation scopes - -You can achieve complex flows by nesting cancellation scopes. - - -[packages/test/src/workflows/nested-cancellation.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/nested-cancellation.ts) -```ts -import { CancellationScope, proxyActivities, isCancellation } from '@temporalio/workflow'; - -import type * as activities from '../activities'; - -const { setup, httpPostJSON, cleanup } = proxyActivities({ - startToCloseTimeout: '10m', -}); - -export async function nestedCancellation(url: string): Promise { - await CancellationScope.cancellable(async () => { - await CancellationScope.nonCancellable(() => setup()); - try { - await CancellationScope.withTimeout(1000, () => httpPostJSON(url, { some: 'data' })); - } catch (err) { - if (isCancellation(err)) { - await CancellationScope.nonCancellable(() => cleanup(url)); - } - throw err; - } - }); -} -``` - - -### Sharing promises between scopes - -Operations like Timers and Activities are cancelled by the cancellation scope they were created in. -Promises returned by these operations can be awaited in different scopes. - - -[activities-cancellation-heartbeating/src/cancellation-scopes.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-cancellation-heartbeating/src/cancellation-scopes.ts) -```ts -export async function sharedScopes(): Promise { - // Start activities in the root scope - const p1 = httpGetJSON('http://url1.ninja'); - const p2 = httpGetJSON('http://url2.ninja'); - - const scopePromise = CancellationScope.cancellable(async () => { - const first = await Promise.race([p1, p2]); - // Does not cancel activity1 or activity2 as they're linked to the root scope - CancellationScope.current().cancel(); - return first; - }); - return await scopePromise; - // The Activity that did not complete will effectively be cancelled when - // Workflow completes unless the Activity is awaited: - // await Promise.all([p1, p2]); -} -``` - - - -[activities-cancellation-heartbeating/src/cancellation-scopes.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-cancellation-heartbeating/src/cancellation-scopes.ts) -```ts -export async function shieldAwaitedInRootScope(): Promise { - let p: Promise | undefined = undefined; - - await CancellationScope.nonCancellable(async () => { - p = httpGetJSON('http://example.com'); // <-- Start activity in nonCancellable scope without awaiting completion - }); - // Activity is shielded from cancellation even though it is awaited in the cancellable root scope - return p; -} -``` - diff --git a/docs/develop/typescript/index.mdx b/docs/develop/typescript/index.mdx index 3a1ff4b647..ba7a60b5aa 100644 --- a/docs/develop/typescript/index.mdx +++ b/docs/develop/typescript/index.mdx @@ -17,161 +17,78 @@ import * as Components from '@site/src/components'; ![TypeScript SDK Banner](/img/assets/banner-typescript-temporal.png) -:::info TYPESCRIPT SPECIFIC RESOURCES Build Temporal Applications with the TypeScript SDK. +Use the essential components of a Temporal Application (Activities, Workflows, and Workers) to build and run a Temporal application. -**Temporal TypeScript Technical Resources:** +- [Develop a Basic Activity](/develop/typescript/activities/basic-activity) +- [Develop a Basic Workflow](/develop/typescript/workflows/basic-workflow) +- [Start an Activity Execution](/develop/typescript/activities/execution) +- [Run Worker Processes](/develop/typescript/client/temporal-client#connect-to-temporal-service-from-a-worker) -- [TypeScript SDK Quickstart - Setup Guide](https://docs.temporal.io/develop/typescript/set-up-your-local-typescript) -- [TypeScript API Documentation](https://typescript.temporal.io) -- [TypeScript SDK Code Samples](https://github.com/temporalio/samples-typescript) -- [TypeScript SDK GitHub](https://github.com/temporalio/sdk-typescript) -- [Temporal 101 in TypeScript Free Course](https://learn.temporal.io/courses/temporal_101/typescript/) - -**Get Connected with the Temporal TypeScript Community:** - -- [Temporal TypeScript Community Slack](https://temporalio.slack.com/archives/C01DKSMU94L) -- [TypeScript SDK Forum](https://community.temporal.io/tag/typescript-sdk) - -::: - -## [Core application](/develop/typescript/core-application) - -Use the essential components of a Temporal Application (Workflows, Activities, and Workers) to build and run a Temporal -application. - -- [Develop a Basic Workflow](/develop/typescript/core-application#develop-workflows) -- [Develop a Basic Activity](/develop/typescript/core-application#develop-activities) -- [Start an Activity Execution](/develop/typescript/core-application#activity-execution) -- [Run Worker Processes](/develop/typescript/core-application#run-a-dev-worker) - -## [Temporal Client](/develop/typescript/temporal-client) - -Connect to a Temporal Service and start a Workflow Execution. - -- [Connect to Development Temporal Service](/develop/typescript/temporal-client#connect-to-development-service) -- [Connect to Temporal Cloud](/develop/typescript/temporal-client#connect-to-temporal-cloud) -- [Start a Workflow Execution](/develop/typescript/temporal-client#start-workflow-execution) - -## [Testing](/develop/typescript/testing-suite) - -Set up the testing suite and test Workflows and Activities. - -- [Test Frameworks](/develop/typescript/testing-suite#test-frameworks) -- [Testing Activities](/develop/typescript/testing-suite#test-activities) -- [Testing Workflows](/develop/typescript/testing-suite#test-workflows) -- [How to Replay a Workflow Execution](/develop/typescript/testing-suite#replay) - -## [Failure detection](/develop/typescript/failure-detection) - -Explore how your application can detect failures using timeouts and automatically attempt to mitigate them with retries. - -- [Workflow Timeouts](/develop/typescript/failure-detection#workflow-timeouts) -- [Set Activity Timeouts](/develop/typescript/failure-detection#activity-timeouts) -- [Heartbeat an Activity](/develop/typescript/failure-detection#activity-heartbeats) - -## [Workflow message passing](/develop/typescript/message-passing) - -Send messages to and read the state of Workflow Executions. - -- [Develop with Signals](/develop/typescript/message-passing#signals) -- [Develop with Queries](/develop/typescript/message-passing#queries) -- [What is a Dynamic Handler](/develop/typescript/message-passing#dynamic-handler) - -## [Interrupt a Workflow feature guide](/develop/typescript/cancellation) - -Interrupt a Workflow Execution with a Cancel or Terminate action. - -- [Cancellation scopes in Typescript](/develop/typescript/cancellation#cancellation-scopes) -- [Reset a Workflow](/develop/typescript/cancellation#reset): Resume a Workflow Execution from an earlier point in its - Event History. - -## [Asynchronous Activity Completion](/develop/typescript/asynchronous-activity-completion) - -Complete Activities asynchronously. +## [Activities](/develop/typescript/activities) -- [Asynchronously Complete an Activity](/develop/typescript/asynchronous-activity-completion) +- [Basic Activity](/develop/typescript/activities/basic-activity) +- [Asynchronous Activity Completion](/develop/typescript/activities/asynchronous-activity-completion) +- [Activity Execution](/develop/typescript/activities/execution) +- [Timeouts](/develop/typescript/activities/timeouts) +- [Benign exceptions](/develop/typescript/activities/benign-exceptions) -## [Versioning](/develop/typescript/versioning) +## [Workflows](/develop/typescript/workflows) -Change Workflow Definitions without causing non-deterministic behavior in running Workflows. +- [Basic Workflow](/develop/typescript/workflows/basic-workflow) +- [Child Workflows](/develop/typescript/workflows/child-workflows) +- [Continue-As-New](/develop/typescript/workflows/continue-as-new) +- [Cancellation](/develop/typescript/workflows/cancellation) +- [Cancellation scopes](/develop/typescript/workflows/cancellation-scopes) +- [Timeouts](/develop/typescript/workflows/timeouts) +- [Message Passing](/develop/typescript/workflows/message-passing) +- [Enriching the UI](/develop/typescript/workflows/enriching-ui) +- [Schedules](/develop/typescript/workflows/schedules) +- [Timers](/develop/typescript/workflows/timers) +- [Workflow execution](/develop/typescript/workflows/workflow-execution) -- [Introduction to Versioning](/develop/typescript/versioning) -- [How to Use the Patching API](/develop/typescript/versioning#patching) +## [Workers](/develop/typescript/workers) -## [Observability](/develop/typescript/observability) +- [Interceptors](/develop/typescript/workers/interceptors) +- [Observability](/develop/typescript/workers/observability) +- [Run Worker processes](/develop/typescript/workers/run-worker-process) -Configure and use the Temporal Observability APIs. +## [Temporal Client](/develop/typescript/client) -- [Emit Metrics](/develop/typescript/observability#metrics) -- [Setup Tracing](/develop/typescript/observability#tracing) -- [Log from a Workflow](/develop/typescript/observability#logging) -- [Use Visibility APIs](/develop/typescript/observability#visibility) - -## [Debugging](/develop/typescript/debugging) - -Explore various ways to debug your application. - -- [Debugging](/develop/typescript/debugging) - -## [Schedules](/develop/typescript/schedules) - -Run Workflows on a schedule and delay the start of a Workflow. - -- [Schedule a Workflow](/develop/typescript/schedules#schedule-a-workflow) -- [Temporal Cron Jobs](/develop/typescript/schedules#temporal-cron-jobs) -- [How to use Start Delay](/develop/typescript/schedules#start-delay) - -## [Data encryption](/develop/typescript/converters-and-encryption) - -Use compression, encryption, and other data handling by implementing custom converters and codecs. - -- [Custom Payload Codec](/develop/typescript/converters-and-encryption#custom-payload-conversion) +- [Temporal Client](/develop/typescript/client/temporal-client) +- [Namespaces](/develop/typescript/namespaces) ## [Temporal Nexus](/develop/typescript/nexus) -The Temporal Nexus feature guide shows how to use Temporal Nexus to connect durable -executions within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. - -- [Create a Nexus Endpoint to route requests from caller to handler](/develop/typescript/nexus#create-nexus-endpoint) -- [Define the Nexus Service contract](/develop/typescript/nexus#define-nexus-service-contract) -- [Develop a Nexus Service and Operation handlers](/develop/typescript/nexus#develop-nexus-service-operation-handlers) -- [Develop a caller Workflow that uses a Nexus Service](/develop/typescript/nexus#develop-caller-workflow-nexus-service) -- [Make Nexus calls across Namespaces with a dev Server](/develop/typescript/nexus#register-the-caller-workflow-in-a-worker-and-start-the-caller-workflow) -- [Make Nexus calls across Namespaces in Temporal Cloud](/develop/typescript/nexus#nexus-calls-across-namespaces-temporal-cloud) - -## [Durable Timers](/develop/typescript/timers) - -Use Timers to make a Workflow Execution pause or "sleep" for seconds, minutes, days, months, or years. - -- [What is a Timer](/develop/typescript/timers) - -## [Child Workflows](/develop/typescript/child-workflows) - -Explore how to spawn a Child Workflow Execution and handle Child Workflow Events. +- [Service Handlers](/develop/typescript/nexus/service-handler) -- [Start a Child Workflow Execution](/develop/typescript/child-workflows) +## [Best practices](/develop/typescript/best-practices) -## [Continue-As-New](/develop/typescript/continue-as-new) +- [Testing](/develop/typescript/best-practices/testing-suite) +- [Debugging](/develop/typescript/best-practices/debugging) +- [Converters and encryption](/develop/typescript/converters-and-encryption) -Continue the Workflow Execution with a new Workflow Execution using the same Workflow ID. - -- [Continue-As-New](/develop/typescript/continue-as-new) +## [Vercel AI SDK Integration](/develop/typescript/integrations/ai-sdk) -## [Enriching the User Interface](/develop/typescript/enriching-ui) +Integrate the Vercel AI SDK with Temporal to build durable AI agents and AI-powered applications. -Add descriptive information to workflows and events for better visibility and context in the UI. +- [Vercel AI SDK Integration](/develop/typescript/integrations/ai-sdk) -- [Adding Summary and Details to Workflows](/develop/typescript/enriching-ui#adding-summary-and-details-to-workflows) +## Temporal TypeScript Technical Resources -## [Interceptors](/develop/typescript/interceptors) +- [TypeScript SDK Quickstart - Setup Guide](https://docs.temporal.io/develop/typescript/set-up-your-local-typescript) +- [TypeScript API Documentation](https://typescript.temporal.io) +- [TypeScript SDK Code Samples](https://github.com/temporalio/samples-typescript) +- [TypeScript SDK GitHub](https://github.com/temporalio/sdk-typescript) +- [Temporal 101 in TypeScript Free Course](https://learn.temporal.io/courses/temporal_101/typescript/) -Manage inbound and outbound SDK calls, enhance tracing, and add authorization to your Workflows and Activities. +### Get Connected with the Temporal TypeScript Community -- [How to implement interceptors](/develop/typescript/interceptors#interceptors) -- [Register an interceptor](/develop/typescript/interceptors#register-interceptor) +- [Temporal TypeScript Community Slack](https://temporalio.slack.com/archives/C01DKSMU94L) +- [TypeScript SDK Forum](https://community.temporal.io/tag/typescript-sdk) -## [Vercel AI SDK Integration](/develop/typescript/integrations/ai-sdk) +## Linting and types in TypeScript {#linting-and-types} -Integrate the Vercel AI SDK with Temporal to build durable AI agents and AI-powered applications. +If you started your project with `@temporalio/create`, you already have our recommended TypeScript and ESLint +configurations. -- [Vercel AI SDK Integration](/develop/typescript/integrations/ai-sdk) +If you incrementally added Temporal to an existing app, we do recommend setting up linting and types because they help catch bugs well before you ship them to production, and they improve your development feedback loop. Take a look at our recommended [.eslintrc](https://github.com/temporalio/samples-typescript/blob/main/.shared/.eslintrc.js) file and tweak to suit your needs. diff --git a/docs/develop/typescript/nexus/index.mdx b/docs/develop/typescript/nexus/index.mdx new file mode 100644 index 0000000000..fef1a74d46 --- /dev/null +++ b/docs/develop/typescript/nexus/index.mdx @@ -0,0 +1,21 @@ +--- +id: index +title: Nexus - TypeScript SDK +sidebar_label: Nexus +description: + This section explains how to implement Temporal Nexus with the TypeScript SDK +toc_max_heading_level: 4 +keywords: + - typescript +tags: + - TypeScript SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![TypeScript SDK Banner](/img/assets/banner-typescript-temporal.png) + +## Temporal Nexus + +- [Service Handlers](/develop/typescript/nexus/service-handler) \ No newline at end of file diff --git a/docs/develop/typescript/nexus/service-handler.mdx b/docs/develop/typescript/nexus/service-handler.mdx new file mode 100644 index 0000000000..5581a98680 --- /dev/null +++ b/docs/develop/typescript/nexus/service-handler.mdx @@ -0,0 +1,339 @@ +--- +id: service-handler +title: Nexus Service Handlers - TypeScript SDK +sidebar_label: Service Handlers +slug: /develop/typescript/nexus/service-handler +description: + Use Temporal Nexus within the TypeScript SDK to connect Durable Executions within and across Namespaces using a Nexus + Endpoint, a Nexus Service contract, and Nexus Operations. +toc_max_heading_level: 4 +keywords: + - temporal + - nexus + - typescript + - sdk +tags: + - Nexus + - TypeScript SDK +--- + +import { CaptionedImage } from '@site/src/components'; + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Temporal TypeScript SDK support for Nexus is at +[Pre-release](/evaluate/development-production-features/release-stages#pre-release). + +All APIs are experimental and may be subject to backwards-incompatible changes. + +::: + +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus +Endpoint, a Nexus Service contract, and Nexus Operations. + +This page shows how to do the following: + +- [Run a development Temporal Service with Nexus enabled](/develop/typescript/nexus#run-the-temporal-nexus-development-server) +- [Define the Nexus Service contract](#define-nexus-service-contract) +- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) +- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) +- [Understand exceptions in Nexus Operations](#exceptions-in-nexus-operations) +- [Cancel a Nexus Operation](#canceling-a-nexus-operation) +- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) + +
+ +:::note + +This documentation uses source code derived from the +[TypeScript Nexus sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-hello). + +::: + +## Define the Nexus Service contract {#define-nexus-service-contract} + +Defining a clear contract for the Nexus Service is crucial for smooth communication. + +In this example, there is a service package that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. + +Each [Temporal SDK includes and uses a default Data Converter](https://docs.temporal.io/dataconversion). The default +data converter encodes payloads in the following order: Null, Byte array, and JSON. In a polyglot environment, that is +where more than one language and SDK is being used to develop a Temporal solution, JSON is a common choice. This example +uses plain TypeScript objects, serialized into JSON. + +Note: By default, the TypeScript SDK +[does not support Protobuf JSON encoding](https://typescript.temporal.io/api/interfaces/common.PayloadConverter). If +passing Protobuf payloads use the +[ProtobufJsonPayloadConverter](https://typescript.temporal.io/api/classes/protobufs.ProtobufJsonPayloadConverter) +instead. + + + +[nexus-hello/src/api.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/api.ts) + +```ts +import * as nexus from 'nexus-rpc'; + +export const helloService = nexus.service('hello', { + /** + * Return the input message, unmodified. In the present sample, this Operation + * will be implemented using the Synchronous Nexus Operation handler syntax. + */ + echo: nexus.operation(), + + /** + * Return a salutation message, in the requested language. In the present sample, + * this Operation will be implemented by starting the `helloWorkflow` Workflow. + */ + hello: nexus.operation(), +}); + +export interface EchoInput { + message: string; +} + +export interface EchoOutput { + message: string; +} + +export interface HelloInput { + name: string; + language: LanguageCode; +} + +export interface HelloOutput { + message: string; +} + +export type LanguageCode = 'en' | 'fr' | 'de' | 'es' | 'tr'; +``` + + + +## Develop a Nexus Service handler and Operation handlers {#develop-nexus-service-operation-handlers} + +A Nexus Service handler is defined using the `nexus-rpc`'s +[`serviceHandler`](https://nexus-rpc.github.io/sdk-typescript/functions/serviceHandler.html) function. {/* Added */} +Nexus Service handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. A +Service handler must provide Operation handlers for each Operation declared by the Service. {/* Added */} Operation +handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can execute arbitrary code, and +invoke underlying Temporal primitives such as a Workflow, Query, Signal, or Update. + +The `@temporalio/nexus` package provides utilities to help create Nexus Operations that interracts with a Temporal +namespace: {/* Extended */} + +- `WorkflowRunOperationHandler` - Create an asynchronous operation handler that starts a Workflow. +- `getClient()` - Get a Temporal Client connected using the same `NativeConnection` as the present Temporal Worker. It + can be used to implement synchronous handlers backed by Temporal primitives such as Signals and Queries. + +### Develop a Synchronous Nexus Operation handler + +Simple RPC handlers can be implemented as synchronous Nexus Operation handlers, which is defined in TypeScript as a +simple async function. {/* sync operation vs async func is very confusing in this context */} The handler function can +obtain a Temporal Client, using `getClient()`, which can be used for signaling, querying, and listing Workflows. +However, implementations are free to make arbitrary calls to other services or databases, or perform computations such +as this one: + + + +[nexus-hello/src/service/handler.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/handler.ts) + +```ts +// ... +import * as nexus from 'nexus-rpc'; +// ... +import { helloService, EchoInput, EchoOutput, HelloInput, HelloOutput } from '../api'; +// ... + +export const helloServiceHandler = nexus.serviceHandler(helloService, { + echo: async (ctx, input: EchoInput): Promise => { + // A simple async function can be used to defined a Synchronous Nexus Operation. + // This is often sufficient for Operations that simply make arbitrary short calls to + // other services or databases, or that perform simple computations such as this one. + // + // You may also access a Temporal Client by calling `temporalNexus.getClient()`. + // That Client can be used to make arbitrary calls, such as signaling, querying, + // or listing workflows. + return input; + }, + // ... +}); +``` + + + +### Develop an Asynchronous Nexus Operation handler to start a Workflow + +Use `@temporalio/nexus`'s `WorkflowRunOperationHandler` helper class to easily expose a Temporal Workflow as a Nexus +Operation. Note that even though a Nexus operation can only take one input parameter, if you need to pass multiple +arguments through to the workflow, you can do so by using multiple properties of the input object, and placing them in +the array provided to the `args` option when calling `startWorkflow`. + + + +[nexus-hello/src/service/handler.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/handler.ts) + +```ts +import { randomUUID } from 'crypto'; +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, { + // ... + hello: new temporalNexus.WorkflowRunOperationHandler( + // WorkflowRunOperationHandler takes a function that receives the Operation's context and input. + // That function can be used to validate and/or transform the input before passing it to + // the Workflow, as well as to customize various Workflow start options as appropriate. + // Call temporalNexus.startWorkflow() to actually start the Workflow from inside the + // WorkflowRunOperationHandler's delegate function. + async (ctx, input: HelloInput) => { + return await temporalNexus.startWorkflow(ctx, helloWorkflow, { + args: [input], + + // Workflow IDs should typically be business-meaningful IDs and are used to dedupe workflow starts. + // For this example, we're using the request ID allocated by Temporal when the caller workflow schedules + // the operation, this ID is guaranteed to be stable across retries of this operation. + workflowId: ctx.requestId ?? randomUUID(), + + // Task queue defaults to the task queue this Operation is handled on. + }); + } + ), +}); +``` + + + +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. + +:::tip RESOURCES + +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. + +::: + +### Register your Nexus Service handler in a Worker + +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus +Service handler in a Worker. + + + +[nexus-hello/src/service/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/worker.ts) + +```ts +import { Worker, NativeConnection } from '@temporalio/worker'; +import { helloServiceHandler } from './handler'; + +// ... +const namespace = 'my-target-namespace'; +const serviceTaskQueue = 'my-handler-task-queue'; +const worker = await Worker.create({ + connection, + namespace, + taskQueue: serviceTaskQueue, + workflowsPath: require.resolve('./workflows'), + nexusServices: [helloServiceHandler], +}); +``` + + + +## Develop a caller Workflow that uses the Nexus Service {#develop-caller-workflow-nexus-service} + +To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use +`@temporalio/workflow`'s `createNexusClient` to create a Nexus client for that service. You will need to provide the +Nexus Endpoint name, which you registered previously in +[Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint). + + + +[nexus-hello/src/caller/workflows.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/workflows.ts) + +```ts +import * as wf from '@temporalio/workflow'; +import { helloService, LanguageCode } from '../service/api'; + +const HELLO_SERVICE_ENDPOINT = 'hello-service-endpoint-name'; + +export async function helloCallerWorkflow(name: string, language: LanguageCode): Promise { + const nexusClient = wf.createNexusClient({ + service: helloService, + endpoint: HELLO_SERVICE_ENDPOINT, + }); + + const helloResult = await nexusClient.executeOperation( + 'hello', + { name, language }, + { scheduleToCloseTimeout: '10s' } + ); + + return helloResult.message; +} +``` + + + +### Register the caller Workflow in a Worker and start the caller Workflow + +This Workflow can be registered with a Worker and started using `client.startWorkflow()` or `client.executeWorkflow()`, +as usual. Refer to the +[complete TypeScript sample](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello) for reference. + +- [nexus-hello/src/caller/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/worker.ts) + shows how to register the caller Workflow in a Worker and run the Worker. +- [nexus-hello/src/starter.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/starter.ts) + shows how to use a Temporal Client to execute the sample caller Workflow. + +## Exceptions in Nexus operations {#exceptions-in-nexus-operations} + +Temporal provides general guidance on +[Errors in Nexus operations](https://docs.temporal.io/references/failures#errors-in-nexus-operations). In TypeScript, +there are three Nexus-specific exception classes: + +- `nexus-rpc`'s [`OperationError`](https://nexus-rpc.github.io/sdk-typescript/classes/OperationError.html): this is the + exception type you should throw in a Nexus operation to indicate that it has failed according to its own application + logic and should not be retried. +- `nexus-rpc`'s [`HandlerError`](https://nexus-rpc.github.io/sdk-typescript/classes/HandlerError.html): you can throw + this exception type in a Nexus operation with a specific + [HandlerErrorType](https://nexus-rpc.github.io/sdk-typescript/types/HandlerErrorType.html). The error will be marked + as either retryable or non-retryable according to the type, following the + [Nexus spec](https://github.com/nexus-rpc/api/blob/main/SPEC.md#predefined-handler-errors). The non-retryable handler + error types are `BAD_REQUEST`, `UNAUTHENTICATED`, `UNAUTHORIZED`, `NOT_FOUND`, `NOT_IMPLEMENTED`; the retryable types + are `RESOURCE_EXHAUSTED`, `INTERNAL`, `UNAVAILABLE`, `UPSTREAM_TIMEOUT`. +- `@temporalio/nexus`'s + [`NexusOperationFailure`](https://typescript.temporal.io/api/classes/common.NexusOperationFailure): this is the error + thrown inside a Workflow when a Nexus operation fails for any reason. Use the `cause` attribute on the exception to + access the cause chain. + +## Canceling a Nexus Operation {#canceling-a-nexus-operation} + +Nexus Operations, just like other cancellable APIs provided by the `@temporalio/workflow` package, execute within +Cancellation Scopes. Requesting cancellation of a Cancellation Scope results in requesting cancellation for all +cancellable operations owned by that scope. The Workflow itself defines the root Cancellation Scope. Requesting +cancellation of the Workflow therefore propagates the cancellation request to all cancellable operations started by that +workflow, including Nexus Operations. + +To provide more granular control over cancellation of a specific Nexus Operation, you may explicitly create a new +Cancellation Scope, and start the Nexus Operation from within that scope. An example demonstrating this can be found at +our [nexus cancellation sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-cancellation). + +Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow +or other resources backing the operation may choose to ignore the cancellation request. + +Once the caller Workflow completes, the caller's Nexus Machinery will not make any further attempts to cancel operations +that are still running. It's okay to leave operations running in some use cases. To ensure cancellations are delivered, +wait for all pending operations to finish before exiting the Workflow. + +## Learn more + +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). +- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/typescript/temporal-nexus.mdx b/docs/develop/typescript/temporal-nexus.mdx deleted file mode 100644 index ec264e4a09..0000000000 --- a/docs/develop/typescript/temporal-nexus.mdx +++ /dev/null @@ -1,439 +0,0 @@ ---- -id: nexus -title: Temporal Nexus - TypeScript SDK feature guide -sidebar_label: Temporal Nexus -description: Use Temporal Nexus within the TypeScript SDK to connect Durable Executions within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. -toc_max_heading_level: 4 -keywords: - - temporal - - nexus - - typescript - - sdk -tags: - - Nexus - - TypeScript SDK ---- - -import { CaptionedImage } from "@site/src/components"; - -:::tip SUPPORT, STABILITY, and DEPENDENCY INFO - -Temporal TypeScript SDK support for Nexus is at [Pre-release](/evaluate/development-production-features/release-stages#pre-release). - -All APIs are experimental and may be subject to backwards-incompatible changes. - -::: - -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. - -This page shows how to do the following: - -- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) -- [Create caller and handler Namespaces](#create-caller-handler-namespaces) -- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) -- [Define the Nexus Service contract](#define-nexus-service-contract) -- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) -- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) -- [Understand exceptions in Nexus Operations](#exceptions-in-nexus-operations) -- [Cancel a Nexus Operation](#canceling-a-nexus-operation) -- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) - -
- -:::note - -This documentation uses source code derived from the [TypeScript Nexus sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-hello). - -::: - -## Run the Temporal Development Server with Nexus enabled {#run-the-temporal-nexus-development-server} - -Prerequisites: - -- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/typescript/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) (`v1.3.0` or higher recommended) -- [Install the latest Temporal TypeScript SDK](https://learn.temporal.io/getting_started/typescript/dev_environment/#add-temporal-typescript-sdk-dependencies) (`v1.12.3` or higher) - -The first step in working with Temporal Nexus involves starting a Temporal Server with Nexus enabled. - -``` -temporal server start-dev -``` - -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. It uses an in-memory database, so do not use it for real use cases. - -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server should now be available for client connections on `localhost:7233`. - -## Create caller and handler Namespaces {#create-caller-handler-namespaces} - -Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. - -``` -temporal operator namespace create --namespace my-target-namespace -temporal operator namespace create --namespace my-caller-namespace -``` - -For this example, `my-target-namespace` will contain the Nexus Operation handler, and you will use a Workflow in `my-caller-namespace` to call that Operation handler. -We use different namespaces to demonstrate cross-Namespace Nexus calls. - -## Create a Nexus Endpoint to route requests from caller to handler {#create-nexus-endpoint} - -After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. - -``` -temporal operator nexus endpoint create \ - --name my-nexus-endpoint-name \ - --target-namespace my-target-namespace \ - --target-task-queue my-handler-task-queue -``` - -You can also use the Web UI to create the Namespaces and Nexus endpoint. - -## Define the Nexus Service contract {#define-nexus-service-contract} - -Defining a clear contract for the Nexus Service is crucial for smooth communication. - -In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. - -Each [Temporal SDK includes and uses a default Data Converter](https://docs.temporal.io/dataconversion). -The default data converter encodes payloads in the following order: Null, Byte array, and JSON. -In a polyglot environment, that is where more than one language and SDK is being used to develop a Temporal solution, JSON is a common choice. -This example uses plain TypeScript objects, serialized into JSON. - -Note: By default, the TypeScript SDK [does not support Protobuf JSON encoding](https://typescript.temporal.io/api/interfaces/common.PayloadConverter). If passing Protobuf payloads use the [ProtobufJsonPayloadConverter](https://typescript.temporal.io/api/classes/protobufs.ProtobufJsonPayloadConverter) instead. - - -[nexus-hello/src/api.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/api.ts) -```ts -import * as nexus from 'nexus-rpc'; - -export const helloService = nexus.service('hello', { - /** - * Return the input message, unmodified. In the present sample, this Operation - * will be implemented using the Synchronous Nexus Operation handler syntax. - */ - echo: nexus.operation(), - - /** - * Return a salutation message, in the requested language. In the present sample, - * this Operation will be implemented by starting the `helloWorkflow` Workflow. - */ - hello: nexus.operation(), -}); - -export interface EchoInput { - message: string; -} - -export interface EchoOutput { - message: string; -} - -export interface HelloInput { - name: string; - language: LanguageCode; -} - -export interface HelloOutput { - message: string; -} - -export type LanguageCode = 'en' | 'fr' | 'de' | 'es' | 'tr'; -``` - - -## Develop a Nexus Service handler and Operation handlers {#develop-nexus-service-operation-handlers} - -A Nexus Service handler is defined using the `nexus-rpc`'s [`serviceHandler`](https://nexus-rpc.github.io/sdk-typescript/functions/serviceHandler.html) function. {/* Added */} -Nexus Service handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -A Service handler must provide Operation handlers for each Operation declared by the Service. {/* Added */} -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. -They can execute arbitrary code, and invoke underlying Temporal primitives such as a Workflow, Query, Signal, or Update. - -The `@temporalio/nexus` package provides utilities to help create Nexus Operations that interracts with a Temporal namespace: {/* Extended */} - -- `WorkflowRunOperationHandler` - Create an asynchronous operation handler that starts a Workflow. -- `getClient()` - Get a Temporal Client connected using the same `NativeConnection` as the present Temporal Worker. - It can be used to implement synchronous handlers backed by Temporal primitives such as Signals and Queries. - -### Develop a Synchronous Nexus Operation handler - -Simple RPC handlers can be implemented as synchronous Nexus Operation handlers, which is defined in TypeScript as a simple async function. {/* sync operation vs async func is very confusing in this context */} -The handler function can obtain a Temporal Client, using `getClient()`, which can be used for signaling, querying, and listing Workflows. -However, implementations are free to make arbitrary calls to other services or databases, or perform computations such as this one: - - -[nexus-hello/src/service/handler.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/handler.ts) -```ts -// ... -import * as nexus from 'nexus-rpc'; -// ... -import { helloService, EchoInput, EchoOutput, HelloInput, HelloOutput } from '../api'; -// ... - -export const helloServiceHandler = nexus.serviceHandler(helloService, { - echo: async (ctx, input: EchoInput): Promise => { - // A simple async function can be used to defined a Synchronous Nexus Operation. - // This is often sufficient for Operations that simply make arbitrary short calls to - // other services or databases, or that perform simple computations such as this one. - // - // You may also access a Temporal Client by calling `temporalNexus.getClient()`. - // That Client can be used to make arbitrary calls, such as signaling, querying, - // or listing workflows. - return input; - }, -// ... -}); -``` - - -### Develop an Asynchronous Nexus Operation handler to start a Workflow - -Use `@temporalio/nexus`'s `WorkflowRunOperationHandler` helper class to easily expose a Temporal Workflow as a Nexus Operation. -Note that even though a Nexus operation can only take one input parameter, if you need to pass -multiple arguments through to the workflow, you can do so by using multiple properties of the input object, and placing them in -the array provided to the `args` option when calling `startWorkflow`. - - -[nexus-hello/src/service/handler.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/handler.ts) -```ts -import { randomUUID } from 'crypto'; -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, { -// ... - hello: new temporalNexus.WorkflowRunOperationHandler( - // WorkflowRunOperationHandler takes a function that receives the Operation's context and input. - // That function can be used to validate and/or transform the input before passing it to - // the Workflow, as well as to customize various Workflow start options as appropriate. - // Call temporalNexus.startWorkflow() to actually start the Workflow from inside the - // WorkflowRunOperationHandler's delegate function. - async (ctx, input: HelloInput) => { - return await temporalNexus.startWorkflow(ctx, helloWorkflow, { - args: [input], - - // Workflow IDs should typically be business-meaningful IDs and are used to dedupe workflow starts. - // For this example, we're using the request ID allocated by Temporal when the caller workflow schedules - // the operation, this ID is guaranteed to be stable across retries of this operation. - workflowId: ctx.requestId ?? randomUUID(), - - // Task queue defaults to the task queue this Operation is handled on. - }); - }, - ), -}); -``` - - -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. -In general, the ID should be passed in the Operation input as part of the Nexus Service contract. - -:::tip RESOURCES - -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a Conflict-Policy of Use-Existing. - -::: - -### Register your Nexus Service handler in a Worker - -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus Service handler in a Worker. - - -[nexus-hello/src/service/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/worker.ts) -```ts -import { Worker, NativeConnection } from '@temporalio/worker'; -import { helloServiceHandler } from './handler'; - -// ... - const namespace = 'my-target-namespace'; - const serviceTaskQueue = 'my-handler-task-queue'; - const worker = await Worker.create({ - connection, - namespace, - taskQueue: serviceTaskQueue, - workflowsPath: require.resolve('./workflows'), - nexusServices: [helloServiceHandler], - }); -``` - - -## Develop a caller Workflow that uses the Nexus Service {#develop-caller-workflow-nexus-service} - -To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use `@temporalio/workflow`'s `createNexusClient` to create a Nexus client for that service. -You will need to provide the Nexus Endpoint name, which you registered previously in [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint). - - - -[nexus-hello/src/caller/workflows.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/workflows.ts) - -```ts -import * as wf from "@temporalio/workflow"; -import { helloService, LanguageCode } from "../service/api"; - -const HELLO_SERVICE_ENDPOINT = "hello-service-endpoint-name"; - -export async function helloCallerWorkflow(name: string, language: LanguageCode): Promise { - const nexusClient = wf.createNexusClient({ - service: helloService, - endpoint: HELLO_SERVICE_ENDPOINT, - }); - - const helloResult = await nexusClient.executeOperation( - "hello", - { name, language }, - { scheduleToCloseTimeout: "10s" } - ); - - return helloResult.message; -} -``` - - - -### Register the caller Workflow in a Worker and start the caller Workflow - -This Workflow can be registered with a Worker and started using `client.startWorkflow()` or `client.executeWorkflow()`, as usual. -Refer to the [complete TypeScript sample](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello) for reference. - -- [nexus-hello/src/caller/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/worker.ts) shows how to register the caller Workflow in a Worker and run the Worker. -- [nexus-hello/src/starter.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/starter.ts) shows how to use a Temporal Client to execute the sample caller Workflow. - -## Exceptions in Nexus operations {#exceptions-in-nexus-operations} - -Temporal provides general guidance on [Errors in Nexus operations](https://docs.temporal.io/references/failures#errors-in-nexus-operations). -In TypeScript, there are three Nexus-specific exception classes: - -- `nexus-rpc`'s [`OperationError`](https://nexus-rpc.github.io/sdk-typescript/classes/OperationError.html): this is the exception type you should throw in a Nexus operation to indicate that it has failed according to its own application logic and should not be retried. -- `nexus-rpc`'s [`HandlerError`](https://nexus-rpc.github.io/sdk-typescript/classes/HandlerError.html): you can throw this exception type in a Nexus operation with a specific [HandlerErrorType](https://nexus-rpc.github.io/sdk-typescript/types/HandlerErrorType.html). The error will be marked as either retryable or non-retryable according to the type, following the [Nexus spec](https://github.com/nexus-rpc/api/blob/main/SPEC.md#predefined-handler-errors). The non-retryable handler error types are `BAD_REQUEST`, `UNAUTHENTICATED`, `UNAUTHORIZED`, `NOT_FOUND`, `NOT_IMPLEMENTED`; the retryable types are `RESOURCE_EXHAUSTED`, `INTERNAL`, `UNAVAILABLE`, `UPSTREAM_TIMEOUT`. -- `@temporalio/nexus`'s [`NexusOperationFailure`](https://typescript.temporal.io/api/classes/common.NexusOperationFailure): this is the error thrown inside a Workflow when a Nexus operation fails for any reason. Use the `cause` attribute on the exception to access the cause chain. - -## Canceling a Nexus Operation {#canceling-a-nexus-operation} - -Nexus Operations, just like other cancellable APIs provided by the `@temporalio/workflow` package, execute within Cancellation Scopes. -Requesting cancellation of a Cancellation Scope results in requesting cancellation for all cancellable operations owned by that scope. -The Workflow itself defines the root Cancellation Scope. -Requesting cancellation of the Workflow therefore propagates the cancellation request to all cancellable operations started by that workflow, including Nexus Operations. - -To provide more granular control over cancellation of a specific Nexus Operation, you may explicitly create a new Cancellation Scope, and start the Nexus Operation from within that scope. -An example demonstrating this can be found at our [nexus cancellation sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-cancellation). - -Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. -The Workflow or other resources backing the operation may choose to ignore the cancellation request. - -Once the caller Workflow completes, the caller's Nexus Machinery will not make any further attempts to cancel operations that are still running. -It's okay to leave operations running in some use cases. -To ensure cancellations are delivered, wait for all pending operations to finish before exiting the Workflow. - -## Make Nexus calls across Namespaces in Temporal Cloud {#nexus-calls-across-namespaces-temporal-cloud} - -This section assumes you are already familiar with how to connect a Worker to Temporal Cloud. -The `tcld` CLI is used to create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the caller and handler Workers to their respective Temporal Cloud Namespaces. - -### Install the latest `tcld` CLI and generate certificates - -To install the latest version of the `tcld` CLI, run the following command (on macOS): - -``` -brew install temporalio/brew/tcld -``` - -If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: - -``` -tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key -``` - -These certificates will be valid for one year. - -### Create caller and handler Namespaces - -Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. -If you already have these Namespaces, you don't need to do this. - -``` -tcld login - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 -``` - -Alternatively, you can create Namespaces through the UI: [https://cloud.temporal.io/namespaces](https://cloud.temporal.io/namespaces). - -### Create a Nexus Endpoint to route requests from caller to handler - -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the `--target-namespace`. - -``` -tcld nexus endpoint create \ - --name \ - --target-task-queue my-handler-task-queue \ - --target-namespace \ - --allow-namespace \ - --description-file description.md -``` - -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as described in Runtime Access Control. - -Alternatively, you can create a Nexus Endpoint through the UI: [https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). - -## Observability - -### Web UI - -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and `NexusOperationCompleted` events in the caller's Workflow history: - - - -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, `NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Workflow history: - - - -### Temporal CLI - -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow: - -``` -temporal workflow describe -w -``` - -Nexus events are included in the caller's Workflow history: - -``` -temporal workflow show -w -``` - -For **asynchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationStarted` -- `NexusOperationCompleted` - -For **synchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationCompleted` - -:::note - -`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. - -::: - -## Learn more - -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and [Encyclopedia](/nexus). -- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/typescript/workers/index.mdx b/docs/develop/typescript/workers/index.mdx new file mode 100644 index 0000000000..18477c6f68 --- /dev/null +++ b/docs/develop/typescript/workers/index.mdx @@ -0,0 +1,23 @@ +--- +id: index +title: Workers - TypeScript SDK +sidebar_label: Workers +description: + This section explains how to implement Workers with the TypeScript SDK +toc_max_heading_level: 4 +keywords: + - typescript +tags: + - TypeScript SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![TypeScript SDK Banner](/img/assets/banner-typescript-temporal.png) + +## Workers + +- [Run Worker processes](/develop/typescript/workers/run-worker-process) +- [Interceptors](/develop/typescript/workers/interceptors) +- [Observability](/develop/typescript/workers/observability) diff --git a/docs/develop/typescript/interceptors.mdx b/docs/develop/typescript/workers/interceptors.mdx similarity index 100% rename from docs/develop/typescript/interceptors.mdx rename to docs/develop/typescript/workers/interceptors.mdx diff --git a/docs/develop/typescript/observability.mdx b/docs/develop/typescript/workers/observability.mdx similarity index 96% rename from docs/develop/typescript/observability.mdx rename to docs/develop/typescript/workers/observability.mdx index 95ad51d595..bca2bcc87e 100644 --- a/docs/develop/typescript/observability.mdx +++ b/docs/develop/typescript/workers/observability.mdx @@ -2,7 +2,7 @@ id: observability title: Observability - TypeScript SDK sidebar_label: Observability -slug: /develop/typescript/observability +slug: /develop/typescript/workers/observability description: Enhance the observability of your Temporal Application with metrics, tracing, logging, and visibility features. View Workflow state, set up OpenTelemetry, and customize logging for seamless monitoring and insights. toc_max_heading_level: 3 keywords: @@ -121,7 +121,7 @@ The Temporal SDK core normally uses `WARN` as its default logging level. Activities run in the standard Node.js environment and may therefore use any Node.js logger directly. -The Temporal SDK however provides a convenient Activity Context logger, which funnels log messages to the [Runtime's logger](/develop/typescript/observability#customizing-the-default-logger). Attributes from the current Activity context are automatically included as metadata on every log entries emitted using the Activity context logger, and some key events of the Activity's lifecycle are automatically logged (at DEBUG level for most messages; WARN for failures). +The Temporal SDK however provides a convenient Activity Context logger, which funnels log messages to the [Runtime's logger](/develop/typescript/workers/observability#customizing-the-default-logger). Attributes from the current Activity context are automatically included as metadata on every log entries emitted using the Activity context logger, and some key events of the Activity's lifecycle are automatically logged (at DEBUG level for most messages; WARN for failures).
@@ -153,7 +153,7 @@ Workflows may not use regular Node.js loggers because: 1. Workflows run in a sandboxed environment and cannot do any I/O. 1. Workflow code might get replayed at any time, which would result in duplicated log messages. -The Temporal SDK however provides a Workflow Context logger, which funnels log messages to the [Runtime's logger](/develop/typescript/observability#customizing-the-default-logger). Attributes from the current Workflow context are automatically included as metadata on every log entries emitted using the Workflow context logger, and some key events of the Workflow's lifecycle are automatically logged (at DEBUG level for most messages; WARN for failures). +The Temporal SDK however provides a Workflow Context logger, which funnels log messages to the [Runtime's logger](/develop/typescript/workers/observability#customizing-the-default-logger). Attributes from the current Workflow context are automatically included as metadata on every log entries emitted using the Workflow context logger, and some key events of the Workflow's lifecycle are automatically logged (at DEBUG level for most messages; WARN for failures).
diff --git a/docs/develop/typescript/workers/run-process.mdx b/docs/develop/typescript/workers/run-process.mdx new file mode 100644 index 0000000000..243625cb01 --- /dev/null +++ b/docs/develop/typescript/workers/run-process.mdx @@ -0,0 +1,303 @@ +--- +id: run-worker-process +title: Run Worker processes - TypeScript SDK +description: Shows how to run Worker processes with the TypeScript SDK +sidebar_label: Worker processes +slug: /develop/typescript/workers/run-worker-process +toc_max_heading_level: 3 +tags: + - TypeScript SDK + - Temporal SDKs + - Worker +--- + +## How to run Worker Processes {#run-a-dev-worker} + +The [Worker Process](/workers#worker-process) is where Workflow Functions and Activity Functions are executed. + +- Each [Worker Entity](/workers#worker-entity) in the Worker Process must register the exact Workflow Types and Activity + Types it may execute. +- Each Worker Entity must also associate itself with exactly one [Task Queue](/task-queue). +- Each Worker Entity polling the same Task Queue must be registered with the same Workflow Types and Activity Types. + +A [Worker Entity](/workers#worker-entity) is the component within a Worker Process that listens to a specific Task +Queue. + +Although multiple Worker Entities can be in a single Worker Process, a single Worker Entity Worker Process may be +perfectly sufficient. For more information, see the [Worker tuning guide](/develop/worker-performance). + +A Worker Entity contains a Workflow Worker and/or an Activity Worker, which makes progress on Workflow Executions and +Activity Executions, respectively. + +## How to run a Worker on Docker in TypeScript {#run-a-worker-on-docker} + +:::note + +To improve worker startup time, we recommend preparing workflow bundles ahead-of-time. See our +[productionsample](https://github.com/temporalio/samples-typescript/tree/main/production) for details. + +::: + +Workers based on the TypeScript SDK can be deployed and run as Docker containers. + +We recommend an LTS Node.js release such as 18, 20, 22, or 24. Both `amd64` and `arm64` architectures are supported. A +glibc-based image is required; musl-based images are _not_ supported (see below). + +The easiest way to deploy a TypeScript SDK Worker on Docker is to start with the `node:20-bullseye` image. For example: + +```dockerfile +FROM node:20-bullseye + +# For better cache utilization, copy package.json and lock file first and install the dependencies before copying the +# rest of the application and building. +COPY . /app +WORKDIR /app + +# Alternatively, run npm ci, which installs only dependencies specified in the lock file and is generally faster. +RUN npm install --only=production \ + && npm run build + +CMD ["npm", "start"] +``` + +For smaller images and/or more secure deployments, it is also possible to use `-slim` Docker image variants (like +`node:20-bullseye-slim`) or `distroless/nodejs` Docker images (like `gcr.io/distroless/nodejs20-debian11`) with the +following caveats. + +### Using `node:slim` images + +`node:slim` images do not contain some of the common packages found in regular images. This results in significantly +smaller images. + +However, TypeScript SDK requires the presence of root TLS certificates (the `ca-certificates` package), which are not +included in `slim` images. The `ca-certificates` package is required even when connecting to a local Temporal Server or +when using a server connection config that doesn't explicitly use TLS. + +For this reason, the `ca-certificates` package must be installed during the construction of the Docker image. For +example: + +```dockerfile +FROM node:20-bullseye-slim + +RUN apt-get update \ + && apt-get install -y ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# ... same as with regular image +``` + +Failure to install this dependency results in a `[TransportError: transport error]` runtime error, because the +certificates cannot be verified. + +### Using `distroless/nodejs` images + +`distroless/nodejs` images include only the files that are strictly required to execute `node`. This results in even +smaller images (approximately half the size of `node:slim` images). It also significantly reduces the surface of +potential security issues that could be exploited by a hacker in the resulting Docker images. + +It is generally possible and safe to execute TypeScript SDK Workers using `distroless/nodejs` images (unless your code +itself requires dependencies that are not included in `distroless/nodejs`). + +However, some tools required for the build process (notably the `npm` command) are _not_ included in the +`distroless/nodejs` image. This might result in various error messages during the Docker build. + +The recommended solution is to use a multi-step Dockerfile. For example: + +```dockerfile +# -- BUILD STEP -- + +FROM node:20-bullseye AS builder + +COPY . /app +WORKDIR /app + +RUN npm install --only=production \ + && npm run build + +# -- RESULTING IMAGE -- + +FROM gcr.io/distroless/nodejs20-debian11 + +COPY --from=builder /app /app +WORKDIR /app + +CMD ["node", "build/worker.js"] +``` + +### Properly configure Node.js memory in Docker + +By default, `node` configures its maximum old-gen memory to 25% of the _physical memory_ of the machine on which it is +executing, with a maximum of 4 GB. This is likely inappropriate when running Node.js in a Docker environment and can +result in either underusage of available memory (`node` only uses a fraction of the memory allocated to the container) +or overusage (`node` tries to use more memory than what is allocated to the container, which will eventually lead to the +process being killed by the operating system). + +Therefore we recommended that you always explicitly set the `--max-old-space-size` `node` argument to approximately 80% +of the maximum size (in megabytes) that you want to allocate the `node` process. You might need some experimentation and +adjustment to find the most appropriate value based on your specific application. + +In practice, it is generally easier to provide this argument through the +[`NODE_OPTIONS` environment variable](https://nodejs.org/api/cli.html#node_optionsoptions). + +### Do not use Alpine + +Alpine replaces glibc with musl, which is incompatible with the Rust core of the TypeScript SDK. If you receive errors +like the following, it's probably because you are using Alpine. + +```sh +Error: Error loading shared library ld-linux-x86-64.so.2: No such file or directory (needed by /opt/app/node_modules/@temporalio/core-bridge/index.node) +``` + +Or like this: + +```sh +Error: Error relocating /opt/app/node_modules/@temporalio/core-bridge/index.node: __register_atfork: symbol not found +``` + +## How to run a Temporal Cloud Worker {#run-a-temporal-cloud-worker} + +To run a Worker that uses [Temporal Cloud](/cloud), you need to provide additional connection and client options that +include the following: + +- An address that includes your [Cloud Namespace Name](/namespaces) and a port number: + `..tmprl.cloud:`. +- mTLS CA certificate. +- mTLS private key. + +For more information about managing and generating client certificates for Temporal Cloud, see +[How to manage certificates in Temporal Cloud](/cloud/certificates). + +For more information about configuring TLS to secure inter- and intra-network communication for a Temporal Service, see +[Temporal Customization Samples](https://github.com/temporalio/samples-server). + +### How to register types {#register-types} + +All Workers listening to the same Task Queue name must be registered to handle the exact same Workflows Types and +Activity Types. + +If a Worker polls a Task for a Workflow Type or Activity Type it does not know about, it fails that Task. However, the +failure of the Task does not cause the associated Workflow Execution to fail. + +In development, use +[`workflowsPath`](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions/#workflowspath): + + + +[snippets/src/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/worker.ts) + +```ts +import { Worker } from '@temporalio/worker'; +import * as activities from './activities'; + +async function run() { + const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + taskQueue: 'snippets', + activities, + }); + + await worker.run(); +} +``` + + + +In this snippet, the Worker bundles the Workflow code at runtime. + +In production, you can improve your Worker's startup time by bundling in advance: as part of your production build, call +`bundleWorkflowCode`: + + + +[production/src/scripts/build-workflow-bundle.ts](https://github.com/temporalio/samples-typescript/blob/main/production/src/scripts/build-workflow-bundle.ts) + +```ts +import { bundleWorkflowCode } from '@temporalio/worker'; +import { writeFile } from 'fs/promises'; +import path from 'path'; + +async function bundle() { + const { code } = await bundleWorkflowCode({ + workflowsPath: require.resolve('../workflows'), + }); + const codePath = path.join(__dirname, '../../workflow-bundle.js'); + + await writeFile(codePath, code); + console.log(`Bundle written to ${codePath}`); +} +``` + + + +Then the bundle can be passed to the Worker: + + + +[production/src/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/production/src/worker.ts) + +```ts +const workflowOption = () => + process.env.NODE_ENV === 'production' + ? { + workflowBundle: { + codePath: require.resolve('../workflow-bundle.js'), + }, + } + : { workflowsPath: require.resolve('./workflows') }; + +async function run() { + const worker = await Worker.create({ + ...workflowOption(), + activities, + taskQueue: 'production-sample', + }); + + await worker.run(); +} +``` + + + +## How to shut down a Worker and track its state {#shut-down-a-worker} + +Workers shut down if they receive any of the Signals enumerated in +[shutdownSignals](https://typescript.temporal.io/api/interfaces/worker.RuntimeOptions#shutdownsignals): `'SIGINT'`, +`'SIGTERM'`, `'SIGQUIT'`, and `'SIGUSR2'`. + +In development, we shut down Workers with `Ctrl+C` (`SIGINT`) or +[nodemon](https://github.com/temporalio/samples-typescript/blob/c37bae3ea235d1b6956fcbe805478aa46af973ce/hello-world/package.json#L10) +(`SIGUSR2`). In production, you usually want to give Workers time to finish any in-progress Activities by setting +[shutdownGraceTime](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#shutdowngracetime). + +As soon as a Worker receives a shutdown Signal or request, the Worker stops polling for new Tasks and allows in-flight +Tasks to complete until `shutdownGraceTime` is reached. Any Activities that are still running at that time will stop +running and will be rescheduled by Temporal Server when an Activity timeout occurs. + +If you must guarantee that the Worker eventually shuts down, you can set +[shutdownForceTime](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#shutdownforcetime). + +You might want to programmatically shut down Workers (with +[Worker.shutdown()](https://typescript.temporal.io/api/classes/worker.Worker#shutdown)) in integration tests or when +automating a fleet of Workers. + +### Worker states + +At any time, you can Query Worker state with +[Worker.getState()](https://typescript.temporal.io/api/classes/worker.Worker#getstate). A Worker is always in one of +seven states: + +- `INITIALIZED`: The initial state of the Worker after calling + [Worker.create()](https://typescript.temporal.io/api/classes/worker.Worker#create) and successfully connecting to the + server. +- `RUNNING`: [Worker.run()](https://typescript.temporal.io/api/classes/worker.Worker#run) was called and the Worker is + polling Task Queues. +- `FAILED`: The Worker encountered an unrecoverable error; `Worker.run()` should reject with the error. +- The last four states are related to the Worker shutdown process: + - `STOPPING`: The Worker received a shutdown Signal or `Worker.shutdown()` was called. The Worker will forcefully shut + down after `shutdownGraceTime` expires. + - `DRAINING`: All Workflow Tasks have been drained; waiting for Activities and cached Workflows eviction. + - `DRAINED`: All Activities and Workflows have completed; ready to shut down. + - `STOPPED`: Shutdown complete; `worker.run()` resolves. + +If you need more visibility into internal Worker state, see the +[Worker class](https://typescript.temporal.io/api/classes/worker.Worker) in the API reference. \ No newline at end of file diff --git a/docs/develop/typescript/workflows/basic-workflow.mdx b/docs/develop/typescript/workflows/basic-workflow.mdx new file mode 100644 index 0000000000..629f11187b --- /dev/null +++ b/docs/develop/typescript/workflows/basic-workflow.mdx @@ -0,0 +1,172 @@ +--- +id: basic-workflow +title: Basic Workflow - TypeScript SDK +description: Shows how to create a basic Workflow with the TypeScript SDK +sidebar_label: Basic Workflow +slug: /develop/typescript/workflows/basic-workflow +toc_max_heading_level: 3 +tags: + - TypeScript SDK + - Temporal SDKs + - Workflow +--- + +## How to develop a basic Workflow {#develop-workflows} + +Workflows are the fundamental unit of a Temporal Application, and it all starts with the development of a +[Workflow Definition](/workflow-definition). + +In the Temporal TypeScript SDK programming model, Workflow Definitions are _just functions_, which can store state and +orchestrate Activity Functions. The following code snippet uses `proxyActivities` to schedule a `greet` Activity in the +system to say hello. + +A Workflow Definition can have multiple parameters; however, we recommend using a single object parameter. + +```typescript +type ExampleArgs = { + name: string; +}; + +export async function example(args: ExampleArgs): Promise<{ greeting: string }> { + const greeting = await greet(args.name); + return { greeting }; +} +``` + +### How to define Workflow parameters {#workflow-parameters} + +Temporal Workflows may have any number of custom parameters. However, we strongly recommend that objects are used as +parameters, so that the object's individual fields may be altered without breaking the signature of the Workflow. All +Workflow Definition parameters must be serializable. + +You can define and pass parameters in your Workflow. In this example, you define your arguments in your `client.ts` file +and pass those parameters to `workflow.ts` through your Workflow function. + +Start a Workflow with the parameters that are in the `client.ts` file. In this example we set the `name` parameter to +`Temporal` and `born` to `2019`. Then set the Task Queue and Workflow Id. + +`client.ts` + +```typescript +import { example } from './workflows'; + +... +await client.workflow.start(example, { + args: [{ name: 'Temporal', born: 2019 }], + taskQueue: 'your-queue', + workflowId: 'business-meaningful-id', +}); +``` + +In `workflows.ts` define the type of the parameter that the Workflow function takes in. The interface `ExampleParam` is +a name we can now use to describe the requirement in the previous example. It still represents having the two properties +called `name` and `born` that is of the type `string`. Then define a function that takes in a parameter of the type +`ExampleParam` and return a `Promise`. The `Promise` object represents the eventual completion, or failure, of +`await client.workflow.start()` and its resulting value. + +```ts +interface ExampleParam { + name: string; + born: number; +} +export async function example({ name, born }: ExampleParam): Promise { + return `Hello ${name}, you were born in ${born}.`; +} +``` + +### How to define Workflow return parameters {#workflow-return-values} + +Workflow return values must also be serializable. Returning results, returning errors, or throwing exceptions is fairly +idiomatic in each language that is supported. However, Temporal APIs that must be used to get the result of a Workflow +Execution will only ever receive one of either the result or the error. + +To return a value of the Workflow function, use `Promise`. The `Promise` is used to make asynchronous calls +and comes with guarantees. + +The following example uses a `Promise` to eventually return a `name` and `born` parameter. + +```typescript +interface ExampleParam { + name: string; + born: number; +} +export async function example({ name, born }: ExampleParam): Promise { + return `Hello ${name}, you were born in ${born}.`; +} +``` + +### How to customize your Workflow Type {#workflow-type} + +Workflows have a Type that are referred to as the Workflow name. + +The following examples demonstrate how to set a custom name for your Workflow Type. + +In TypeScript, the Workflow Type is the Workflow function name and there isn't a mechanism to customize the Workflow +Type. + +In the following example, the Workflow Type is the name of the function, `helloWorld`. + + + +[snippets/src/workflows.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/workflows.ts) + +```ts +export async function helloWorld(): Promise { + return 'đź‘‹ Hello World!'; +} +``` + + + +### How to develop Workflow logic {#workflow-logic-requirements} + +Workflow logic is constrained by [deterministic execution requirements](/workflow-definition#deterministic-constraints). +Therefore, each language is limited to the use of certain idiomatic techniques. However, each Temporal SDK provides a +set of APIs that can be used inside your Workflow to interact with external (to the Workflow) application code. + +In the Temporal TypeScript SDK, Workflows run in a deterministic sandboxed environment. The code is bundled on Worker +creation using Webpack, and 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](https://typescript.temporal.io/api/interfaces/worker.BundleOptions#ignoremodules) list. + +::: + +The Workflow sandbox can run only deterministic code, so side effects and access to external state must be done through +Activities because Activity outputs are recorded in the Event History and can read deterministically by the Workflow. + +This limitation also means that Workflow code cannot directly import the [Activity Definition](/activity-definition). +[Activity Types](/activity-definition#activity-type) can be imported, so they can be invoked in a type-safe manner. + +To make the Workflow runtime deterministic, functions like `Math.random()`, `Date`, and `setTimeout()` are replaced by +deterministic versions. + +[FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry) +and [WeakRef](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef) are removed +because v8's garbage collector is not deterministic. + +
+ + + Expand to see the implications of the deterministic Date API + + +```typescript +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()); +} +``` + +
diff --git a/docs/develop/typescript/workflows/cancellation-scopes.mdx b/docs/develop/typescript/workflows/cancellation-scopes.mdx new file mode 100644 index 0000000000..61124b2806 --- /dev/null +++ b/docs/develop/typescript/workflows/cancellation-scopes.mdx @@ -0,0 +1,342 @@ +--- +id: cancellation-scopes +title: Cancellation scopes - TypeScript SDK +description: Shows cancellation scopes with the TypeScript SDK +sidebar_label: Cancellation scopes +slug: /develop/typescript/workflows/cancellation-scopes +toc_max_heading_level: 3 +tags: + - TypeScript SDK + - Temporal SDKs + - Workflow +--- + +## Cancellation scopes in Typescript {#cancellation-scopes} + +In the TypeScript SDK, Workflows are represented internally by a tree of cancellation scopes, each with cancellation +behaviors you can specify. By default, everything runs in the "root" scope. + +Scopes are created using the [CancellationScope](https://typescript.temporal.io/api/classes/workflow.CancellationScope) +constructor or one of three static helpers: + +- [cancellable(fn)](https://typescript.temporal.io/api/classes/workflow.CancellationScope#cancellable-1): Children are + automatically cancelled when their containing scope is cancelled. + - Equivalent to `new CancellationScope().run(fn)`. +- [nonCancellable(fn)](https://typescript.temporal.io/api/classes/workflow.CancellationScope#noncancellable): + Cancellation does not propagate to children. + - Equivalent to `new CancellationScope({ cancellable: false }).run(fn)`. +- [withTimeout(timeoutMs, fn)](https://typescript.temporal.io/api/classes/workflow.CancellationScope#withtimeout): If a + timeout triggers before `fn` resolves, the scope is cancelled, triggering cancellation of any enclosed operations, + such as Activities and Timers. + - Equivalent to `new CancellationScope({ cancellable: true, timeout: timeoutMs }).run(fn)`. + +Cancellations are applied to cancellation scopes, which can encompass an entire Workflow or just part of one. Scopes can +be nested, and cancellation propagates from outer scopes to inner ones. A Workflow's `main` function runs in the +outermost scope. Cancellations are handled by catching `CancelledFailure`s thrown by cancelable operations. + +`CancellationScope.run()` and the static helpers mentioned earlier return native JavaScript promises, so you can use the +familiar Promise APIs like `Promise.all` and `Promise.race` to model your asynchronous logic. You can also use the +following APIs: + +- `CancellationScope.current()`: Get the current scope. +- `scope.cancel()`: Cancel all operations inside a `scope`. +- `scope.run(fn)`: Run an async function within a `scope` and return the result of `fn`. +- `scope.cancelRequested`: A promise that resolves when a scope cancellation is requested, such as when Workflow code + calls `cancel()` or the entire Workflow is cancelled by an external client. + +When a `CancellationScope` is cancelled, it propagates cancellation in any child scopes and of any cancelable operations +created within it, such as the following: + +- Activities +- Timers (created with the [sleep](https://typescript.temporal.io/api/namespaces/workflow#sleep) function) +- [Triggers](https://typescript.temporal.io/api/classes/workflow.Trigger) + +### CancelledFailure + +Timers and triggers throw [CancelledFailure](https://typescript.temporal.io/api/classes/common.CancelledFailure) when +cancelled; Activities and Child Workflows throw `ActivityFailure` and `ChildWorkflowFailure` with cause set to +`CancelledFailure`. One exception is when an Activity or Child Workflow is scheduled in an already cancelled scope (or +Workflow). In this case, they propagate the `CancelledFailure` that was thrown to cancel the scope. + +To simplify checking for cancellation, use the +[isCancellation(err)](https://typescript.temporal.io/api/namespaces/workflow#iscancellation) function. + +### Internal cancellation example + + + +[packages/test/src/workflows/cancel-timer-immediately.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/cancel-timer-immediately.ts) + +```ts +import { CancelledFailure, CancellationScope, sleep } from '@temporalio/workflow'; + +export async function cancelTimer(): Promise { + // Timers and Activities are automatically cancelled when their containing scope is cancelled. + try { + await CancellationScope.cancellable(async () => { + const promise = sleep(1); // <-- Will be cancelled because it is attached to this closure's scope + CancellationScope.current().cancel(); + await promise; // <-- Promise must be awaited in order for `cancellable` to throw + }); + } catch (e) { + if (e instanceof CancelledFailure) { + console.log('Timer cancelled 👍'); + } else { + throw e; // <-- Fail the workflow + } + } +} +``` + + + +Alternatively, the preceding can be written as the following. + + + +[packages/test/src/workflows/cancel-timer-immediately-alternative-impl.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/cancel-timer-immediately-alternative-impl.ts) + +```ts +import { CancelledFailure, CancellationScope, sleep } from '@temporalio/workflow'; + +export async function cancelTimerAltImpl(): Promise { + try { + const scope = new CancellationScope(); + const promise = scope.run(() => sleep(1)); + scope.cancel(); // <-- Cancel the timer created in scope + await promise; // <-- Throws CancelledFailure + } catch (e) { + if (e instanceof CancelledFailure) { + console.log('Timer cancelled 👍'); + } else { + throw e; // <-- Fail the workflow + } + } +} +``` + + + +### External cancellation example + +The following code shows how to handle Workflow cancellation by an external client while an Activity is running. + +{/* TODO: add a sample here of how this Workflow could be cancelled using a WorkflowHandle */} + + + +[packages/test/src/workflows/handle-external-workflow-cancellation-while-activity-running.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/handle-external-workflow-cancellation-while-activity-running.ts) + +```ts +import { CancellationScope, proxyActivities, isCancellation } from '@temporalio/workflow'; +import type * as activities from '../activities'; + +const { httpPostJSON, cleanup } = proxyActivities({ + startToCloseTimeout: '10m', +}); + +export async function handleExternalWorkflowCancellationWhileActivityRunning(url: string, data: any): Promise { + try { + await httpPostJSON(url, data); + } catch (err) { + if (isCancellation(err)) { + console.log('Workflow cancelled'); + // Cleanup logic must be in a nonCancellable scope + // If we'd run cleanup outside of a nonCancellable scope it would've been cancelled + // before being started because the Workflow's root scope is cancelled. + await CancellationScope.nonCancellable(() => cleanup(url)); + } + throw err; // <-- Fail the Workflow + } +} +``` + + + +### nonCancellable example + +`CancellationScope.nonCancellable` prevents cancellation from propagating to children. + + + +[activities-cancellation-heartbeating/src/cancellation-scopes.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-cancellation-heartbeating/src/cancellation-scopes.ts) + +```ts +export async function nonCancellable(url: string): Promise { + // Prevent Activity from being cancelled and await completion. + // Note that the Workflow is completely oblivious and impervious to cancellation in this example. + return CancellationScope.nonCancellable(() => httpGetJSON(url)); +} +``` + + + +### withTimeout example + +A common operation is to cancel one or more Activities if a deadline elapses. `withTimeout` creates a +`CancellationScope` that is automatically cancelled after a timeout. + + + +[packages/test/src/workflows/multiple-activities-single-timeout.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/multiple-activities-single-timeout.ts) + +```ts +import { CancellationScope, proxyActivities } from '@temporalio/workflow'; +import type * as activities from '../activities'; + +export function multipleActivitiesSingleTimeout(urls: string[], timeoutMs: number): Promise { + const { httpGetJSON } = proxyActivities({ + startToCloseTimeout: timeoutMs, + }); + + // If timeout triggers before all activities complete + // the Workflow will fail with a CancelledError. + return CancellationScope.withTimeout(timeoutMs, () => Promise.all(urls.map((url) => httpGetJSON(url)))); +} +``` + + + +### scope.cancelRequested + +You can await `cancelRequested` to make a Workflow aware of cancellation while waiting on `nonCancellable` scopes. + + + +[packages/test/src/workflows/cancel-requested-with-non-cancellable.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/cancel-requested-with-non-cancellable.ts) + +```ts +import { CancellationScope, CancelledFailure, proxyActivities } from '@temporalio/workflow'; +import type * as activities from '../activities'; + +const { httpGetJSON } = proxyActivities({ + startToCloseTimeout: '10m', +}); + +export async function resumeAfterCancellation(url: string): Promise { + let result: any = undefined; + const scope = new CancellationScope({ cancellable: false }); + const promise = scope.run(() => httpGetJSON(url)); + try { + result = await Promise.race([scope.cancelRequested, promise]); + } catch (err) { + if (!(err instanceof CancelledFailure)) { + throw err; + } + // Prevent Workflow from completing so Activity can complete + result = await promise; + } + return result; +} +``` + + + +### Cancellation scopes and callbacks + +Callbacks are not particularly useful in Workflows because all meaningful asynchronous operations return promises. In +the rare case that code uses callbacks and needs to handle cancellation, a callback can consume the +`CancellationScope.cancelRequested` promise. + + + +[packages/test/src/workflows/cancellation-scopes-with-callbacks.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/cancellation-scopes-with-callbacks.ts) + +```ts +import { CancellationScope } from '@temporalio/workflow'; + +function doSomething(callback: () => any) { + setTimeout(callback, 10); +} + +export async function cancellationScopesWithCallbacks(): Promise { + await new Promise((resolve, reject) => { + doSomething(resolve); + CancellationScope.current().cancelRequested.catch(reject); + }); +} +``` + + + +### Nesting cancellation scopes + +You can achieve complex flows by nesting cancellation scopes. + + + +[packages/test/src/workflows/nested-cancellation.ts](https://github.com/temporalio/sdk-typescript/blob/main/packages/test/src/workflows/nested-cancellation.ts) + +```ts +import { CancellationScope, proxyActivities, isCancellation } from '@temporalio/workflow'; + +import type * as activities from '../activities'; + +const { setup, httpPostJSON, cleanup } = proxyActivities({ + startToCloseTimeout: '10m', +}); + +export async function nestedCancellation(url: string): Promise { + await CancellationScope.cancellable(async () => { + await CancellationScope.nonCancellable(() => setup()); + try { + await CancellationScope.withTimeout(1000, () => httpPostJSON(url, { some: 'data' })); + } catch (err) { + if (isCancellation(err)) { + await CancellationScope.nonCancellable(() => cleanup(url)); + } + throw err; + } + }); +} +``` + + + +### Sharing promises between scopes + +Operations like Timers and Activities are cancelled by the cancellation scope they were created in. Promises returned by +these operations can be awaited in different scopes. + + + +[activities-cancellation-heartbeating/src/cancellation-scopes.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-cancellation-heartbeating/src/cancellation-scopes.ts) + +```ts +export async function sharedScopes(): Promise { + // Start activities in the root scope + const p1 = httpGetJSON('http://url1.ninja'); + const p2 = httpGetJSON('http://url2.ninja'); + + const scopePromise = CancellationScope.cancellable(async () => { + const first = await Promise.race([p1, p2]); + // Does not cancel activity1 or activity2 as they're linked to the root scope + CancellationScope.current().cancel(); + return first; + }); + return await scopePromise; + // The Activity that did not complete will effectively be cancelled when + // Workflow completes unless the Activity is awaited: + // await Promise.all([p1, p2]); +} +``` + + + + + +[activities-cancellation-heartbeating/src/cancellation-scopes.ts](https://github.com/temporalio/samples-typescript/blob/main/activities-cancellation-heartbeating/src/cancellation-scopes.ts) + +```ts +export async function shieldAwaitedInRootScope(): Promise { + let p: Promise | undefined = undefined; + + await CancellationScope.nonCancellable(async () => { + p = httpGetJSON('http://example.com'); // <-- Start activity in nonCancellable scope without awaiting completion + }); + // Activity is shielded from cancellation even though it is awaited in the cancellable root scope + return p; +} +``` + + \ No newline at end of file diff --git a/docs/develop/typescript/cancellation.mdx b/docs/develop/typescript/workflows/cancellation.mdx similarity index 99% rename from docs/develop/typescript/cancellation.mdx rename to docs/develop/typescript/workflows/cancellation.mdx index b02fb7b441..f05722056d 100644 --- a/docs/develop/typescript/cancellation.mdx +++ b/docs/develop/typescript/workflows/cancellation.mdx @@ -1,8 +1,8 @@ --- id: cancellation -title: Interrupt a Workflow - TypeScript SDK -sidebar_label: Interrupt a Workflow -slug: /develop/typescript/cancellation +title: Cancel a Workflow - TypeScript SDK +sidebar_label: Cancellation +slug: /develop/typescript/workflows/cancellation keywords: - cancellation tags: diff --git a/docs/develop/typescript/child-workflows.mdx b/docs/develop/typescript/workflows/child-workflows.mdx similarity index 98% rename from docs/develop/typescript/child-workflows.mdx rename to docs/develop/typescript/workflows/child-workflows.mdx index 2733c4aa96..3492d19441 100644 --- a/docs/develop/typescript/child-workflows.mdx +++ b/docs/develop/typescript/workflows/child-workflows.mdx @@ -2,7 +2,7 @@ id: child-workflows title: Child Workflows - TypeScript SDK sidebar_label: Child Workflows -slug: /develop/typescript/child-workflows +slug: /develop/typescript/workflows/child-workflows toc_max_heading_level: 2 keywords: - child-workflows @@ -91,7 +91,7 @@ Two advanced options are unique to Child Workflows: - [cancellationType](https://typescript.temporal.io/api/enums/proto.coresdk.child_workflow.ChildWorkflowCancellationType): Controls when to throw the `CanceledFailure` exception when a Child Workflow is canceled. - `parentClosePolicy`: Explained in the next section. -If you need to cancel a Child Workflow Execution, use [cancellation scopes](/develop/typescript/core-application#cancellation-scopes). +If you need to cancel a Child Workflow Execution, use [cancellation scopes](/develop/typescript/workflows/cancellation-scopes). A Child Workflow Execution is automatically cancelled when its containing scope is cancelled. ### How to set a Parent Close Policy {#parent-close-policy} diff --git a/docs/develop/typescript/continue-as-new.mdx b/docs/develop/typescript/workflows/continue-as-new.mdx similarity index 100% rename from docs/develop/typescript/continue-as-new.mdx rename to docs/develop/typescript/workflows/continue-as-new.mdx diff --git a/docs/develop/typescript/enriching-ui.mdx b/docs/develop/typescript/workflows/enriching-ui.mdx similarity index 100% rename from docs/develop/typescript/enriching-ui.mdx rename to docs/develop/typescript/workflows/enriching-ui.mdx diff --git a/docs/develop/typescript/workflows/index.mdx b/docs/develop/typescript/workflows/index.mdx new file mode 100644 index 0000000000..875d145d35 --- /dev/null +++ b/docs/develop/typescript/workflows/index.mdx @@ -0,0 +1,31 @@ +--- +id: index +title: Workflows - TypeScript SDK +sidebar_label: Workflows +description: + This section explains how to implement Workers with the TypeScript SDK +toc_max_heading_level: 4 +keywords: + - typescript +tags: + - TypeScript SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![TypeScript SDK Banner](/img/assets/banner-typescript-temporal.png) + +## Workflows + +- [Basic Workflow](/develop/typescript/workflows/basic-workflow) +- [Workflow execution](/develop/typescript/workflows/workflow-execution) +- [Continue-As-New](/develop/typescript/workflows/continue-as-new) +- [Timeouts](/develop/typescript/workflows/timeouts) +- [Cancellation](/develop/typescript/workflows/cancellation) +- [Cancellation scopes](/develop/typescript/workflows/cancellation-scopes) +- [Message Passing](/develop/typescript/workflows/message-passing) +- [Child Workflows](/develop/typescript/workflows/child-workflows) +- [Schedules](/develop/typescript/workflows/schedules) +- [Timers](/develop/typescript/workflows/timers) +- [Enriching the UI](/develop/typescript/workflows/enriching-ui) diff --git a/docs/develop/typescript/message-passing.mdx b/docs/develop/typescript/workflows/message-passing.mdx similarity index 99% rename from docs/develop/typescript/message-passing.mdx rename to docs/develop/typescript/workflows/message-passing.mdx index e522bc37ae..cf775dd1f5 100644 --- a/docs/develop/typescript/message-passing.mdx +++ b/docs/develop/typescript/workflows/message-passing.mdx @@ -361,7 +361,7 @@ For open source server users, Temporal Server version [Temporal Server version 1 ::: [Update-with-Start](/sending-messages#update-with-start) lets you -[send an Update](/develop/typescript/message-passing#send-update-from-client) that checks whether an already-running Workflow with that ID exists: +[send an Update](/develop/typescript/workflows/message-passing#send-update-from-client) that checks whether an already-running Workflow with that ID exists: - If the Workflow exists, the Update is processed. - If the Workflow does not exist, a new Workflow Execution is started with the given ID, and the Update is processed before the main Workflow method starts to execute. diff --git a/docs/develop/typescript/schedules.mdx b/docs/develop/typescript/workflows/schedules.mdx similarity index 99% rename from docs/develop/typescript/schedules.mdx rename to docs/develop/typescript/workflows/schedules.mdx index c21ab1f9e1..bfc093a246 100644 --- a/docs/develop/typescript/schedules.mdx +++ b/docs/develop/typescript/workflows/schedules.mdx @@ -2,7 +2,7 @@ id: schedules title: Schedules - TypeScript SDK sidebar_label: Schedules -slug: /develop/typescript/schedules +slug: /develop/typescript/workflows/schedules toc_max_heading_level: 2 keywords: - schedules diff --git a/docs/develop/typescript/workflows/timeouts.mdx b/docs/develop/typescript/workflows/timeouts.mdx new file mode 100644 index 0000000000..5f671118e4 --- /dev/null +++ b/docs/develop/typescript/workflows/timeouts.mdx @@ -0,0 +1,160 @@ +--- +id: timeouts +title: Workflow Timeouts - TypeScript SDK +sidebar_label: Timeouts +description: Optimize Workflow Execution with Temporal TypeScript SDK - Set Workflow Timeouts and Retry Policies efficiently. +toc_max_heading_level: 4 +keywords: + - typescript + - failure detection + - timeouts +tags: + - Activities + - Workflows + - Errors + - Failures + - TypeScript SDK + - Temporal SDKs +--- + +This page shows how to do the following: + +- [Raise and Handle Exceptions](#exception-handling) +- [Deliberately Fail Workflows](#workflow-failure) +- [Workflow Timeouts](#workflow-timeouts) +- [Workflow retries](#workflow-retries) + +## Raise and Handle Exceptions {#exception-handling} + +In each Temporal SDK, error handling is implemented idiomatically, following the conventions of the language. +Temporal uses several different error classes internally — for example, [`CancelledFailure`](https://typescript.temporal.io/api/classes/common.CancelledFailure) in the Typescript SDK, to handle a Workflow cancellation. +You should not raise or otherwise implement these manually, as they are tied to Temporal platform logic. + +The one Temporal error class that you will typically raise deliberately is [`ApplicationFailure`](https://typescript.temporal.io/api/classes/common.ApplicationFailure). +In fact, *any* other exceptions that are raised from your Typescript code in a Temporal Activity will be converted to an `ApplicationError` internally. +This way, an error's type, severity, and any additional details can be sent to the Temporal Service, indexed by the Web UI, and even serialized across language boundaries. + +In other words, these two code samples do the same thing: + +```typescript +class InvalidChargeError extends Error { + constructor(message: string) { + super(message); + this.name = "InvalidChargeError"; + Object.setPrototypeOf(this, CustomError.prototype); + } +} + +if (chargeAmount < 0) { + throw new InvalidChargeError(`Invalid charge amount: ${chargeAmount} (must be above zero)`); +} +``` + +```typescript +if (chargeAmount < 0) { + throw ApplicationFailure.create({ + message: `Invalid charge amount: ${chargeAmount} (must be above zero)`, + type: 'InvalidChargeError', + }); +} +``` + +Depending on your implementation, you may decide to use either method. +One reason to use the Temporal `ApplicationFailure` class is because it allows you to set an additional `non_retryable` parameter. +This way, you can decide whether an error should not be retried automatically by Temporal. +This can be useful for deliberately failing a Workflow due to bad input data, rather than waiting for a timeout to elapse: + +```typescript +if (chargeAmount < 0) { + throw ApplicationFailure.create({ + message: `Invalid charge amount: ${chargeAmount} (must be above zero)`, + nonRetryable: true + }); +} +``` + +You can alternately specify a list of errors that are non-retryable in your Activity [Retry Policy](#activity-retries). + +## Failing Workflows {#workflow-failure} + +One of the core design principles of Temporal is that an Activity Failure will never directly cause a Workflow Failure — a Workflow should never return as Failed unless deliberately. +The default retry policy associated with Temporal Activities is to retry them until reaching a certain timeout threshold. +Activities will not actually *return* a failure to your Workflow until this condition or another non-retryable condition is met. +At this point, you can decide how to handle an error returned by your Activity the way you would in any other program. +For example, you could implement a [Saga Pattern](https://github.com/temporalio/samples-typescript/tree/main/saga) that uses `try` and `catch` blocks to "unwind" some of the steps your Workflow has performed up to the point of Activity Failure. + +**You will only fail a Workflow by manually raising an `ApplicationFailure` from the Workflow code.** +You could do this in response to an Activity Failure, if the failure of that Activity means that your Workflow should not continue: + +```typescript +try { + await addAddress(); +} catch (err) { + if (err instanceof ActivityFailure && err.cause instanceof ApplicationFailure) { + log.error(err.cause.message); + throw err; + } +} +``` + +This works differently in a Workflow than raising exceptions from Activities. +In an Activity, any Typescript exceptions or custom exceptions are converted to a Temporal `ApplicationFailure`. +In a Workflow, any exceptions that are raised other than an explicit Temporal `ApplicationFailure` will only fail that particular [Workflow Task](https://docs.temporal.io/tasks#workflow-task-execution) and be retried. +This includes any typical Typescript runtime errors like an `undefined` error that are raised automatically. +These errors are treated as bugs that can be corrected with a fixed deployment, rather than a reason for a Temporal Workflow Execution to return unexpectedly. + +## Workflow Timeouts {#workflow-timeouts} + +**How to set Workflow Timeouts using the Temporal TypeScript SDK** + +Each Workflow timeout controls the maximum duration of a different aspect of a Workflow Execution. + +Before we continue, we want to note that we generally do not recommend setting Workflow Timeouts, because Workflows are designed to be long-running and resilient. +Instead, setting a Timeout can limit its ability to handle unexpected delays or long-running processes. +If you need to perform an action inside your Workflow after a specific period of time, we recommend using a Timer. + +Workflow Timeouts are set when starting a Workflow using either the Client or Workflow API. + +- **[Workflow Execution Timeout](/encyclopedia/detecting-workflow-failures#workflow-execution-timeout)** - restricts the maximum amount of time that a single Workflow Execution can be executed +- **[Workflow Run Timeout](/encyclopedia/detecting-workflow-failures#workflow-run-timeout):** restricts the maximum amount of time that a single Workflow Run can last +- **[Workflow Task Timeout](/encyclopedia/detecting-workflow-failures#workflow-task-timeout):** restricts the maximum amount of time that a Worker can execute a Workflow Task + +The following properties can be set on the [`WorkflowOptions`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/) when starting a Workflow using either the Client or Workflow API: + +- [`workflowExecutionTimeout​`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/#workflowexecutiontimeout) +- [`workflowRunTimeout`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/#workflowruntimeout) +- [`workflowTaskTimeout`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/#workflowtasktimeout) + +```typescript +await client.workflow.start(example, { + taskQueue, + workflowId, + // Set Workflow Timeout duration + workflowExecutionTimeout: '1 day', + // workflowRunTimeout: '1 minute', + // workflowTaskTimeout: '30 seconds', +}); +``` + +## Workflow retries {#workflow-retries} + +**How to set Workflow retries using the Temporal TypeScript SDK** + +A Retry Policy can work in cooperation with the timeouts to provide fine controls to optimize the execution experience. + +Use a [Retry Policy](/encyclopedia/retry-policies) to retry a Workflow Execution in the event of a failure. + +Workflow Executions do not retry by default, and Retry Policies should be used with Workflow Executions only in certain situations. + +The Retry Policy can be set through the [`WorkflowOptions.retry`](https://typescript.temporal.io/api/interfaces/client.WorkflowOptions/#retry) property when starting a Workflow using either the Client or Workflow API. + +```typescript +const handle = await client.workflow.start(example, { + taskQueue, + workflowId, + retry: { + maximumAttempts: 3, + maximumInterval: '30 seconds', + }, +}); +``` \ No newline at end of file diff --git a/docs/develop/typescript/timers.mdx b/docs/develop/typescript/workflows/timers.mdx similarity index 99% rename from docs/develop/typescript/timers.mdx rename to docs/develop/typescript/workflows/timers.mdx index 3237989e02..a5e3d67abf 100644 --- a/docs/develop/typescript/timers.mdx +++ b/docs/develop/typescript/workflows/timers.mdx @@ -2,7 +2,7 @@ id: timers title: Durable Timers - TypeScript SDK sidebar_label: Durable Timers -slug: /develop/typescript/timers +slug: /develop/typescript/workflows/timers toc_max_heading_level: 2 keywords: - timers diff --git a/docs/develop/typescript/versioning.mdx b/docs/develop/typescript/workflows/versioning.mdx similarity index 97% rename from docs/develop/typescript/versioning.mdx rename to docs/develop/typescript/workflows/versioning.mdx index 1b99ad5ed6..1acd189d58 100644 --- a/docs/develop/typescript/versioning.mdx +++ b/docs/develop/typescript/workflows/versioning.mdx @@ -2,7 +2,7 @@ id: versioning title: Versioning - TypeScript SDK sidebar_label: Versioning -slug: /develop/typescript/versioning +slug: /develop/typescript/workflows/versioning description: Temporal TypeScript SDK ensures deterministic Workflow code with versioning features like Workflow Patching APIs, Worker Build IDs, and Workflow migration strategies. toc_max_heading_level: 4 keywords: @@ -31,7 +31,7 @@ There are two primary Versioning methods that you can use: - [Versioning with Patching](#patching). This method works by adding branches to your code tied to specific revisions. It applies a code change to new Workflow Executions while avoiding disruptive changes to in-progress Workflow Executions. :::danger -Support for the experimental Worker Versioning method before 2025 will be removed from Temporal Server in March 2026. Refer to the [latest Worker Versioning docs](/worker-versioning) for guidance. You can still refer to the [Worker Versioning Legacy](worker-versioning-legacy) docs if needed. +Support for the experimental Worker Versioning method before 2025 will be removed from Temporal Server in March 2026. Refer to the [latest Worker Versioning docs](/worker-versioning) for guidance. You can still refer to the [Worker Versioning Legacy](/develop/typescript/worker-versioning-legacy) docs if needed. ::: ## Versioning with Patching {#patching} @@ -154,7 +154,7 @@ This method also does not provide a way to version any still-running Workflows - ### Testing a Workflow for replay safety -To determine whether your Workflow your needs a patch, or that you've patched it successfully, you should incorporate [Replay Testing](/develop/typescript/testing-suite#replay). +To determine whether your Workflow your needs a patch, or that you've patched it successfully, you should incorporate [Replay Testing](/develop/typescript/best-practices/testing-suite#replay). ## Worker Versioning diff --git a/docs/develop/typescript/workflows/workflow-execution.mdx b/docs/develop/typescript/workflows/workflow-execution.mdx new file mode 100644 index 0000000000..2ef382f539 --- /dev/null +++ b/docs/develop/typescript/workflows/workflow-execution.mdx @@ -0,0 +1,215 @@ +--- +id: workflow-execution +title: Workflow execution - TypeScript SDK +description: Shows how to do Workflow execution with the TypeScript SDK +sidebar_label: Workflow execution +slug: /develop/typescript/workflows/workflow-execution +toc_max_heading_level: 3 +tags: + - TypeScript SDK + - Temporal SDKs + - Workflow +--- + +## How to start a Workflow Execution {#start-workflow-execution} + +[Workflow Execution](/workflow-execution) semantics rely on several parameters—that is, to start a Workflow Execution +you must supply a Task Queue that will be used for the Tasks (one that a Worker is polling), the Workflow Type, +language-specific contextual data, and Workflow Function parameters. + +In the examples below, all Workflow Executions are started using a Temporal Client. To spawn Workflow Executions from +within another Workflow Execution, use either the [Child Workflow](/develop/typescript/workflows/child-workflows) or External +Workflow APIs. + +See the [Customize Workflow Type](#workflow-type) section to see how to customize the name of the Workflow Type. + +A request to spawn a Workflow Execution causes the Temporal Service to create the first Event +([WorkflowExecutionStarted](/references/events#workflowexecutionstarted)) in the Workflow Execution Event History. The +Temporal Service then creates the first Workflow Task, resulting in the first +[WorkflowTaskScheduled](/references/events#workflowtaskscheduled) Event. + +When you have a Client, you can schedule the start of a Workflow with `client.workflow.start()`, specifying +`workflowId`, `taskQueue`, and `args` and returning a Workflow handle immediately after the Server acknowledges the +receipt. + +```typescript +const handle = await client.workflow.start(example, { + workflowId: 'your-workflow-id', + taskQueue: 'your-task-queue', + args: ['argument01', 'argument02', 'argument03'], // this is typechecked against workflowFn's args +}); +const handle = client.getHandle(workflowId); +const result = await handle.result(); +``` + +Calling `client.workflow.start()` and `client.workflow.execute()` send a command to Temporal Server to schedule a new +Workflow Execution on the specified Task Queue. It does not actually start until a Worker that has a matching Workflow +Type, polling that Task Queue, picks it up. + +You can test this by executing a Client command without a matching Worker. Temporal Server records the command in Event +History, but does not make progress with the Workflow Execution until a Worker starts polling with a matching Task Queue +and Workflow Definition. + +Workflow Execution run in a separate V8 isolate context in order to provide a +[deterministic runtime](/workflow-definition#deterministic-constraints). + +### How to set a Workflow's Task Queue {#set-task-queue} + +In most SDKs, the only Workflow Option that must be set is the name of the [Task Queue](/task-queue). + +For any code to execute, a Worker Process must be running that contains a Worker Entity that is polling the same Task +Queue name. + +A Task Queue is a dynamic queue in Temporal polled by one or more Workers. + +Workers bundle Workflow code and node modules using Webpack v5 and execute them inside V8 isolates. Activities are +directly required and run by Workers in the Node.js environment. + +Workers are flexible. You can host any or all of your Workflows and Activities on a Worker, and you can host multiple +Workers on a single machine. + +The Worker need three main things: + +- `taskQueue`: The Task Queue to poll. This is the only required argument. +- `activities`: Optional. Imported and supplied directly to the Worker. +- Workflow bundle. Choose one of the following options: + - Specify `workflowsPath` pointing to your `workflows.ts` file to pass to Webpack; for example, + `require.resolve('./workflows')`. Workflows are bundled with their dependencies. + - If you prefer to handle the bundling yourself, pass a prebuilt bundle to `workflowBundle`. + +```ts +import { Worker } from '@temporalio/worker'; +import * as activities from './activities'; + +async function run() { + // Step 1: Register Workflows and Activities with the Worker and connect to + // the Temporal server. + const worker = await Worker.create({ + workflowsPath: require.resolve('./workflows'), + activities, + taskQueue: 'hello-world', + }); + // Worker connects to localhost by default and uses console.error for logging. + // Customize the Worker by passing more options to create(): + // https://typescript.temporal.io/api/classes/worker.Worker + // If you need to configure server connection parameters, see docs: + // /typescript/security#encryption-in-transit-with-mtls + + // Step 2: Start accepting tasks on the `tutorial` queue + await worker.run(); +} + +run().catch((err) => { + console.error(err); + process.exit(1); +}); +``` + +`taskQueue` is the only required option; however, use `workflowsPath` and `activities` to register Workflows and +Activities with the Worker. + +When scheduling a Workflow, you must specify `taskQueue`. + +```ts +import { Client, Connection } from '@temporalio/client'; +// This is the code that is used to start a Workflow. +const connection = await Connection.create(); +const client = new Client({ connection }); +const result = await client.workflow.execute(yourWorkflow, { + // required + taskQueue: 'your-task-queue', + // required + workflowId: 'your-workflow-id', +}); +``` + +When creating a Worker, you must pass the `taskQueue` option to the `Worker.create()` function. + +```ts +const worker = await Worker.create({ + // imported elsewhere + activities, + taskQueue: 'your-task-queue', +}); +``` + +Optionally, in Workflow code, when calling an Activity, you can specify the Task Queue by passing the `taskQueue` option +to `proxyActivities()`, `startChild()`, or `executeChild()`. If you do not specify `taskQueue`, the TypeScript SDK +places Activity and Child Workflow Tasks in the same Task Queue as the Workflow Task Queue. + +### How to set a Workflow Id {#workflow-id} + +Although it is not required, we recommend providing your own +[Workflow Id](/workflow-execution/workflowid-runid#workflow-id)that maps to a business process or business entity +identifier, such as an order identifier or customer identifier. + +Connect to a Client with `client.workflow.start()` and any arguments. Then specify your `taskQueue` and set your +`workflowId` to a meaningful business identifier. + +```typescript +const handle = await client.workflow.start(example, { + workflowId: 'yourWorkflowId', + taskQueue: 'yourTaskQueue', + args: ['your', 'arg', 'uments'], +}); +``` + +This starts a new Client with the given Workflow Id, Task Queue name, and an argument. + +### How to get the results of a Workflow Execution {#get-workflow-results} + +If the call to start a Workflow Execution is successful, you will gain access to the Workflow Execution's Run Id. + +The Workflow Id, Run Id, and Namespace may be used to uniquely identify a Workflow Execution in the system and get its +result. + +It's possible to both block progress on the result (synchronous execution) or get the result at some other point in time +(asynchronous execution). + +In the Temporal Platform, it's also acceptable to use Queries as the preferred method for accessing the state and +results of Workflow Executions. + +To return the results of a Workflow Execution: + +```typescript +return 'Completed ' + wf.workflowInfo().workflowId + ', Total Charged: ' + totalCharged; +``` + +`totalCharged` is just a function declared in your code. For a full example, see +[subscription-workflow-project-template-typescript/src/workflows.ts](https://github.com/temporalio/subscription-workflow-project-template-typescript/blob/main/src/workflows.ts). + +A Workflow function may return a result. If it doesn't (in which case the return type is `Promise`), the result +will be `undefined`. + +If you started a Workflow with `client.workflow.start()`, you can choose to wait for the result anytime with +`handle.result()`. + +```typescript +const handle = client.getHandle(workflowId); +const result = await handle.result(); +``` + +Using a Workflow Handle isn't necessary with `client.workflow.execute()`. + +Workflows that prematurely end will throw a `WorkflowFailedError` if you call `result()`. + +If you call `result()` on a Workflow that prematurely ended for some reason, it throws a +[`WorkflowFailedError` error](https://typescript.temporal.io/api/classes/client.WorkflowFailedError/) that reflects the +reason. For that reason, it is recommended to catch that error. + +```typescript +const handle = client.getHandle(workflowId); +try { + const result = await handle.result(); +} catch (err) { + if (err instanceof WorkflowFailedError) { + throw new Error('Temporal workflow failed: ' + workflowId, { + cause: err, + }); + } else { + throw new Error('error from Temporal workflow ' + workflowId, { + cause: err, + }); + } +} +``` \ No newline at end of file diff --git a/docs/encyclopedia/activities/activity-definition.mdx b/docs/encyclopedia/activities/activity-definition.mdx index 5acbfba998..2372f01ccd 100644 --- a/docs/encyclopedia/activities/activity-definition.mdx +++ b/docs/encyclopedia/activities/activity-definition.mdx @@ -37,7 +37,7 @@ Activities encapsulate business logic that is prone to failure, allowing for aut - [How to develop an Activity Definition using the Java SDK](/develop/java/core-application#develop-activities) - [How to develop an Activity Definition using the PHP SDK](/develop/php/core-application#develop-activities) - [How to develop an Activity Definition using the Python SDK](/develop/python/core-application#develop-activities) -- [How to develop an Activity Definition using the TypeScript SDK](/develop/typescript/core-application#develop-activities) +- [How to develop an Activity Definition using the TypeScript SDK](/develop/typescript/activities) - [How to develop an Activity Definition using the .NET SDK](/develop/dotnet/core-application#develop-activity) The term 'Activity Definition' is used to refer to the full set of primitives in any given language SDK that provides an access point to an Activity Function Definition——the method or function that is invoked for an [Activity Task Execution](/tasks#activity-task-execution). diff --git a/docs/encyclopedia/activities/activity-execution.mdx b/docs/encyclopedia/activities/activity-execution.mdx index 5696d66a19..7610c3fc63 100644 --- a/docs/encyclopedia/activities/activity-execution.mdx +++ b/docs/encyclopedia/activities/activity-execution.mdx @@ -35,7 +35,7 @@ An Activity Execution is the full chain of [Activity Task Executions](/tasks#act - [How to start an Activity Execution using the Java SDK](/develop/java/core-application#activity-execution) - [How to start an Activity Execution using the PHP SDK](/develop/php/core-application#activity-execution) - [How to start an Activity Execution using the Python SDK](/develop/python/core-application#activity-execution) -- [How to start an Activity Execution using the TypeScript SDK](/develop/typescript/core-application#activity-execution) +- [How to start an Activity Execution using the TypeScript SDK](/develop/typescript/activities/execution) - [How to start an Activity Execution using the .NET SDK](/develop/dotnet/core-application#activity-execution) ::: @@ -125,7 +125,7 @@ How to complete an Activity Asynchronously in: - [Java](/develop/java/asynchronous-activity-completion) - [PHP](/develop/php/asynchronous-activity-completion) - [Python](/develop/python/asynchronous-activity-completion) -- [TypeScript](/develop/typescript/asynchronous-activity-completion) +- [TypeScript](/develop/typescript/activities/asynchronous-activity-completion) - [.NET](/develop/dotnet/asynchronous-activity) ### When to use Async Completion diff --git a/docs/encyclopedia/child-workflows/child-workflows.mdx b/docs/encyclopedia/child-workflows/child-workflows.mdx index c6881a77ee..d1b754f808 100644 --- a/docs/encyclopedia/child-workflows/child-workflows.mdx +++ b/docs/encyclopedia/child-workflows/child-workflows.mdx @@ -34,7 +34,7 @@ A Child Workflow Execution is a [Workflow Execution](/workflow-execution) that i - [Java SDK Child Workflow feature guide](/develop/java/child-workflows) - [PHP SDK Child Workflow feature guide](/develop/php/child-workflows) - [Python SDK Child Workflow feature guide](/develop/python/child-workflows) -- [TypeScript SDK Child Workflow feature guide](/develop/typescript/child-workflows) +- [TypeScript SDK Child Workflow feature guide](/develop/typescript/workflows/child-workflows) - [.NET SDK Child Workflow feature guide](/develop/dotnet/child-workflows) A Workflow Execution can be both a Parent and a Child Workflow Execution because any Workflow can spawn another Workflow. diff --git a/docs/encyclopedia/child-workflows/parent-close-policy.mdx b/docs/encyclopedia/child-workflows/parent-close-policy.mdx index 8f75186071..54ac27fdbf 100644 --- a/docs/encyclopedia/child-workflows/parent-close-policy.mdx +++ b/docs/encyclopedia/child-workflows/parent-close-policy.mdx @@ -39,7 +39,7 @@ A Parent Close Policy determines what happens to a Child Workflow Execution if i - [How to set a Parent Close Policy using the Java SDK](/develop/java/child-workflows#parent-close-policy) - [How to set a Parent Close Policy using the PHP SDK](/develop/php/child-workflows#parent-close-policy) - [How to set a Parent Close Policy using the Python SDK](/develop/python/child-workflows#parent-close-policy) -- [How to set a Parent Close Policy using the TypeScript SDK](/develop/typescript/child-workflows#parent-close-policy) +- [How to set a Parent Close Policy using the TypeScript SDK](/develop/typescript/workflows/child-workflows#parent-close-policy) - [How to set a Parent Close Policy using the .NET SDK](/develop/dotnet/child-workflows#parent-close-policy) There are three possible values: diff --git a/docs/encyclopedia/detecting-activity-failures.mdx b/docs/encyclopedia/detecting-activity-failures.mdx index 9a2355d719..46a946ac42 100644 --- a/docs/encyclopedia/detecting-activity-failures.mdx +++ b/docs/encyclopedia/detecting-activity-failures.mdx @@ -36,7 +36,7 @@ In other words, it's a limit for how long an Activity Task can be enqueued. - + @@ -83,7 +83,7 @@ A Start-To-Close Timeout is the maximum time allowed for a single [Activity Task - + @@ -135,7 +135,7 @@ A Schedule-To-Close Timeout is the maximum amount of time allowed for the overal - + @@ -175,7 +175,7 @@ Each ping informs the Temporal Service that the Activity Execution is making pro - + @@ -257,7 +257,7 @@ A Heartbeat Timeout is the maximum time between [Activity Heartbeats](#activity- - + diff --git a/docs/encyclopedia/detecting-workflow-failures.mdx b/docs/encyclopedia/detecting-workflow-failures.mdx index 0bd8267c03..e9b8326627 100644 --- a/docs/encyclopedia/detecting-workflow-failures.mdx +++ b/docs/encyclopedia/detecting-workflow-failures.mdx @@ -38,7 +38,7 @@ A Workflow Execution Timeout is the maximum time that a Workflow Execution can b - + @@ -66,7 +66,7 @@ A Workflow Run Timeout restricts the maximum duration of a single Workflow Run. - + @@ -92,7 +92,7 @@ This Timeout is primarily available to recognize whether a Worker has gone down - + diff --git a/docs/encyclopedia/event-history/typescript.mdx b/docs/encyclopedia/event-history/typescript.mdx index e47d6f7a77..9731d4cdeb 100644 --- a/docs/encyclopedia/event-history/typescript.mdx +++ b/docs/encyclopedia/event-history/typescript.mdx @@ -314,7 +314,7 @@ considered a [Workflow Task Failure](https://docs.temporal.io/references/failure considered a transient failure, meaning it retries over and over. Users can also fix the source of non-determinism, perhaps by removing the Activity, and then restart the Workers. This means that this type of failure can recover by itself. You can also use a strategy called versioning to address this non-determinism error. See -[versioning](https://docs.temporal.io/develop/typescript/versioning) to learn more. +[versioning](https://docs.temporal.io/develop/typescript/workflows/versioning) to learn more. For more information on how Temporal handles Durable Execution or to see these slides in a video format with more explanation, check out our free, self-paced courses: [Temporal 102](https://learn.temporal.io/courses/temporal_102/) and diff --git a/docs/encyclopedia/retry-policies.mdx b/docs/encyclopedia/retry-policies.mdx index ae019e3559..19ffaaf0d9 100644 --- a/docs/encyclopedia/retry-policies.mdx +++ b/docs/encyclopedia/retry-policies.mdx @@ -67,7 +67,7 @@ Try out the [Activity retry simulator](/develop/activity-retry-simulator) to vis archetype="feature-guide" /> @@ -97,7 +97,7 @@ Try out the [Activity retry simulator](/develop/activity-retry-simulator) to vis archetype="feature-guide" /> @@ -389,7 +389,7 @@ timeouts. For an Activity, its Schedule-to-Close Timeout applies. For a Workflow archetype="feature-guide" /> diff --git a/docs/encyclopedia/temporal-sdks.mdx b/docs/encyclopedia/temporal-sdks.mdx index 36bef5eaa7..269e71c70f 100644 --- a/docs/encyclopedia/temporal-sdks.mdx +++ b/docs/encyclopedia/temporal-sdks.mdx @@ -185,7 +185,7 @@ For tested code samples and best practices, use your preferred language SDK's de - [Java SDK Temporal Client feature guide](/develop/java/temporal-client) - [PHP SDK Temporal Client feature guide](/develop/php/temporal-client) - [Python SDK Temporal Client feature guide](/develop/python/temporal-client) -- [TypeScript SDK Temporal Client feature guide](/develop/typescript/core-application) +- [TypeScript SDK Temporal Client feature guide](/develop/typescript/client) ::: diff --git a/docs/encyclopedia/visibility/search-attributes.mdx b/docs/encyclopedia/visibility/search-attributes.mdx index 264027c984..f403a6d005 100644 --- a/docs/encyclopedia/visibility/search-attributes.mdx +++ b/docs/encyclopedia/visibility/search-attributes.mdx @@ -217,7 +217,7 @@ With Workflows you can do the following: - [How to manage Search Attributes using the Java SDK](/develop/java/observability#visibility) - [How to manage Search Attributes using the PHP SDK](/develop/php/observability#visibility) - [How to manage Search Attributes using the Python SDK](/develop/python/observability#visibility) -- [How to manage Search Attributes using the TypeScript SDK](/develop/typescript/observability#visibility) +- [How to manage Search Attributes using the TypeScript SDK](/develop/typescript/workers/observability#visibility) - [How to manage Search Attributes using the .NET SDK](/develop/dotnet/observability#search-attributes) ::: diff --git a/docs/encyclopedia/workers/task-queues.mdx b/docs/encyclopedia/workers/task-queues.mdx index f68072c482..4cd09f1ecf 100644 --- a/docs/encyclopedia/workers/task-queues.mdx +++ b/docs/encyclopedia/workers/task-queues.mdx @@ -83,7 +83,7 @@ There are five places where the name of the Task Queue can be set by the develop - [How to start a Workflow Execution using the Java SDK](/develop/java/temporal-client#start-workflow-execution) - [How to start a Workflow Execution using the PHP SDK](/develop/php/temporal-client#start-workflow-execution) - [How to start a Workflow Execution using the Python SDK](/develop/python/temporal-client#start-workflow-execution) - - [How to start a Workflow Execution using the TypeScript SDK](/develop/typescript/temporal-client#start-workflow-execution) + - [How to start a Workflow Execution using the TypeScript SDK](/develop/typescript/client/temporal-client#start-workflow-execution) - [How to start a Workflow Execution using the .NET SDK](/develop/dotnet/temporal-client#start-workflow) 2. A Task Queue name must be set when creating a Worker Entity and when running a Worker Process: @@ -92,10 +92,10 @@ There are five places where the name of the Task Queue can be set by the develop - [How to run a development Worker using the Java SDK](/develop/java/core-application#run-a-dev-worker) - [How to run a development Worker using the PHP SDK](/develop/php/core-application#run-a-dev-worker) - [How to run a development Worker using the Python SDK](/develop/python/core-application#run-a-dev-worker) - - [How to run a development Worker using the TypeScript SDK](/develop/typescript/core-application#run-a-dev-worker) + - [How to run a development Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-dev-worker) - [How to run a development Worker using the .NET SDK](/develop/dotnet/core-application#run-worker-process)

- [How to run a Temporal Cloud Worker using the Go SDK](/develop/go/core-application#run-a-temporal-cloud-worker) - - [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/core-application#run-a-temporal-cloud-worker) + - [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-temporal-cloud-worker) Note that all Worker Entities listening to the same Task Queue name must be registered to handle the exact same Workflows Types, Activity Types, and Nexus Operations. @@ -111,7 +111,7 @@ There are five places where the name of the Task Queue can be set by the develop - [How to start an Activity Execution using the Java SDK](/develop/java/core-application#activity-execution) - [How to start an Activity Execution using the PHP SDK](/develop/php/core-application#activity-execution) - [How to start an Activity Execution using the Python SDK](/develop/python/core-application#activity-execution) - - [How to start an Activity Execution using the TypeScript SDK](/develop/typescript/core-application#activity-execution) + - [How to start an Activity Execution using the TypeScript SDK](/develop/typescript/activities/execution) - [How to start an Activity Execution using the .NET SDK](/develop/dotnet/core-application#activity-execution) 4. A Task Queue name can be provided when spawning a Child Workflow Execution: @@ -123,7 +123,7 @@ There are five places where the name of the Task Queue can be set by the develop - [How to start a Child Workflow Execution using the Java SDK](/develop/java/child-workflows) - [How to start a Child Workflow Execution using the PHP SDK](/develop/php/continue-as-new) - [How to start a Child Workflow Execution using the Python SDK](/develop/python/child-workflows) - - [How to start a Child Workflow Execution using the TypeScript SDK](/develop/typescript/child-workflows) + - [How to start a Child Workflow Execution using the TypeScript SDK](/develop/typescript/workflows/child-workflows) - [How to start a Child Workflow Execution using the .NET SDK](/develop/dotnet/child-workflows) 5. A Task Queue name can be provided when creating a Nexus Endpoint. diff --git a/docs/encyclopedia/workers/workers.mdx b/docs/encyclopedia/workers/workers.mdx index 72d4529bd8..e159b290ba 100644 --- a/docs/encyclopedia/workers/workers.mdx +++ b/docs/encyclopedia/workers/workers.mdx @@ -38,11 +38,11 @@ A Worker Program is the static code that defines the constraints of the Worker P - [How to run a development Worker using the Java SDK](/develop/java/core-application#run-a-dev-worker) - [How to run a development Worker using the PHP SDK](/develop/php/core-application#run-a-dev-worker) - [How to run a development Worker using the Python SDK](/develop/python/core-application#run-a-dev-worker) -- [How to run a development Worker using the TypeScript SDK](/develop/typescript/core-application#run-a-dev-worker) +- [How to run a development Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-dev-worker) - [How to run a development Worker using the .NET SDK](/develop/dotnet/core-application#run-worker-process) - [How to run a Temporal Cloud Worker using the Go SDK](/develop/go/core-application#run-a-temporal-cloud-worker) -- [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/core-application#run-a-temporal-cloud-worker) +- [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-temporal-cloud-worker) ::: diff --git a/docs/encyclopedia/workflow-message-passing/handling-messages.mdx b/docs/encyclopedia/workflow-message-passing/handling-messages.mdx index 5987ce4675..d5d7956a98 100644 --- a/docs/encyclopedia/workflow-message-passing/handling-messages.mdx +++ b/docs/encyclopedia/workflow-message-passing/handling-messages.mdx @@ -133,7 +133,7 @@ See examples of the above patterns. - + ### Update Validators {#update-validators} @@ -156,7 +156,7 @@ Once the Update handler is finished and has returned a value, the operation is c - + @@ -199,7 +199,7 @@ Use these links to see a simple Signal handler. - + @@ -212,7 +212,7 @@ Use these links to see a simple update handler. - + @@ -225,7 +225,7 @@ Author queries using these per-language guides. - + diff --git a/docs/encyclopedia/workflow-message-passing/sending-messages.mdx b/docs/encyclopedia/workflow-message-passing/sending-messages.mdx index 70deb90c48..3c284f9f39 100644 --- a/docs/encyclopedia/workflow-message-passing/sending-messages.mdx +++ b/docs/encyclopedia/workflow-message-passing/sending-messages.mdx @@ -52,7 +52,7 @@ You can also Signal-With-Start to lazily initialize a Workflow while sending a S - + @@ -63,7 +63,7 @@ You can also Signal-With-Start to lazily initialize a Workflow while sending a S - + @@ -76,7 +76,7 @@ Signal-With-Start is a great tool for lazily initializing Workflows. When you se - + @@ -118,7 +118,7 @@ Use [Update Validators](/handling-messages#update-validators) and [Update IDs](/ - + @@ -166,7 +166,7 @@ The SDKs will retry the Update-With-Start request, but there is no guarantee tha - + @@ -181,7 +181,7 @@ You can also send a built-in "Stack Trace Query" for debugging. - + diff --git a/docs/encyclopedia/workflow/cron-job.mdx b/docs/encyclopedia/workflow/cron-job.mdx index 241f08596e..409eb6856a 100644 --- a/docs/encyclopedia/workflow/cron-job.mdx +++ b/docs/encyclopedia/workflow/cron-job.mdx @@ -32,7 +32,7 @@ A Temporal Cron Job is the series of Workflow Executions that occur when a Cron - [How to set a Cron Schedule using the Java SDK](/develop/java/schedules#cron-schedule) - [How to set a Cron Schedule using the PHP SDK](/develop/php/schedules#temporal-cron-jobs) - [How to set a Cron Schedule using the Python SDK](/develop/python/schedules#temporal-cron-jobs) -- [How to set a Cron Schedule using the TypeScript SDK](/develop/typescript/schedules#temporal-cron-jobs) +- [How to set a Cron Schedule using the TypeScript SDK](/develop/typescript/workflows/schedules#temporal-cron-jobs) -**[Workflow Definition in Typescript](/develop/typescript/core-application#develop-workflows)** +**[Workflow Definition in Typescript](/develop/typescript/workflows/basic-workflow)** ```Typescript type BasicWorkflowArgs = { @@ -291,7 +291,7 @@ To patch: - [How to patch Workflow code in Java](/develop/java/versioning#patching) - [How to patch Workflow code in Python](/develop/python/versioning#patching) - [How to patch Workflow code in PHP](/develop/php/versioning#php-sdk-patching-api) -- [How to patch Workflow code in TypeScript](/develop/typescript/versioning#patching) +- [How to patch Workflow code in TypeScript](/develop/typescript/workflows/versioning#patching) - [How to patch Workflow code in .NET](/develop/dotnet/versioning#patching) To test, see [Safe Deployments](/develop/safe-deployments.mdx). diff --git a/docs/encyclopedia/workflow/workflow-execution/continue-as-new.mdx b/docs/encyclopedia/workflow/workflow-execution/continue-as-new.mdx index 9ef1ff61e4..f20de75c61 100644 --- a/docs/encyclopedia/workflow/workflow-execution/continue-as-new.mdx +++ b/docs/encyclopedia/workflow/workflow-execution/continue-as-new.mdx @@ -40,7 +40,7 @@ Workflows that do this are often called Entity Workflows because they represent - [How to Continue-As-New using the Java SDK](/develop/java/continue-as-new) - [How to Continue-As-New using the PHP SDK](/develop/php/continue-as-new) - [How to Continue-As-New using the Python SDK](/develop/python/continue-as-new#how) -- [How to Continue-As-New using the TypeScript SDK](/develop/typescript/continue-as-new) +- [How to Continue-As-New using the TypeScript SDK](/develop/typescript/workflows/continue-as-new) - [How to Continue-As-New using the .NET SDK](/develop/dotnet/continue-as-new) ## When in your Workflow is it right to Continue-As-New? {#when} @@ -54,5 +54,5 @@ To prevent long-running Workflows from running on stale versions of code, you ma - [Determine when to Continue-As-New using the Java SDK](/develop/java/continue-as-new) - [Determine when to Continue-As-New using the PHP SDK](/develop/php/continue-as-new) - [Determine when to Continue-As-New using the Python SDK](/develop/python/continue-as-new#when) -- [Determine when to Continue-As-New using the TypeScript SDK](/develop/typescript/continue-as-new) +- [Determine when to Continue-As-New using the TypeScript SDK](/develop/typescript/workflows/continue-as-new) - [Determine when to Continue-As-New using the .NET SDK](/develop/dotnet/continue-as-new) diff --git a/docs/encyclopedia/workflow/workflow-execution/timers-delays.mdx b/docs/encyclopedia/workflow/workflow-execution/timers-delays.mdx index 2da07e0453..bf05caa2b9 100644 --- a/docs/encyclopedia/workflow/workflow-execution/timers-delays.mdx +++ b/docs/encyclopedia/workflow/workflow-execution/timers-delays.mdx @@ -27,7 +27,7 @@ Workers consume no additional resources while waiting for a Timer to fire, so a - [How to set Timers in Java](/develop/java/timers) - [How to set Timers in PHP](/develop/php/timers) - [How to set Timers in Python](/develop/python/timers) -- [How to set Timers in TypeScript](/develop/typescript/timers) +- [How to set Timers in TypeScript](/develop/typescript/workflows/timers) - [How to set Timers in .NET](/develop/dotnet/durable-timers) The duration of a Timer is fixed, and your Workflow might specify a value as short as one second or as long as several years. diff --git a/docs/encyclopedia/workflow/workflow-execution/workflow-execution.mdx b/docs/encyclopedia/workflow/workflow-execution/workflow-execution.mdx index 6c334226b4..8fbe6faff1 100644 --- a/docs/encyclopedia/workflow/workflow-execution/workflow-execution.mdx +++ b/docs/encyclopedia/workflow/workflow-execution/workflow-execution.mdx @@ -37,7 +37,7 @@ It is the main unit of execution of a [Temporal Application](/temporal#temporal- - [How to start a Workflow Execution using the Java SDK](/develop/java/temporal-client#start-workflow-execution) - [How to start a Workflow Execution using the PHP SDK](/develop/php/temporal-client#start-workflow-execution) - [How to start a Workflow Execution using the Python SDK](/develop/python/temporal-client#start-workflow-execution) -- [How to start a Workflow Execution using the TypeScript SDK](/develop/typescript/temporal-client#start-workflow-execution) +- [How to start a Workflow Execution using the TypeScript SDK](/develop/typescript/client/temporal-client#start-workflow-execution) - [How to start a Workflow Execution using the .NET SDK](/develop/dotnet/temporal-client#start-workflow) Each Temporal Workflow Execution has exclusive access to its local state. @@ -75,7 +75,7 @@ If a failure occurs, the Workflow Execution picks up where the last recorded eve - [How to use Replay APIs using the Go SDK](/develop/go/testing-suite#replay) - [How to use Replay APIs using the Java SDK](/develop/java/testing-suite#replay) - [How to use Replay APIs using the Python SDK](/develop/python/testing-suite#replay) -- [How to use Replay APIs using the TypeScript SDK](/develop/typescript/testing-suite#replay) +- [How to use Replay APIs using the TypeScript SDK](/develop/typescript/best-practices/testing-suite#replay) - [How to use Replay APIs using the .NET SDK](/develop/dotnet/testing-suite#replay) ### Commands and awaitables {#commands-awaitables} diff --git a/docs/evaluate/development-production-features/core-application.mdx b/docs/evaluate/development-production-features/core-application.mdx index 632c860040..c122aece26 100644 --- a/docs/evaluate/development-production-features/core-application.mdx +++ b/docs/evaluate/development-production-features/core-application.mdx @@ -1,7 +1,7 @@ --- id: core-application title: Core application - Temporal feature -description: Discover Temporal's Workflow, Activity, and Worker framework; orchestrate steps, encapsulate business logic, and execute code efficiently using the Temporal SDK in your favorite language. +description: Discover Temporal's Workflow, Activity, and Worker framework; orchestrate steps, encapsulate business logic, and execute code efficiently using the Temporal SDK in your favorite language. [TODO] update as part of core apps sidebar_label: Core application tags: - Workflows @@ -49,7 +49,7 @@ Or jump straight to a Temporal SDK feature guide: - + diff --git a/docs/evaluate/development-production-features/debugging.mdx b/docs/evaluate/development-production-features/debugging.mdx index ad6beaebd7..3233e97567 100644 --- a/docs/evaluate/development-production-features/debugging.mdx +++ b/docs/evaluate/development-production-features/debugging.mdx @@ -37,7 +37,7 @@ Jump straight to a Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/failure-detection.mdx b/docs/evaluate/development-production-features/failure-detection.mdx index 9ed8340540..cc776f44a5 100644 --- a/docs/evaluate/development-production-features/failure-detection.mdx +++ b/docs/evaluate/development-production-features/failure-detection.mdx @@ -45,7 +45,7 @@ Or jump straight to a Temporal SDK feature guide. - + @@ -55,7 +55,7 @@ Or jump straight to a Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/interrupt-a-workflow.mdx b/docs/evaluate/development-production-features/interrupt-a-workflow.mdx index 3732d4e353..76d2121797 100644 --- a/docs/evaluate/development-production-features/interrupt-a-workflow.mdx +++ b/docs/evaluate/development-production-features/interrupt-a-workflow.mdx @@ -55,7 +55,7 @@ robust and responsive. archetype="feature-guide" /> diff --git a/docs/evaluate/development-production-features/observability.mdx b/docs/evaluate/development-production-features/observability.mdx index e781938868..a5401dc6be 100644 --- a/docs/evaluate/development-production-features/observability.mdx +++ b/docs/evaluate/development-production-features/observability.mdx @@ -56,6 +56,6 @@ Jump straight into the Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/schedules.mdx b/docs/evaluate/development-production-features/schedules.mdx index 5a0955cd33..63188bc019 100644 --- a/docs/evaluate/development-production-features/schedules.mdx +++ b/docs/evaluate/development-production-features/schedules.mdx @@ -40,7 +40,7 @@ Jump straight to a Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/testing-suite.mdx b/docs/evaluate/development-production-features/testing-suite.mdx index f3f8b0cd2e..9c5fb8761f 100644 --- a/docs/evaluate/development-production-features/testing-suite.mdx +++ b/docs/evaluate/development-production-features/testing-suite.mdx @@ -41,7 +41,7 @@ Jump straight to a Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/throughput-composability.mdx b/docs/evaluate/development-production-features/throughput-composability.mdx index 1c11f5d923..fc71227ba2 100644 --- a/docs/evaluate/development-production-features/throughput-composability.mdx +++ b/docs/evaluate/development-production-features/throughput-composability.mdx @@ -36,7 +36,7 @@ See the SDK feature guides for implementation details: - + diff --git a/docs/evaluate/development-production-features/workflow-message-passing.mdx b/docs/evaluate/development-production-features/workflow-message-passing.mdx index 5220949f8d..a1e8713cb6 100644 --- a/docs/evaluate/development-production-features/workflow-message-passing.mdx +++ b/docs/evaluate/development-production-features/workflow-message-passing.mdx @@ -56,7 +56,7 @@ If you want to jump to straight to implementation details, see the SDK feature g - + diff --git a/docs/production-deployment/self-hosted-guide/monitoring.mdx b/docs/production-deployment/self-hosted-guide/monitoring.mdx index cb1e4b978f..7abe023a91 100644 --- a/docs/production-deployment/self-hosted-guide/monitoring.mdx +++ b/docs/production-deployment/self-hosted-guide/monitoring.mdx @@ -97,7 +97,7 @@ The Metrics section in the Observability guide details how to create hooks for a - [Java](/develop/java/observability#metrics) - [PHP](/develop/php/observability) - [Python](/develop/python/observability#metrics) -- [TypeScript](/develop/typescript/observability#metrics) +- [TypeScript](/develop/typescript/workers/observability#metrics) - [.NET](/develop/dotnet/observability#metrics) - [Ruby](/develop/ruby/observability#metrics) diff --git a/docs/references/failures.mdx b/docs/references/failures.mdx index 0d8e1a5b46..c56367b29c 100644 --- a/docs/references/failures.mdx +++ b/docs/references/failures.mdx @@ -176,7 +176,7 @@ By setting the Next Retry Delay for a given Application Failure, you can tell th This will override whatever the Retry Policy would have computed for your specific exception. Java: [NextRetryDelay](/develop/java/failure-detection#activity-next-retry-delay) -TypeScript: [nextRetryDelay](/develop/typescript/failure-detection#activity-next-retry-delay) +TypeScript: [nextRetryDelay](/develop/typescript/activities/timeouts#activity-next-retry-delay) PHP: [NextRetryDelay](/develop/php/failure-detection#activity-next-retry-delay) ### Nexus errors {#nexus-errors} diff --git a/docs/references/sdk-metrics.mdx b/docs/references/sdk-metrics.mdx index c87792e0fa..5ee141d730 100644 --- a/docs/references/sdk-metrics.mdx +++ b/docs/references/sdk-metrics.mdx @@ -30,7 +30,7 @@ The Temporal SDKs emit a set of metrics from Temporal Client usage and Worker Pr - [How to emit metrics using the Go SDK](/develop/go/observability#metrics) - [How to emit metrics using the Java SDK](/develop/java/observability#metrics) - [How to emit metrics using the Python SDK](/develop/python/observability#metrics) -- [How to emit metrics using the TypeScript SDK](/develop/typescript/observability#metrics) +- [How to emit metrics using the TypeScript SDK](/develop/typescript/workers/observability#metrics) - [How to emit metrics using the .NET SDK](/develop/dotnet/observability#metrics) - [How to emit metrics using the Ruby SDK](/develop/ruby/observability#metrics) - [How to tune Worker performance based on metrics](/develop/worker-performance) diff --git a/docs/web-ui.mdx b/docs/web-ui.mdx index aee2192560..0e6edcc1b9 100644 --- a/docs/web-ui.mdx +++ b/docs/web-ui.mdx @@ -261,7 +261,7 @@ To read more about Schedules, explore these links: diff --git a/sidebars.js b/sidebars.js index 17633d1b6e..c448273582 100644 --- a/sidebars.js +++ b/sidebars.js @@ -235,26 +235,99 @@ module.exports = { }, items: [ 'develop/typescript/set-up-your-local-typescript', - 'develop/typescript/core-application', - 'develop/typescript/temporal-client', - 'develop/typescript/namespaces', - 'develop/typescript/testing-suite', - 'develop/typescript/failure-detection', - 'develop/typescript/message-passing', - 'develop/typescript/cancellation', - 'develop/typescript/asynchronous-activity-completion', - 'develop/typescript/versioning', - 'develop/typescript/observability', - 'develop/typescript/benign-exceptions', - 'develop/typescript/enriching-ui', - 'develop/typescript/debugging', - 'develop/typescript/schedules', - 'develop/typescript/converters-and-encryption', - 'develop/typescript/timers', - 'develop/typescript/nexus', - 'develop/typescript/child-workflows', - 'develop/typescript/continue-as-new', - 'develop/typescript/interceptors', + { + type: 'category', + label: 'Activities', + collapsed: true, + link: { + type: 'doc', + id: 'develop/typescript/activities/index', + }, + items: [ + 'develop/typescript/activities/basic-activity', + 'develop/typescript/activities/execution', + 'develop/typescript/activities/timeouts', + 'develop/typescript/activities/asynchronous-activity-completion', + 'develop/typescript/activities/benign-exceptions', + ], + }, + { + type: 'category', + label: 'Workflows', + collapsed: true, + link: { + type: 'doc', + id: 'develop/typescript/workflows/index', + }, + items: [ + 'develop/typescript/workflows/basic-workflow', + 'develop/typescript/workflows/workflow-execution', + 'develop/typescript/workflows/continue-as-new', + 'develop/typescript/workflows/timeouts', + 'develop/typescript/workflows/cancellation', + 'develop/typescript/workflows/cancellation-scopes', + 'develop/typescript/workflows/message-passing', + 'develop/typescript/workflows/child-workflows', + 'develop/typescript/workflows/schedules', + 'develop/typescript/workflows/timers', + 'develop/typescript/workflows/enriching-ui', + 'develop/typescript/workflows/versioning', + ], + }, + { + type: 'category', + label: 'Workers', + collapsed: true, + link: { + type: 'doc', + id: 'develop/typescript/workers/index', + }, + items: [ + 'develop/typescript/workers/run-worker-process', + 'develop/typescript/workers/interceptors', + 'develop/typescript/workers/observability', + ], + }, + { + type: 'category', + label: 'Client', + collapsed: true, + link: { + type: 'doc', + id: 'develop/typescript/client/index', + }, + items: [ + 'develop/typescript/client/temporal-client', + 'develop/typescript/client/namespaces', + ], + }, + { + type: 'category', + label: 'Nexus', + collapsed: true, + link: { + type: 'doc', + id: 'develop/typescript/nexus/index', + }, + items: [ + 'develop/typescript/nexus/service-handler', + ], + }, + { + type: 'category', + label: 'Best practices', + collapsed: true, + link: { + type: 'doc', + id: 'develop/typescript/best-practices/index', + }, + items: [ + 'develop/typescript/best-practices/testing-suite', + 'develop/typescript/best-practices/debugging', + 'develop/typescript/best-practices/converters-and-encryption', + 'develop/typescript/best-practices/entity-pattern', + ], + }, { type: 'category', label: 'Integrations', @@ -332,6 +405,7 @@ module.exports = { 'develop/worker-performance', 'develop/safe-deployments', 'develop/plugins-guide', + 'develop/run-a-development-server', ], }, { diff --git a/vercel.json b/vercel.json index 1b0cb480bd..b5f3762d44 100644 --- a/vercel.json +++ b/vercel.json @@ -976,7 +976,7 @@ }, { "source": "/dev-guide/typescript/foundations:path*", - "destination": "/develop/typescript/core-application", + "destination": "/develop/typescript", "permanent": true }, { @@ -1275,4 +1275,4 @@ "permanent": true } ] -} \ No newline at end of file +}