Effect escape hatches
Frond.Driver.Async is the default and covers most nodes — return a value or a Promise and you are done. Frond.Driver.Effect is the escape hatch: the same hooks, written in Effect. Reach for it when your code already lives in Effect, or when you want Effect’s structured concurrency, retries, and scheduling inside a hook.
The same driver, in Effect
import { Effect } from "effect";
driver: Frond.Driver.Effect<ProfileSpec>({
acquire: Frond.Driver.Acquire((ctx) =>
Effect.gen(function* () {
const profile = yield* ctx.tryPromise((signal) =>
fetch(`/api/profile/${ctx.args.userId}`, { signal }).then((r) => r.json())
);
return profile;
})
),
}),
The hooks are identical to the async driver — acquire, refresh, release, live, actions — but each returns an Effect. On the Effect context, setResult, setResultValidity, and patchResult return Effects you yield*.
Bridging promises
ctx.tryPromise((signal) => run(signal)) lifts a promise into Effect and wires the abort signal for you, so cancellation propagates without extra plumbing.
yield* ctx.tryPromise((signal) => fetchWithRetry(url, { signal }));
In an async driver you would pass ctx.signal to fetch directly; tryPromise is the Effect equivalent.
When to reach for it
| Use Async | Use Effect |
|---|---|
Plain async/await or promises. | Code already written in Effect. |
| A single fetch or call. | Combining effects — concurrency, racing, retry schedules. |
| The common case. | You want Effect’s interruption and resource scoping inside the hook. |
Effect drivers can require Effect services through the hook’s environment. Note that this applies to the driver only — a node’s key function must stay pure and may not use Effect services, time, or randomness. See Identity and keys.
Whichever you choose, the runtime treats the normalized driver identically: readiness, serialization, result validity, timeouts, cancellation, and cleanup do not change. The driver mode is an authoring choice, not a behavior change. See Drivers.
Next: Public surface — the full export map.