0Z4C0
// navigation
// @ozaco/server

Daemon

Bootstrap and cluster/worker replication for a server process.

import from@ozaco/server/daemon

The 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).

// on this page
01

Daemon

The bootstrap + replication plugin.

const

Daemon

A plugin that bootstraps modules per replica and supervises cluster/worker replication.

signature
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.

example
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()
})
02

Types

The `DaemonDef` namespace: modules, options, runtime, and replication config.

iface

DaemonDef.Options

The daemon configuration: bootstrap, modules, replication, and failure handling.

signature
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>
}
iface

DaemonDef.Module

One env-gated unit of the app: install + register + mount its services/plugins.

signature
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>
}
iface

DaemonDef.Runtime

Per-replica facts resolved from env + spawn topology, passed to every callback.

signature
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
}
iface

DaemonDef.Replicate

Replication topology: strategy, count, cluster mode, roles, or worker script.

signature
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
}
iface

DaemonDef.Failure

Failure handling for module setup and replica crashes: retry, then isolate or fail-all.

signature
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
}
ns

DaemonDef

The namespace bundling the daemon types plus the `Strategy`/`Mode`/`FailMode` unions.

signature
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)
}
03

Constants

Environment variable names and error tags.

const

DAEMON_ENV

Env var names the daemon reads to resolve a replica’s identity.

signature
const DAEMON_ENV = {
  strategy: 'DAEMON_STRATEGY',
  count: 'DAEMON_COUNT',
  mode: 'DAEMON_MODE',
  role: 'DAEMON_ROLE',
  service: 'SERVICE',
} as const

The 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.

const

DaemonErrors

The `server:daemon.*` error tags.

signature
const DaemonErrors: {
  UnsupportedStrategy: 'server:daemon.unsupported-strategy'
  MissingScript: 'server:daemon.missing-script'
  Spawn: 'server:daemon.spawn'
}