Skip to main content

resonate-basic-ephemeral-world-usage-typescript

Core patterns for using Resonate Client APIs in the Ephemeral World - initialization, registration, top-level invocations, promise management, and dependency injection. Covers connecting to a Resonate Server, running on Postgres with no server process (PostgresNetwork), and token authentication - the constructor takes `token`, not `auth`, and silently ignores unknown options. Use this for application entry points and orchestration code outside of durable functions.

Jump to install

Source facts

Repository
resonatehq/resonate-skills
Last source activity
August 5, 2026 at 23:29
Detected SKILL.md language
English
Stars
6
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
resonate-basic-ephemeral-world-usage-typescript
description
Core patterns for using Resonate Client APIs in the Ephemeral World - initialization, registration, top-level invocations, promise management, and dependency injection. Covers connecting to a Resonate Server, running on Postgres with no server process (PostgresNetwork), and token authentication - the constructor takes `token`, not `auth`, and silently ignores unknown options. Use this for application entry points and orchestration code outside of durable functions.
license
Apache-2.0
# Resonate Basic Ephemeral World Usage > **SDK version:** This skill reflects `@resonatehq/sdk` v0.11.4 (current on npm). > > **Two execution engines (v0.11.0+):** The SDK now ships two engines. This skill documents the **generator engine** (imported from `@resonatehq/sdk`), which uses `function*` / `yield*` and is the basis of all existing Resonate examples. An **async/await engine** (imported from `@resonatehq/sdk/async`) was added in v0.11.0 and is documented in `resonate-async-await-engine-typescript`. ## Overview The **Ephemeral World** is where your application code lives that is NOT inside generator functions. This is where you: - Initialize the Resonate Client - Register functions - Start top-level executions - Manage promises externally - Set dependencies **Key distinction:** Ephemeral World code uses `resonate` (the Client). Durable World code uses `ctx` (the Context). ## Mental Model ``` ┌─────────────────────────────────────────┐ │ EPHEMERAL WORLD (Stateless) │ │ │ │ • Resonate Client initialization │ │ • Function registration │ │ • Top-level run/rpc calls │ │ • Promise management (create/resolve) │ │ • Dependency injection │ │ │ │ Uses: resonate.run(), resonate.rpc() │ └─────────────────────────────────────────┘ ↓ invokes ↓ ┌─────────────────────────────────────────┐ │ DURABLE WORLD (Stateful) │ │ │ │ • Generator functions with yield* │ │ • Context APIs for sub-invocations │ │ • Durable coordination and recovery │ │ │ │ Uses: ctx.run(), ctx.rpc() │ └─────────────────────────────────────────┘ ``` ## Core Rule **You CANNOT use Context APIs in the ephemeral world, and you CANNOT use Client APIs in durable functions.** ## Initialization Patterns ### Local Development Mode (Zero Dependencies) ```ts import { Resonate } from "@resonatehq/sdk"; // No URL = local in-memory mode const resonate = new Resonate(); ``` **Key behaviors:** - In-memory storage (no persistence across restarts) - No external dependencies required - Perfect for testing and development - **CRITICAL:** When using `new Resonate()` without a URL, function arguments are wrapped in an array ### Connected to Resonate Server ```ts import { Resonate } from "@resonatehq/sdk"; const resonate = new Resonate({ url: "http://localhost:8001", group: "workers", token: process.env.RESONATE_TOKEN, // JWT bearer token, if the server is secured }); ``` **When to use:** - Multiple worker processes - Persistence across restarts - Distributed execution - Production deployments > **There is no basic-auth option on current releases.** The constructor accepts `token` (a JWT bearer token) and nothing else auth-related — there is no `auth`, `username`, or `password` field, and no `RESONATE_USERNAME` / `RESONATE_PASSWORD` environment variable. Passing an unknown option does **not** throw, so credentials handed to a field that does not exist are silently dropped and the client sends unauthenticated requests. > > Basic auth did exist through `0.9.6`, where `auth: { username, password }` sent an `Authorization: Basic` header that the server of that era validated. The `0.10.0` networking rewrite removed it with no deprecation warning. If you are upgrading from `0.9.x`, a working `auth` block does not fail loudly — it stops authenticating. ### Postgres Instead of a Server The SDK can also run durable execution directly on Postgres, with no Resonate Server process. Apply the `resonate.sql` schema from [resonate-pg](https://github.com/resonatehq/resonate-pg) to a Postgres 16+ database, install the `pg` peer dependency, and pass a `PostgresNetwork` as the `network` option: ```ts import { Resonate } from "@resonatehq/sdk"; import { PostgresNetwork } from "@resonatehq/sdk/postgres"; const network = new PostgresNetwork({ connectionString: process.env.DATABASE_URL ?? "postgres://localhost:5432/mydb", group: "workers", }); const resonate = new Resonate({ network }); ``` Works with both engines — pass the same `network` to the `Resonate` class from `@resonatehq/sdk/async`. **Key behavior to know:** with no server process, due timers are advanced by the workers themselves. Durable sleeps and timeouts progress only while at least one worker is connected; with every worker down they suspend until one reconnects. ### Environment Variables ```bash export RESONATE_URL="http://localhost:8001" export RESONATE_TOKEN="<jwt>" ``` ```ts const resonate = new Resonate(); // Picks up env vars automatically ``` **Resolution order:** 1. Constructor arguments (highest priority) 2. Environment variables 3. Built-in defaults (local mode) ## Registration Patterns ### Correct: Register with Function Reference ```ts function* myWorkflow(ctx: Context, arg: string) { return `Hello ${arg}`; } // ✅ CORRECT const stub = resonate.register(myWorkflow); ``` ### Wrong: Custom String Names ```ts // ❌ WRONG - Don't use custom string names resonate.register("my_workflow", myWorkflow); // ❌ WRONG - Don't use snake_case resonate.register("db_get_user", dbGetUser); ``` **Why this matters:** - Resonate uses function names for routing - Custom names break RPC resolution - Snake_case violates naming conventions ### Using the Returned Stub ```ts const workflowStub = resonate.register(myWorkflow); // Later, invoke directly via stub const result = await workflowStub.run("execution-1", "World"); ``` ## Top-Level Invocation Patterns ### Run (Local Execution) ```ts // Blocks until result is ready const result = await resonate.run( "execution-id", myWorkflow, "arg1", "arg2" ); ``` **Use when:** - Running in the same process - Need the result immediately ### Begin Run (Non-Blocking Local) ```ts // Returns immediately with handle const handle = await resonate.beginRun( "execution-id", myWorkflow, "arg1" ); // Do other work... // Get result later const result = await handle.result(); ``` **Use when:** - Starting work but not waiting immediately - Running multiple executions concurrently ### RPC (Remote Execution) ```ts // Blocks until remote execution completes const result = await resonate.rpc( "execution-id", "myWorkflow", // String name! "arg1", resonate.options({ target: "poll://any@workers" }) ); ``` **Critical differences from run:** - Uses string function name (not function reference) - Requires target specification - Executes in different process/group ### Begin RPC (Non-Blocking Remote) ```ts const handle = await resonate.beginRpc( "execution-id", "myWorkflow", "arg1", resonate.options({ target: "poll://any@workers" }) ); const result = await handle.result(); ``` ## Options Pattern ```ts await resonate.run( "execution-id", myWorkflow, "arg1", resonate.options({ timeout: 60_000, // 60 seconds in ms tags: { userId: "123" }, version: 1 }) ); ``` **Available options:** - `timeout`: Max execution time in milliseconds - `tags`: Metadata for filtering/searching - `version`: Function version for schema evolution - `target`: RPC routing (poll://any@group-name) ## Promise Management ### Create Promise ```ts await resonate.promises.create( "approval-123", Date.now() + 30000 // timeout 30s from now ); ``` ### Get Promise ```ts const promise = await resonate.promises.get("approval-123"); ``` ### Resolve Promise (Human-in-the-Loop) ```ts // Elsewhere (webhook, UI, CLI): await resonate.promises.resolve("approval-123", { data: JSON.stringify({ approved: true, approver: "alice@example.com", }), }); ``` ### Reject Promise ```ts await resonate.promises.reject("approval-123", { data: JSON.stringify({ reason: "Insufficient funds", }), }); ``` ### Cancel Promise ```ts await resonate.promises.cancel("approval-123"); ``` > **Migration note (v0.10.0 → v0.10.1+):** The single `resonate.promises.settle(id, state, value)` method was split into `resolve`, `reject`, and `cancel` in v0.10.1. The old `settle()` is private in v0.10.2 and remains private in v0.11.4. Update any code that calls `.settle()` directly. ## Dependency Injection ### Set Dependencies ```ts import { createClient } from "@supabase/supabase-js"; const db = createClient(url, key); // Set in ephemeral world resonate.setDependency("db", db); resonate.setDependency("config", { apiKey: "..." }); ``` **Rules:** - Only set dependencies in ephemeral world - Set before registering functions that use them - Pass non-serializable objects (DB connections, etc.) ### Access in Durable World ```ts function* myWorkflow(ctx: Context, userId: string) { // Get in durable world const db = ctx.getDependency("db"); const user = yield* ctx.run(async (_ctx, id) => { return await db.from("users").select().eq("id", id).single(); }, userId); return user; } ``` ## Scheduling ```ts const schedule = await resonate.schedule( "daily-report", "0 8 * * *", // Every day at 8am generateReport, "arg1" ); // Later, delete the schedule await schedule.delete(); ``` ## Subscription (Get Existing Execution) ```ts // Subscribe to existing execution const handle = await resonate.get("execution-id"); // Wait for result const result = await handle.result(); // Check if done const isDone = await handle.done(); ``` **Use when:** - Checking status of long-running executions - Reconnecting to executions after restart - Monitoring from external systems ## Common Pitfalls ### 1. Mixing Ephemeral and Durable APIs ```ts // ❌ WRONG - Using ctx in ephemeral world async function main() { const result = await ctx.run(myFunc); // ctx doesn't exist here! } // ✅ CORRECT async function main() { const result = await resonate.run("id", myFunc); } ``` ### 2. Using Client APIs Inside Generators ```ts // ❌ WRONG function* myWorkflow(ctx: Context) { const result = await resonate.run("id", otherFunc); // Wrong API! } // ✅ CORRECT function* myWorkflow(ctx: Context) { const result = yield* ctx.run(otherFunc); } ``` ### 3. Missing Target on RPC ```ts // ❌ WRONG - RPC needs target await resonate.rpc("id", "funcName", "arg"); // ✅ CORRECT await resonate.rpc( "id", "funcName", "arg", resonate.options({ target: "poll://any@workers" }) ); ``` ### 4. Forgetting In-Memory Mode Wraps Args in Array ```ts // In-memory mode (no URL) const resonate = new Resonate(); function* workflow(ctx: Context, state: MyState) { // ❌ WRONG - state will be [MyState], not MyState! console.log(state.someField); // undefined! } // ✅ CORRECT - Handle array wrapping in local mode function* workflow(ctx: Context, state: any) { const actualState = Array.isArray(state) ? state[0] : state; console.log(actualState.someField); // Works! } ``` ### 5. Using Async Instead of Generators ```ts // ❌ WRONG - Durable functions must be generators async function* workflow(ctx: Context) { } // ✅ CORRECT function* workflow(ctx: Context) { } ``` ## Complete Example ```ts import "dotenv/config"; import { Resonate, type Context } from "@resonatehq/sdk"; // Initialize client (ephemeral world) const resonate = new Resonate({ url: process.env.RESONATE_URL, group: "workers" }); // Set dependencies (ephemeral world) resonate.setDependency("apiKey", process.env.API_KEY); // Register functions (ephemeral world) const workflowStub = resonate.register(myWorkflow); // Start execution (ephemeral world) async function main() { const handle = await resonate.beginRun( "workflow-1", myWorkflow, { userId: "123" } ); console.log("Started:", handle.id); const result = await handle.result(); console.log("Result:", result);
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub