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.

معلومات المصدر

المستودع
resonatehq/resonate-skills
آخر نشاط في المصدر
٥ أغسطس ٢٠٢٦ في ٢٣:٢٩
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٦
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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);
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub