/** * Supervised local Oxigraph server (Release 2, phase 2b lifecycle; used * opt-in in 2a via `store.backend: 'oxigraph-server'`). * * # What this is * * The DKG daemon spawns a single `oxigraph serve` child bound to * loopback, health-checks it before the agent boots, and restarts it * (with backoff) if it dies unexpectedly. The agent then talks to it over * the existing `sparql-http` adapter — this module owns only the child * process lifecycle, not the SPARQL traffic. * * Moving the triple store out of the in-process Oxigraph worker into this * external server is what buys MVCC concurrent reads (reads stop blocking * on the single writer) and incremental RocksDB persistence (no * O(total-triples) full-dump flush). * * # Security * * `oxigraph serve` has no native authentication (upstream documents auth * as an nginx-proxy concern). For a daemon-managed *local* server the * security boundary is therefore the loopback bind (`127.0.0.1`): the * endpoint is never exposed off-host. We do NOT send an Authorization * header to the managed server because it would be meaningless — the * `sparql-http` adapter's `auth` option remains for operators pointing at * their own externally-secured SPARQL endpoint. * * # Shutdown ordering * * The handle's `stop()` sets a `stopping` flag (so the exit handler does * NOT restart), sends SIGTERM, and escalates to SIGKILL after a grace * period. Callers must stop the server AFTER the agent has stopped * issuing store queries, so an in-flight SPARQL request never races a * killed child. * * `spawn`/`fetch` are injectable so unit tests exercise ready-polling, * crash-restart, and shutdown without launching a real binary. */ import { spawn, type ChildProcess } from 'node:child_process'; export interface OxigraphServerIo { spawn: typeof spawn; fetch: typeof globalThis.fetch; /** * Verify the spawned child owns the listen socket (not merely that * something on the port returns HTTP 200). Tests inject `async () => true`. */ childOwnsListenPort?: (child: ChildProcess, port: number, host: string) => Promise; } export interface StartOxigraphServerOptions { /** Absolute path to the verified `oxigraph` binary. */ binaryPath: string; /** RocksDB storage directory (`--location`). */ location: string; /** Bind host. Always loopback in production; overridable for tests. */ host?: string; /** Bind port. */ port: number; log?: (msg: string) => void; /** Total time to wait for the server to answer before failing start. */ readyTimeoutMs?: number; /** Poll interval while waiting for readiness. */ readyIntervalMs?: number; /** Grace period between SIGTERM and SIGKILL on stop. */ stopGraceMs?: number; /** Base delay for restart backoff after an unexpected crash. */ restartBackoffBaseMs?: number; /** Cap for restart backoff. */ restartBackoffMaxMs?: number; io?: Partial; } export interface OxigraphServerHandle { readonly host: string; readonly port: number; readonly queryEndpoint: string; readonly updateEndpoint: string; /** Stop the server and prevent further restarts. Idempotent. */ stop(): Promise; /** * Synchronous best-effort SIGTERM for `process.on('exit')` handlers * (which cannot await). Prevents orphaning the server when boot hits a * fatal `process.exit()` after the server started. Idempotent. */ killSync(): void; } /** * Spawn and health-check a local Oxigraph server. Resolves once the * server answers an `ASK` probe; rejects if it never becomes ready within * `readyTimeoutMs` (the child is killed first so we don't leak it). */ export declare function startOxigraphServer(opts: StartOxigraphServerOptions): Promise; //# sourceMappingURL=oxigraph-server.d.ts.map