Daemon
Bootstrap and cluster/worker replication for a server process.
@ozaco/server/daemonThe Daemon turns a set of env-gated Modules into a running process — and, when asked, into a fleet of replicas. It runs the same entry script everywhere; each replica resolves its own identity (role, index, strategy) from the environment the supervisor sets, so no per-replica code branches are needed.
A daemon is configured with a base bootstrap (install the logger/broker/gateway/plugins), a list of modules (each installs and mounts a slice of the app, optionally gated by roles or a when predicate), and an optional ready hook. On start the daemon resolves the per-replica Runtime, runs base, assembles every eligible module, starts the broker (and the gateway if base installed one), then calls ready.
Replication has two strategies. cluster forks OS processes (shared-port via SO_REUSEPORT, or roles mode where each role gets a count); worker spawns worker threads. In either case the primary becomes a supervisor that only forks and does not host services. Failure handling is configurable per module and daemon-wide: retry the failing unit, then either isolate it (drop just that module / replica-safe) or fail all (crash the replica).
Daemon
The bootstrap + replication plugin.
Daemon
A plugin that bootstraps modules per replica and supervises cluster/worker replication.
const Daemon: Plugin<
DaemonDef.Context,
[options: DaemonDef.Options],
{ start(): Future<DaemonDef.Runtime> }
>setup stores the Options. start resolves the Runtime from env + spawn topology: on a supervisor it forks replicas per replicate (DAEMON_COUNT overrides the count) and returns; on a worker/single process it runs base, assembles each eligible module (retrying per the resolved failure policy — isolate skips a broken module, all aborts the replica), starts Broker and (if installed) Gateway, then runs ready. A module is eligible when its roles intersect the replica's roles (or it has none) and its when predicate passes.
import { Daemon } from '@ozaco/server/daemon'
import { install } from '@ozaco/std/plugin'
import { main } from '@ozaco/std/effect'
await main(function* () {
yield* install(Daemon, {
replicate: { strategy: 'cluster', mode: 'shared-port' },
failure: { mode: 'isolate', retry: 2 },
*base(rt) {
yield* install(Broker)
yield* install(BunGateway, { port: 3000 })
},
modules: [
{ name: 'todos', setup: rt => install(Todos) },
{ name: 'auth', roles: ['auth'], setup: rt => install(Auth) },
],
*ready(rt) {
console.log('replica', rt.index, 'ready')
},
})
yield* Daemon.actions.start()
})Types
The `DaemonDef` namespace: modules, options, runtime, and replication config.
DaemonDef.Options
The daemon configuration: bootstrap, modules, replication, and failure handling.
interface Options {
replicate?: Replicate
// default failure handling for module setup AND crashed replicas; { mode: 'all' }, no retry.
failure?: Failure
// run on every replica BEFORE modules: install logger/broker/gateway/plugins (do NOT start them).
base?: (rt: Runtime) => Operation<unknown>
// env-gated units assembled per replica.
modules: Module[]
// run on every replica AFTER broker + gateway start (logging, warmup, seeding).
ready?: (rt: Runtime) => Operation<unknown>
}DaemonDef.Module
One env-gated unit of the app: install + register + mount its services/plugins.
interface Module {
name: string
// role-gate: eligible only where roles intersect. Absent = role-independent.
roles?: string[]
// extra predicate (e.g. a feature flag from rt.env). Absent = always eligible.
when?: (rt: Runtime) => boolean
// per-module failure handling, overriding the daemon-level failure.
failure?: Failure
// run only when eligible.
setup: (rt: Runtime) => Operation<unknown>
}DaemonDef.Runtime
Per-replica facts resolved from env + spawn topology, passed to every callback.
interface Runtime {
env: Record<string, string>
roles: Set<string>
runsAll: boolean // no specific role → runs every eligible module
role: string | null
index: number // cluster worker id / thread id; -1 for supervisor/single
strategy: Strategy
mode: Mode | null
primary: boolean // OS-level primary (cluster primary / main thread)
supervisor: boolean // primary && strategy !== 'none' → forks, hosts nothing
reusePort: boolean // start the gateway with SO_REUSEPORT
}DaemonDef.Replicate
Replication topology: strategy, count, cluster mode, roles, or worker script.
interface Replicate {
strategy: Strategy // 'none' | 'worker' | 'cluster'
count?: number // shared-port / worker pool size; default os.cpus().length
mode?: Mode // 'shared-port' | 'roles'; default 'shared-port'
roles?: Record<string, number> // roles mode: role name → replica count
script?: string | URL // worker strategy: entry script each thread runs
}DaemonDef.Failure
Failure handling for module setup and replica crashes: retry, then isolate or fail-all.
interface Failure {
mode?: 'all' | 'isolate' // 'all' stops everything; 'isolate' drops only the broken unit
retry?: Retry | number // a number shorthand sets the attempt count
}
interface Retry {
attempts?: number // TOTAL tries incl. the first
delay?: number // ms; grows by backoff each attempt
backoff?: number
maxDelay?: number
}DaemonDef
The namespace bundling the daemon types plus the `Strategy`/`Mode`/`FailMode` unions.
namespace DaemonDef {
type Strategy = 'none' | 'worker' | 'cluster'
type Mode = 'shared-port' | 'roles'
type FailMode = 'all' | 'isolate'
interface Context { options: Options }
// Options, Module, Runtime, Replicate, Failure, Retry (documented above)
}Constants
Environment variable names and error tags.
DAEMON_ENV
Env var names the daemon reads to resolve a replica’s identity.
const DAEMON_ENV = {
strategy: 'DAEMON_STRATEGY',
count: 'DAEMON_COUNT',
mode: 'DAEMON_MODE',
role: 'DAEMON_ROLE',
service: 'SERVICE',
} as constThe supervisor sets strategy/mode/role on each forked child, so a replica re-running the same entry resolves its identity from these alone. DAEMON_COUNT overrides the configured replica count.
DaemonErrors
The `server:daemon.*` error tags.
const DaemonErrors: {
UnsupportedStrategy: 'server:daemon.unsupported-strategy'
MissingScript: 'server:daemon.missing-script'
Spawn: 'server:daemon.spawn'
}