Skip to content
resonatehqPublic

About

A Resonate Effect-TS SDK

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

@resonatehq/effect

Effect-native durable execution for Resonate.

Status: experimental. The public API and execution semantics are under active development.

Requirements

  • Resonate protocol 2026-04-01
  • Effect 4.0.5 (pinned because fiber scheduling affects positional child ordinals)
  • A runtime with standard fetch, Web Streams, AbortController, Web Crypto, TextEncoder, TextDecoder, and TextDecoderStream

Workflows

import { Effect } from "effect";
import { Resonate, type Workflow } from "@resonatehq/effect";

const resonate = new Resonate({ url: "http://localhost:8001" });

let factorial!: Workflow<number, number, unknown, never>;
factorial = resonate.register(
  "factorial",
  (context, n: number) =>
    n <= 1 ? Effect.succeed(1) : Effect.map(context.run(factorial, n - 1), (child) => n * child),
  { version: 1 },
);

const program = factorial.run("factorial-6", 6);
console.log(await Effect.runPromise(program)); // 720

run executes a durable child locally. rpc dispatches it through Resonate. Their beginRun and beginRpc variants return an Effect-compatible Durable without waiting for its result.

At the root, beginRun is scoped so Effect services cannot outlive their Layer:

const program = Effect.scoped(
  Effect.gen(function* () {
    const left = yield* factorial.beginRun("left", 5);
    const right = yield* factorial.beginRun("right", 6);
    return yield* Effect.all([left, right]);
  }),
);

Activities

Activities can use arbitrary Effect services but cannot create or await durable operations. They require an explicit, idiomatic Effect retry schedule:

import { Effect, Schedule } from "effect";

const charge = resonate.registerActivity("charge", (_info, payment: Payment) => PaymentGateway.charge(payment), {
  version: 1,
});

const checkout = resonate.register(
  "checkout",
  (context, payment: Payment) =>
    context.act(charge, payment, {
      retry: Schedule.exponential("100 millis").pipe(Schedule.compose(Schedule.recurs(5))),
    }),
  { version: 1 },
);

The activity boundary is checked at runtime, including captured Durable values.

Examples

Start a stock server with resonate dev, then run:

# README flagship: plan searches, RPC fan-out, synthesize a report
npm run example:research -- "What is durable execution?"

# Recursive location transparency: run for even n, rpc for odd n
npm run example:fibonacci -- 10

The research example uses deterministic activity stubs so it runs without API credentials. Replace those activity bodies with model and search services; the durable workflow remains unchanged.

Execution rules

  • Child IDs use Dewey notation: root:1, root:1.1, and so on.
  • Replays always resolve the exact positive workflow version stored with the invocation.
  • Workflow code must only block on durable operations. Put external I/O and ordinary asynchronous work in an activity.
  • Starting durable operations concurrently is supported when their Effect launch order is deterministic. The SDK serializes create acknowledgements and records an operation fingerprint to detect replay mismatches.
  • Interrupting a durable wait inside a workflow does not cancel its durable child. Durable race and timeout combinators are intentionally unsupported until the server has child-abandonment semantics.
  • Effect finalizers run when an attempt suspends or fails. Use them only for attempt-local resource cleanup, never for business compensation.

Validation

npm run validate

The test suite includes recursive local and RPC factorial chains, Dewey ID checks, version pinning, suspension and resumption, activity retries, runtime activity-boundary checks, replay fingerprints, and lifecycle failure cases.

About

A Resonate Effect-TS SDK

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages