Temporal TypeScript SDK
repository·main·Indexed 21 days ago
https://github.com/temporalio/sdk-typescriptA framework for authoring and executing asynchronous, long-running, and resilient business logic (Workflows and Activities) using TypeScript or JavaScript. Includes support for external storage drivers for Google Cloud Storage (GCS) and Amazon S3 for payload offloading, as well as OpenTelemetry interceptors for tracing.
What's inside Temporal TypeScript SDK
- @temporalio/nexus is a package within the Temporal TypeScript SDK. It provides specialized capabilities (Nexus) for Temporal workflows and activities. For detailed technical specifications, refer to the official API reference.
Overview of @temporalio/cloud
mainThe@temporalio/cloudpackage is a specialized component of the Temporal TypeScript SDK designed for interacting with Temporal Cloud. It provides the necessary client capabilities to manage and interact with workflows hosted on Temporal's managed cloud service.Overview of @temporalio/common
mainThe
@temporalio/commonpackage is a shared library within the Temporal TypeScript SDK. It provides core utilities and abstractions used across different components of the Temporal ecosystem, including the Client, Worker, and Workflows. Key functionalities include:- Data Converters: Managing how data is serialized and deserialized when sent between the client and the Temporal server.
- Failure Handling: Providing standardized mechanisms for handling and representing failures within Temporal workflows and activities.
Identify the correct Temporal TypeScript SDK package for your task
mainThe Temporal TypeScript SDK is modular. Depending on whether you are running code in a Worker, a Workflow, or interacting with the Temporal Server, you will need to import specific packages:
@temporalio/worker: Use this to run Workflows and Activities.@temporalio/workflow: Use this as the workflow runtime library (code that runs inside a Workflow).@temporalio/activity: Use this to access the current Activity's context.@temporalio/client: Use this to send commands to the Temporal Server (e.g., starting workflows, querying status).@temporalio/nexus: Use this to implement and invoke Nexus Operations.@temporalio/common: Provides shared utilities used across Client, Worker, and Workflow code.@temporalio/proto: Contains compiled protobuf definitions.@temporalio/testing: Provides the framework for testing Temporal code.@temporalio/interceptors-opentelemetry: Provides interceptors for adding OpenTelemetry tracing to your Temporal application.
Use @temporalio/interceptors-opentelemetry for tracing
mainThe@temporalio/interceptors-opentelemetrypackage provides Temporal TypeScript SDK interceptors that enable tracing of Workflow and Activity executions using OpenTelemetry. This allows you to capture telemetry data for Temporal operations to monitor and debug your distributed workflows.Overview of Temporal TypeScript SDK packages
mainThe SDK is distributed as a monorepo containing several specialized packages. Common packages include:
@temporalio/client: For interacting with the Temporal cluster (starting workflows, etc.).@temporalio/worker: For running Workers that host Workflows and Activities.@temporalio/workflow: For authoring Workflow logic.@temporalio/activity: For authoring Activity logic.@temporalio/common: Shared utilities and types.@temporalio/testing: For testing Temporal code.@temporalio/create: For project scaffolding.
Important notice regarding @temporalio/core-bridge usage
mainThe@temporalio/core-bridgepackage is an internal component of the Temporal TypeScript SDK. It is not intended to be used directly by end-users. Any APIs provided by this package are considered internal and are subject to change without notice. Developers should use the high-level, public packages provided by the Temporal TypeScript SDK instead.GCS Object Name Specification and Encoding
mainThe Temporal SDK generates GCS object names using a consistent format.
Object Name Formats
- Workflow:
v0/ns/{namespace}/wt/{workflow-type}/wi/{workflow-id}/ri/{run-id}/d/{hash-algorithm}/{hex-digest} - Activity:
v0/ns/{namespace}/at/{activity-type}/ai/{activity-id}/ri/{run-id}/d/{hash-algorithm}/{hex-digest} - Fallback:
v0/d/{hash-algorithm}/{hex-digest}(used when namespace, workflow, or activity info is unavailable)
Encoding Rules
To ensure compatibility with Google Cloud Storage, the SDK percent-encodes the following:
- Control characters (U+0000–U+001F, U+007F–U+009F)
- The discouraged set:
# [ ] * ? : " < > | - Forward slash (
/) to prevent unintended path segments - Percent (
%) to ensure reversible encoding - Reserved segments
.and..are encoded as%2Eand%2E%2Erespectively.
Missing values (like a missing
run-id) are encoded asnull.- Workflow:
Handle errors in the bridge layer using `BridgeResult`
mainThe bridge uses
BridgeErrorand theBridgeResult<T>type alias as the standard way to report and propagate errors.Key Advantages of
BridgeError:- Encapsulation: It can wrap a
Throwobject, allowing errors to propagate through non-JS-aware functions before being rethrown. - Thread Safety: Errors can be sent across threads and converted to a
Throwobject once they reach a JS-aware parent. - Automatic JS Mapping: The JS Error type is automatically determined based on the
BridgeErrorvariant. - Context Enrichment: You can add context to errors as they propagate.
Best Practices for Error Context:
- Use
.field()for object paths: When accessing properties, use.field("propertyName")to prepend the path. This results in clear error messages likefn some_func.args[4].foo.bar: .... - Use
.context()for foreign errors: When wrapping errors from other sources or propagating them up the stack, use.context("description")to provide additional information.
// Adding field context fn get_user_field(id: u64) -> BridgeResult<User> { find_user(id).map_err(|e| e.field("user")) } // Adding general context fn process() -> BridgeResult<()> { do_work().context("failed to process task") }- Encapsulation: It can wrap a
How the Bridge Layer is structured
mainThe bridge layer is organized to facilitate side-by-side comparison between Rust and TypeScript definitions to ensure type safety across the boundary.
Rust Side Organization
- API Functions and Types: Defined in
core-bridge/src/(e.g.,client.rs,worker.rs). - Configuration: Component-specific configuration types are grouped in a nested
configsubmodule within the component's file. - Helpers: Abstractions are located in the
helpersmodule, with some functionality provided via Derive Macros in thebridge-macroscrate.
TypeScript Side Organization
- API Declarations: Functions and types are declared in
core-bridge/ts/native.ts.
Naming and Ordering Conventions
To simplify review and maintain consistency:
- Naming: API entrypoint functions use component-specific prefixes (e.g.,
client_new,worker_poll_activity_task). Function names usesnake_casein Rust andcamelCasein TypeScript. - Ordering: Functions, types, and properties should be listed in the same order in both Rust and TypeScript to allow for easy side-by-side comparison.
- API Functions and Types: Defined in
Handle asynchronous operations and thread safety in the bridge
mainThe bridge layer requires strict management of concurrency and thread boundaries:
Asynchronous Operations
- Future Conversion: Use
future_to_promise()to convert Rust futures into JavaScript promises. - Simplification: Use the
RuntimeExtextension trait to simplify working with futures. - Error Propagation: Ensure proper typing of Promise results and careful handling of async error propagation.
Thread Safety
- Isolation: Maintain a strict separation between JS and Rust threads. Avoid using JS contexts across different threads.
- Context Management: Use the
enter_sync!macro when entering atokiocontext. - Synchronization: Use
ArcandMutexfor thread-safe data sharing and ensure all shared resources are properly synchronized.
- Future Conversion: Use
Handle optional values and object properties in the bridge
mainTo ensure type safety and prevent incoherency between JS and Rust, the bridge follows strict rules for optionality:
- Represent
Option<T>withnull: In TypeScript, anOption<T>from Rust should be modeled asT | null. Usenullto represent an intentionally unspecified value (None). - Avoid TypeScript optional properties: Never use the
?operator for properties on objects sent across the bridge (e.g., do not usesomeProperty?: number). - Explicitly set all properties: Every property expected by the Rust side must be present and set to a non-
undefinedvalue when sending objects to native code.
This design allows the bridge to distinguish between an intentionally unspecified value (
null) and an unintentionally missing property (undefined).// Correct TypeScript modeling for bridge objects interface MyBridgeObject { someProperty: number | null; // Use | null, NOT someProperty?: number }- Represent