Restate Typescript SDK
    Preparing search index...

    Module @restatedev/restate-sdk-gen

    @restatedev/restate-sdk-gen

    A composable, generator-based DSL for Restate workflows. Built around two user-visible concepts: Operation<T> (a lazy, one-shot description of work) and Future<T> (an eager, memoized handle to an eventual T).

    For the rationale and internal architecture, see DESIGN.md. For user-facing patterns and a longer tour, see guide.md.

    npm install @restatedev/restate-sdk @restatedev/restate-sdk-gen
    

    @restatedev/restate-sdk is a peer dependency — bring your own SDK version.

    import * as restate from "@restatedev/restate-sdk";
    import { gen, execute, run, all } from "@restatedev/restate-sdk-gen";

    const greeter = restate.service({
    name: "greeter",
    handlers: {
    greet: async (ctx: restate.Context, name: string): Promise<string> =>
    execute(
    ctx,
    gen(function* () {
    const a = run(({ signal }) => fetchA(signal), { name: "a" });
    const b = run(({ signal }) => fetchB(signal), { name: "b" });
    const [aVal, bVal] = yield* all([a, b]);
    return `${aVal}+${bVal} for ${name}`;
    })
    ),
    },
    });

    restate.endpoint().bind(greeter).listen();

    execute(ctx, op) constructs a scheduler wired to the SDK and runs the Operation<T>. The free-standing functions (run, sleep, all, …) inside the generator body read the active scheduler from a synchronous current-fiber slot — no ops parameter, no AsyncLocalStorage.

    By default execute resolves the moment the main operation settles. Any spawned fibers (and race losers) still running at that point are abandoned: they are never resumed, their catch/finally blocks never run, and the sources they were parked on are dropped. The stop is prompt — nothing observable (journal writes, channel sends, side effects) happens after the main operation's outcome is decided. Durable work a fiber already performed is journaled as usual; only the in-memory continuation is discarded. Pass { onMainExit: "join" } as a third argument to instead keep driving until every spawned fiber has finished (ExecuteOptions = { onMainExit?: "abandon" | "join" }, both re-exported from the package).

    • Operation<T> — lazy, one-shot. Constructed via gen() for user-authored bodies, or by primitives that yield a marker the scheduler dispatches on. gen() takes a factory function (() => Generator<...>), not a generator instance — the type closes the reuse-after-exhausted trap.
    • Future<T> — eager, memoized, reusable. Returned by run, sleep, awakeable, etc. (journal-backed) and by spawn (routine-backed). Both backings are indistinguishable to user code; combinators dispatch internally to pick the cheapest implementation.

    Imported directly from @restatedev/restate-sdk-gen:

    • run(action, opts?) — journaled side effect. action is (opts: { signal: AbortSignal }) => Promise<T>. Pass signal into AbortSignal-aware APIs (fetch(url, { signal })) for cancellation hygiene. Journal-entry name comes from opts.name if given, otherwise from action.name (works for named functions and const-bound arrows). Retry policy via opts.retry ({ maxAttempts, initialInterval, maxInterval, exponentiationFactor, maxDuration }).
    • sleep(duration) — journaled timer.
    • awakeable<T>() — journaled awakeable; returns { id, promise: Future<T> }.
    • channel<T>() — single-shot in-memory Channel<T>.
    • state<T>() / sharedState<T>() — typed key-value store (durable, survives across invocations).
    • contextLocal<T>(default?) — ambient, in-memory storage scoped to the current invocation and shared by every fiber under it. Set once near the top, read anywhere downstream without threading a parameter. Not durable — for state that must outlive the invocation, use state().
    • serviceClient / objectClient / workflowClient (+ *SendClient) — typed RPC into other Restate services.
    • genericCall / genericSend — untyped RPC.
    • cancel(invocationId) — cancel another invocation.
    • workflowPromise(name) — workflow-bound durable promise.
    • all(futures) — wait for every future, return their values in order. Heterogeneous-tuple typed (mirrors Promise.all).
    • race(futures) — return the first to settle; the losing routines are abandoned once the main operation settles (under the default onMainExit: "abandon"), so their results are discarded. Under onMainExit: "join" the losers keep running and a loser parked on a never-settling source keeps the handler alive (see Cancellation).
    • select({ tag1: future1, tag2: future2, ... }) — Tokio/Go-style. Returns { tag, future } of the winning branch; switch on tag and unwrap future.
    • spawn(op) — register an Operation as a new routine; returns a Task<T> (a Future<T> plus interrupt(err?)). Under the default onMainExit: "abandon", a spawned routine still running when the main operation settles is abandoned — fire-and-forget spawns are not guaranteed to complete. To ensure completion, yield* the returned future before returning, or run with { onMainExit: "join" }.
    • task.interrupt(err?) — throw err into a spawned routine at its next yield point (verbatim; a default InterruptedError if omitted) and abort its in-flight run I/O. Cascades down the spawn subtree — every routine the task spawned (transitively) is interrupted too, with the same error, so interrupting a parent winds down the whole tree it rooted; routines spawned elsewhere are untouched. The routine's own try/catch may catch and recover (interrupt is swallowable). Under the default onMainExit: "abandon", interrupt-then-return abandons the routine before its cleanup runs — interrupt then yield* the task ("interrupt-then-join") to drive its catch/finally.

    Combinators have a fast path: when every input Future is journal-backed, they collapse to a single RestatePromise.all/race. Otherwise they fall back to a synthesized fiber. Same semantics either way.

    Invocation-level cancellation (from outside, via the SDK) is delivered as a TerminalError thrown by the next yield* boundary. Catch it to do cleanup; yield more journal work afterward and the next cancellation event is independent of the previous one — cancellation is not sticky.

    Each run closure receives an { signal } argument — an AbortSignal that aborts before the TerminalError fans out to parked routines. Plumb it into AbortSignal-aware APIs (fetch(url, { signal })) so in-flight syscalls cancel immediately instead of waiting for cancellation to surface at the next yield.

    On a cancellation fan-out the parked fibers are woken FIFO; if the main fiber catches the cancellation and returns, the remaining fibers are abandoned (their catch blocks may never run) under the default onMainExit: "abandon".

    For routine-level "stop": use a Channel<void> plus select({ work, stop: stop.receive }). Per-routine cancellation primitives are deferred — see DESIGN.md.

    Under the default onMainExit: "abandon" a spawned routine or race loser parked on a never-settling source does not hang the handler: the handler returns as soon as the main operation settles and the parked routine is abandoned. That hang only applies under onMainExit: "join", where the scheduler keeps driving until every fiber finishes.

    To call a deployed endpoint from the outside — a plain process, not a handler — use the ingress client under the clients namespace. The same typed definition that hosts the handlers also types the client:

    import { clients } from "@restatedev/restate-sdk-gen";
    import { greeter } from "./greeter.js";

    const ingress = clients.connect({ url: "http://localhost:8080" });
    const greeterClient = clients.client(ingress, greeter);

    const greeting = await greeterClient.greet("sam");
    • clients.client(ing, def) / clients.client(ing, def, key) — typed request/response.
    • clients.sendClient(ing, def) / (ing, def, key) — fire-and-forget; resolves once the invocation is accepted.
    • clients.Opts.from({ ... }) — per-call options (idempotencyKey, headers, timeout, signal).

    Retries are opt-in and configured connection-wide via retry (clients.RetryPolicy). Enable the built-in policy with true, or pass an object to tune it:

    clients.connect({ url, retry: true });
    clients.connect({
    url,
    retry: {
    maxAttempts: 6,
    initialInterval: { milliseconds: 100 },
    maxInterval: { seconds: 2 },
    },
    });

    When enabled, the client retries ambiguous failures — network errors, HTTP 429, and HTTP 5xx — with exponential backoff and jitter, but only when the call carries an idempotencyKey:

    await greeterClient.greet("sam", clients.Opts.from({ idempotencyKey: "greet-sam-once" }));
    

    The idempotency key is the safety boundary: Restate dedupes on it, so a retry attaches to the in-flight or completed invocation instead of starting a duplicate. Without a key, no retry is attempted — retrying a non-idempotent invocation could double-execute it.

    To decide per-failure (e.g. to skip a terminal 5xx), supply shouldRetry. It fully replaces the built-in rule; compose with clients.defaultShouldRetry to narrow it. The RetryFailure carries the status, headers, and — for response failures — the body text when present:

    clients.connect({
    url,
    retry: {
    shouldRetry: (failure, attempt) =>
    clients.defaultShouldRetry(failure) &&
    !(failure.kind === "response" && failure.body?.includes("do-not-retry")),
    },
    });

    See examples/tutorial/src/13-ingress.ts for a runnable walkthrough.

    This package lives in the sdk-typescript workspace. The library proper is in src/; auxiliary subdirectories live alongside but are not published:

    packages/libs/restate-sdk-gen/
    ├── src/ # published library
    ├── test/ # vitest unit tests (227 tests / 22 files)
    ├── bench/ # vitest benchmarks
    ├── examples/tutorial/ # 6-tier tutorial; run with `pnpm start:tutorial`
    ├── e2e/ # testcontainers-based e2e; run with `pnpm test:e2e`
    └── test-services/ # sdk-test-suite endpoint service

    Only dist/ and README.md are published to npm.

    From the workspace root:

    pnpm install
    pnpm --filter @restatedev/restate-sdk-gen _test # unit tests
    pnpm --filter @restatedev/restate-sdk-gen _build # build dist/
    pnpm --filter @restatedev/restate-sdk-gen test:e2e # e2e (Docker required)
    pnpm --filter @restatedev/restate-sdk-gen start:tutorial # boot the tutorial
    pnpm --filter @restatedev/restate-sdk-gen bench # microbenchmarks

    For the full architecture and design rationale, read DESIGN.md. For user-facing patterns, read guide.md. For benchmark interpretation, read BENCHMARKS.md.

    Namespaces

    clients
    iface
    internal
    serde
    StandardSchemaV1
    StandardTypedV1

    Classes

    InterruptedError

    Interfaces

    Channel
    ContextDate
    ContextLocal
    Future
    FutureFulfilledResult
    FutureRejectedResult
    GenContextDate
    InvocationReference
    Operation
    Rand
    SchedulerOptions
    ScopedOperations
    Serde
    SharedState
    SignalReference
    StandardSchemaV1
    StandardTypedV1
    State
    Task

    Type Aliases

    AnyGenFn
    ClientFuture
    Descriptor
    Duration
    EntryToDescriptor
    ExecuteOptions
    FutureSettledResult
    FutureValue
    FutureValues
    GenClient
    GenDurablePromise
    GenHandlerOpts
    GenObjectHandlerOpts
    GenSendClient
    GenWorkflowHandlerOpts
    HandlerDef
    HandlerDescriptor
    HandlerDescriptors
    HandlerOrHandlerDescriptor
    HandlerRequest
    ImplementedDefinition
    ImplementedObjectDefinition
    ImplementedServiceDefinition
    ImplementedWorkflowDefinition
    ObjectDescriptor
    OnMainExit
    RetryOptions
    RunAction
    RunActionOpts
    RunOpts
    SelectResult
    Service
    ServiceDefinition
    ServiceDefinitionFrom
    ServiceDescriptor
    TypedState
    UntypedState
    VirtualObject
    VirtualObjectDefinition
    VirtualObjectDefinitionFrom
    Workflow
    WorkflowDefinition
    WorkflowDefinitionFrom
    WorkflowDescriptor

    Functions

    all
    allSettled
    any
    attach
    awakeable
    call
    cancel
    channel
    client
    contextLocal
    date
    execute
    gen
    handlerRequest
    implement
    invocation
    logger
    object
    race
    rand
    rejectAwakeable
    resolveAwakeable
    run
    schemas
    scope
    select
    send
    sendClient
    serdes
    service
    sharedState
    signal
    sleep
    spawn
    state
    workflow
    workflowPromise
    wrapActionForCancellation

    References

    ImplementHandlers → iface.ImplementHandlers
    InferInput → iface.InferInput
    InferOutput → iface.InferOutput