Skip to content

temporalio/nex-gen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nex-gen

Warning

This repository is experimental. Generated APIs, input formats, and CLI behavior may change without compatibility guarantees.

Rust CLI for generating language-specific Nexus operation bindings from WIT.

The WIT definition is the source of truth for the public API. Protobuf descriptor sets are optional and are only needed when the WIT opts into proto-backed models or when using add-rpc to scaffold WIT from an existing proto RPC.

Contents

Current status:

  • Python generation is implemented
  • TypeScript generation is implemented
  • WIT records, enums, flags, variants, results, resources, resource methods, and no-result operations are supported without proto backing
  • proto-backed request models can serialize into proto inputs when WIT types are annotated with @nexus.proto
  • proto-backed response and nested models remain bidirectional where generated
  • support files, native type substitutions, sourced fields, function metadata, flattened API fields, and output transforms are driven from WIT @nexus directives

Examples

Each example starts with authored WIT under examples/inputs/. Checked-in generated output lives under examples/python/<example_name>/ and examples/typescript/<example-name>/, with language-specific tests under each language's tests/ directory. See examples/README.md for links to each example's WIT, generated code, and tests.

  • user-service: a small WIT-direct API showing the basic shape of an operation returning a resource and a resource method that calls another operation.
  • type-showcase: a WIT-direct API focused on type coverage, including records, enums, flags, variants, results, maps, tuples, resources, resource methods, and no-result operations.
  • start-workflow: a proto-backed Temporal workflow-start API that returns a generated resource handle with follow-up operations such as cancel, restart, and get-result.
  • workflow-service: a proto-backed SignalWithStartWorkflowExecution example showing flattened APIs, function arguments, sourced fields, support converters, and output transforms.
  • type-roundtrip: a proto-backed type roundtrip example for focused native/proto conversion coverage, including retry policies, activity options, durations, task queues, and priority.

Rebuild the checked-in example outputs:

cargo build-examples

Rebuild one language or one example only:

cargo build-examples --lang python
cargo build-examples user-service
cargo build-examples --lang typescript user-service

Run the same validations as the CI pipeline:

./scripts/validate.sh

Write the prepared WIT workspace the loader actually parses:

cargo run -- debug-wit-dir \
  --input examples/inputs/user-service.wit \
  --output /tmp/user-service-wit

WIT-Direct Generation

Start with a WIT file that describes the API surface directly:

package temporal:user-service@1.0.0;

world system {
  export user-service;
}

/// @nexus.endpoint "__user_service"
interface user-service {
  resource user {
    constructor(user-id: string, email: string);

    update-email: func(email: string) -> user-result;
  }

  record get-user-request {
    user-id: string,
  }

  type user-result = own<user>;

  record update-email-request {
    user-id: string,
    email: string,
  }

  get-user: func(request: get-user-request) -> user-result;
  update-email: func(request: update-email-request) -> user-result;
}

Generate Python:

cargo run -- generate \
  --lang python \
  --input examples/inputs/user-service.wit \
  --output /tmp/user_service

Generate TypeScript:

cargo run -- generate \
  --lang typescript \
  --input examples/inputs/user-service.wit \
  --output /tmp/user-service

Add --format to run a formatter after generation:

  • Python: ruff format
  • TypeScript: prettier --write

The user-service example is intentionally small and WIT-native. The type-showcase example demonstrates broader WIT type coverage: records, enums, flags, variants, results, maps, tuples, resources, resource methods, and an operation with no return value without proto annotations.

WIT Directives

The WIT file defines the public surface. @nexus directives carry the parts WIT does not express directly:

  • service endpoint names
  • service wire names
  • support file paths
  • language-native override types
  • flattened API-only field types
  • sourced field expressions
  • function conversion metadata
  • output transforms
  • explicit resource method operation bindings
  • experimental service, operation, and record warnings
  • @nexus.delay-load-temporalio-workflow on Python services that must not import temporalio.workflow until an operation executes

Resource methods bind to operations only when the method and operation have the same generated operation name. When they intentionally differ, mark the method with @nexus.operation, for example /// @nexus.operation "cancel-workflow" on cancel: func(...) to bind it to cancel-workflow: func(...).

Input WIT files can add support code with @nexus.support. Python support fragments are copied into the generated private _support package, and TypeScript support fragments are emitted as support.ts next to the generated index.ts.

Runtimes

The examples include small language runtimes that are not generated from WIT:

  • Python: examples/python/nex_gen_runtime.py
  • TypeScript: examples/typescript/nex-gen-runtime.ts

These runtimes provide shared serialization helpers for WIT-direct values, including the json/nexus payload encoding used by the example tests to round-trip generated records and resources through real Temporal Nexus clients. The TypeScript examples also include nex-gen-payload-converter.cjs so the Temporal TypeScript SDK can load the same payload converter through its payloadConverterPath data-converter hook.

These files are intentionally example/runtime shims. They should eventually be removed once the corresponding Temporal SDKs provide native serialization support for Nexus API generator values and resources.

Proto Backing

Proto backing is opt-in per WIT type. Use it when an operation should accept or return generated API models while converting to or from protobuf messages at the Nexus boundary.

Proto-backed WIT uses:

  • @nexus.proto on a WIT type to identify the protobuf message or enum it represents
  • @nexus.proto-field when the WIT field name differs from the proto field name
  • --descriptors on generate so the generator can validate fields and derive proto conversion code

Example:

package temporal:nexus@1.0.0;

world system {
  export workflow-service;
}

/// @nexus.endpoint "__temporal_system"
/// @nexus.service-name "temporal.api.workflowservice.v1.WorkflowService"
interface workflow-service {
  use nexus:temporal-types/model@1.0.0.{signal-function, task-queue, workflow-function};

  /// @nexus.proto "temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest"
  record signal-with-start-workflow-request {
    /// @nexus.proto-field "workflow_type"
    workflow: workflow-function,
    workflow-id: string,
    task-queue: task-queue,
    /// @nexus.proto-field "signal_name"
    signal: signal-function,
    /// @nexus.source "workflow_namespace"
    namespace: option<string>,
  }

  /// @nexus.proto "temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse"
  record signal-with-start-workflow-response {
    run-id: option<string>,
  }

  /// @nexus.output-transform
  ///   python-type="workflow.ExternalWorkflowHandle[typing.Any]"
  ///   python="workflow.get_external_workflow_handle(request.workflow_id, run_id=result.run_id)"
  ///   typescript-type="workflow.ExternalWorkflowHandle"
  ///   typescript="workflow.getExternalWorkflowHandle(request.workflowId, result.runId ?? undefined)"
  /// @nexus.operation name="SignalWithStartWorkflowExecution"
  signal-with-start-workflow: func(
    request: signal-with-start-workflow-request,
  ) -> signal-with-start-workflow-response;
}

Generate a proto-backed example:

cargo run -- generate \
  --lang python \
  --input examples/inputs/workflow-service.wit \
  --input examples/inputs/deps \
  --descriptors examples/descriptors/temporal_api.bin \
  --output /tmp/workflow_service

--descriptors can be passed more than once when a proto-backed API depends on multiple descriptor files. Duplicate files or duplicate symbols are rejected.

The examples include a reusable Temporal semantic/common type WIT input:

  • nexus:temporal-types/model@1.0.0

Pass it as an additional --input when generating an API that uses nexus:temporal-types/model@1.0.0. For generate, the first --input is the API generation root and later inputs are linked into the parser workspace. For add-rpc, pass any WIT inputs needed to resolve shared types; when extending an existing WIT file, put that file first. A linked input can be a single WIT file, a WIT package directory, or a directory containing WIT package directories, so examples/inputs/deps links every package under it.

Generate WIT for a proto RPC from a descriptor set:

cargo run -- add-rpc \
  --descriptors examples/descriptors/temporal_api.bin \
  --rpc SignalWithStartExecution \
  --input examples/inputs/deps

Write the standalone WIT scaffold to a file instead of stdout:

cargo run -- add-rpc \
  --descriptors examples/descriptors/temporal_api.bin \
  --rpc temporal.api.workflowservice.v1.WorkflowService.SignalWithStartWorkflowExecution \
  --input examples/inputs/deps \
  --output /tmp/add-rpc.wit

Extend an existing WIT file with a new RPC:

cargo run -- add-rpc \
  --descriptors examples/descriptors/temporal_api.bin \
  --rpc SignalWorkflowExecution \
  --input examples/inputs/workflow-service.wit \
  --input examples/inputs/deps

Rewrite the existing WIT file in place by pointing --output at the same path:

cargo run -- add-rpc \
  --descriptors examples/descriptors/temporal_api.bin \
  --rpc SignalWorkflowExecution \
  --input examples/inputs/workflow-service.wit \
  --input examples/inputs/deps \
  --output examples/inputs/workflow-service.wit

Validation

Validate the Python examples:

cargo build-examples --lang python
cd examples/python
uv run pytest
uv run basedpyright

Validate the TypeScript examples:

cargo build-examples --lang typescript
cd examples/typescript
npm install
npm run test
npm run typecheck

cargo test validates the checked-in example outputs as-is. Use cargo build-examples when you want to refresh them.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors