The runtime
Services, layers, and wiring it in with mount — where server vs client lives.
mount builds an Effect ManagedRuntime once from a Layer and returns a ReactNode for your root
REC. This is the seam where “server vs client” lives: the same components run wherever you provide a
runtime.
import { mount } from '@tmonier/effract';
import { createRoot } from 'react-dom/client';
createRoot(el).render(mount(AppLive, Dashboard));
mount(AppLive, Dashboard) verifies at compile time that AppLive provides every service the tree
needs — a missing service is a type error that names the service. Services then resolve up-front into
the runtime’s context, so reading one inside a component is a synchronous lookup — not an async
round-trip.
Defining a service
import * as Context from 'effect/Context';
import * as Layer from 'effect/Layer';
class Stats extends Context.Service<Stats, { total: number; online: number }>()('app/Stats') {}
const StatsLive = Layer.succeed(Stats)({ total: 1280, online: 42 });
Composing layers
Services can depend on services. Build one from another with Layer.effect, wire it with
Layer.provide, and assemble everything with Layer.mergeAll:
import * as Effect from 'effect/Effect';
class Config extends Context.Service<Config, { appName: string }>()('app/Config') {}
const ConfigLive = Layer.succeed(Config)({ appName: 'effract' });
class Greeter extends Context.Service<Greeter, { greet: (n: string) => Effect.Effect<string> }>()(
'app/Greeter',
) {}
const GreeterLive = Layer.effect(
Greeter,
)(
Effect.gen(function* () {
const config = yield* Config; // Greeter depends on Config
return { greet: (n) => Effect.succeed(`${config.appName}: hi ${n}`) };
}),
);
// provide Config into Greeter, then merge everything for the app
export const AppLive = Layer.mergeAll(ConfigLive, Layer.provide(GreeterLive, ConfigLive));
The component just reads the finished result — all the wiring is ordinary Effect, and the same layer drives the client and the server.
Escape hatch
Need the underlying runtime for imperative work? useEffractRuntime() returns the ManagedRuntime,
so you can runPromise/runFork from an event handler.