@restatedev/restate-sdk-gen is a preview package available since SDK v1.15.0.
Its API may change in future releases. Feedback is welcome.@restatedev/restate-sdk-gen is a composable, generator-based API for authoring Restate handlers.
It builds on the standard @restatedev/restate-sdk and adds:
- Concurrent durable tasks within a single handler, via the
spawnAPI. - Fewer non-determinism pitfalls, because generators make sequencing explicit.
- No
Contextto thread around: every Restate operation is a free function imported from the package.
Installation
Quick start
A handler usingrestate-sdk-gen wraps its body in gen(function*() { ... }) and calls execute(ctx, op):
gen body, the free functions (run, sleep, all, …) read the active scheduler from a synchronous slot — no ctx parameter to pass down.
Core concepts
Operations and Futures
gen(factory)— wraps a generator-body factory into anOperation<T>. Pass a factory function (() => Generator<...>), not a generator instance. The type prevents the reuse-after-exhausted trap.Future<T>— an eager, memoized handle to an eventual result. Returned byrun,sleep,awakeable,spawn, etc. Yielding a Future twice returns the same memoized value.
run — durable side effects
Date.now(), Math.random(), or any non-deterministic value in a name.
Sequential work
Concurrent work
Spawning concurrent routines
spawn registers an Operation as a separate concurrent routine and returns a Task<T>.
- Use
run+all/racefor a flat set of side effects. - Use
spawnwhen each unit is a multi-step sub-workflow with its own logic, branches, or retries.
Spawn lifetime
By default (onMainExit: "abandon"), execute returns as soon as the main operation settles. Any spawned routine still running is abandoned — it is never resumed and its finally blocks do not run. Fire-and-forget spawns are not guaranteed to complete.
To ensure a spawned routine completes, yield* its future before returning, or pass { onMainExit: "join" }:
Interrupting a task
Cooperative cancellation
Use aChannel<void> to signal a running routine to stop:
stop.receive Future is the same object on every access — once sent, every subsequent select takes the stop branch immediately.
Context-local storage
contextLocal() provides ambient, in-memory storage scoped to the current invocation and shared by every routine under it. Set it once near the top, read it anywhere downstream:
contextLocal is in-memory only — not durable. For state that must outlive the invocation, use state() / sharedState().
Timers and awakeables
Calling other services
Common pitfalls
Don't put non-determinism in journal entry names
Don't put non-determinism in journal entry names
Don't reuse a generator instance
Don't reuse a generator instance
Don't call free functions outside a gen body
Don't call free functions outside a gen body
Free functions (
sleep, run, all, …) must be called inside a gen(function*() { ... }) body, not at module init or inside async callbacks.See also
- Concurrent Tasks: The standard promise-based approach for concurrent work
- Durable Steps: The standard
ctx.runAPI - Error Handling:
TerminalError,RetryableError, andPauseError