/** * App lifecycle orchestrator — owns the plugin queue, register/setup phase * execution, the boot/shutdown state machine, and the firing of * `AppRegistering` / `AppBooting` / `AppReady` / `AppShutdown` hooks. * * The Runtime is the dispatch substrate (container, capabilities, sinks, * events, middleware, telemetry). It does not know about plugins. The * App composes the Runtime with this orchestrator to get plugin lifecycle. */ import type { Container } from "@nwire/container"; import type { AppHooks, PluginDefinition, Runtime } from "@nwire/runtime"; /** * What a plugin contributed to the app during its register + setup phases: * DI bindings, capabilities, source/sink stages, registered handlers, and * lifecycle hooks. Captured by diffing the runtime + global hook registry * around each plugin's phase, so Studio can attribute every topology entry to * the plugin that added it (the `contributes` graph edge). */ export interface PluginContributions { readonly plugin: string; readonly bindings: readonly string[]; readonly capabilities: readonly string[]; readonly sourceStages: readonly string[]; readonly sinkStages: readonly string[]; readonly handlers: readonly string[]; readonly hooks: readonly string[]; } /** Lifecycle state. */ export type LifecycleState = "idle" | "starting" | "running" | "stopping" | "stopped"; export interface AppLifecycleOptions { readonly appName: string; readonly container: Container; readonly runtime: Runtime; } /** * The orchestrator. Each App owns one of these. */ export declare class AppLifecycle { private readonly appName; private readonly container; private readonly runtime; /** * App / plugin lifecycle hook registry. Owned by the App, not the runtime — * the runtime has no lifecycle. Wired through `runtime.observe(...)` so each * slot emits `hook.step` telemetry exactly like the runtime's own hooks * (Studio's plugin live-tap consumes these). */ private readonly hooks; /** Plugins enqueued via `enqueue` whose phases haven't run yet. */ private readonly pendingPlugins; /** Plugins whose register + setup have run. Ordered by registration. */ private readonly plugins; /** What each plugin contributed, captured during `runPluginPhases`. */ private readonly contributions; private state; private startPromise; private stopPromise; constructor(opts: AppLifecycleOptions); /** Current lifecycle phase. */ get currentState(): LifecycleState; /** The app's lifecycle hook registry (App* / Plugin* slots). */ get appHooks(): AppHooks; /** Names of every plugin queued or registered, in registration order. */ pluginNames(): readonly string[]; /** * Queue a plugin for the next `start()`. Throws if a plugin of the same * name is already enqueued or registered, or if the lifecycle has * advanced past `idle` (queue is closed once start begins). */ enqueue(plugin: PluginDefinition): void; /** * Drain queued plugins: all `register`s first, then all `setup`s, then * record their boot/dispose queues for the lifecycle to run. Synchronous; * idempotent (no-op once the queue is empty). */ runPluginPhases(): void; /** A snapshot of the app's current registered surface — for contribution diffs. */ private snapshot; /** Global hook registry names — best-effort (tolerate an absent registry). */ private hookNames; /** * Fold the surface added since `before` into the named plugin's contribution * record, merging across the register + setup phases. */ private recordContribution; /** Per-plugin contribution attribution — read by `captureTopology`. */ pluginContributions(): readonly PluginContributions[]; /** * Boot the app: * * 1. Drain queued plugins (registers, then setups). * 2. Fire `PluginRegistered` for each registered plugin. * 3. Fire `AppRegistering` chain (vetoable). * 4. Fire `AppBooting` chain (vetoable). * 5. For each plugin: `PluginBooting` chain → boot queue → `PluginBooted`. * 6. Run container health checks; fail-fast if any throw. * 7. Fire `AppBooted` + `AppReady`. * * Idempotent + in-flight-shared. */ start(): Promise; /** * Graceful shutdown: * * 1. Fire `AppShuttingDown` chain (vetoable). * 2. For each plugin in reverse order: `PluginShuttingDown` chain → * dispose queue (LIFO, errors isolated) → `PluginShutdown`. * 3. `container.dispose()` — runs `bind({ dispose })` callbacks LIFO. * 4. Fire `AppShutdown`. * * Idempotent + in-flight-shared. The first thrown error during plugin * dispose or container dispose is rethrown after the sequence completes. */ stop(reason?: string): Promise; /** Build the plugin context — same shape across register and setup. */ private buildPluginContext; /** * Materialise a plugin-defined slot on the app lifecycle registry. * Idempotent; the new slot is observed for `hook.step` telemetry like the * built-in ones. Mirrors `Runtime.defineHook` but for app hooks. */ private defineAppHook; }