Temporal TypeScript SDK

repository·main·Indexed 21 days ago

https://github.com/temporalio/sdk-typescript

A 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.

Tokens
119.3K
Snippets
354
Records
507
Agent score
75%

What's inside Temporal TypeScript SDK

  1. Overview of @temporalio/common

    main

    The @temporalio/common package 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.
  2. Identify the correct Temporal TypeScript SDK package for your task

    main

    The 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.
  3. Overview of Temporal TypeScript SDK packages

    main

    The 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.
  4. Important notice regarding @temporalio/core-bridge usage

    main
    The @temporalio/core-bridge package 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.
  5. GCS Object Name Specification and Encoding

    main

    The 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 %2E and %2E%2E respectively.

    Missing values (like a missing run-id) are encoded as null.

  6. Handle errors in the bridge layer using `BridgeResult`

    main

    The bridge uses BridgeError and the BridgeResult<T> type alias as the standard way to report and propagate errors.

    Key Advantages of BridgeError:

    • Encapsulation: It can wrap a Throw object, allowing errors to propagate through non-JS-aware functions before being rethrown.
    • Thread Safety: Errors can be sent across threads and converted to a Throw object once they reach a JS-aware parent.
    • Automatic JS Mapping: The JS Error type is automatically determined based on the BridgeError variant.
    • 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 like fn 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")
    }
  7. How the Bridge Layer is structured

    main

    The 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 config submodule within the component's file.
    • Helpers: Abstractions are located in the helpers module, with some functionality provided via Derive Macros in the bridge-macros crate.

    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 use snake_case in Rust and camelCase in 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.
  8. Handle asynchronous operations and thread safety in the bridge

    main

    The 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 RuntimeExt extension 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 a tokio context.
    • Synchronization: Use Arc and Mutex for thread-safe data sharing and ensure all shared resources are properly synchronized.
  9. Handle optional values and object properties in the bridge

    main

    To ensure type safety and prevent incoherency between JS and Rust, the bridge follows strict rules for optionality:

    1. Represent Option<T> with null: In TypeScript, an Option<T> from Rust should be modeled as T | null. Use null to represent an intentionally unspecified value (None).
    2. Avoid TypeScript optional properties: Never use the ? operator for properties on objects sent across the bridge (e.g., do not use someProperty?: number).
    3. Explicitly set all properties: Every property expected by the Rust side must be present and set to a non-undefined value 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
    }