# context _(no submodules)_ ## Purpose The `context` module ships the runtime object every plugin lifecycle hook receives. `PluginContext` carries the structural `logger` and `eventBus` (typed against the universal contracts in `@suaveplan/types`), the in-flight `signal` (an `AbortSignal` the registry aborts on timeout or shutdown), the plugin's own merged config via `getOwnConfig()`, and the typed `registerService`/`getService`/ `hasService` trio backed by the shared `ServiceRegistry` augmentation surface. `createPluginContext` is the constructor — the registry calls it once per plugin per lifecycle phase, binding that plugin's config and the shared `services` Map. The Map instance is shared across every plugin in the same boot, so a producer plugin's `registerService` is visible to every later consumer's `getService`. ## Features - Universal — no `node:fs`, no DOM, no Hono dependency. Works in Bun/Node/browser. - Typed `getService` / `registerService` via the augmentable `ServiceRegistry` interface; untyped string keys fail at compile time. - `signal: AbortSignal` for graceful cooperative cancellation — every lifecycle hook can branch on `ctx.signal.aborted` or thread the signal into long-running async work. - `getOwnConfig()` returns the calling plugin's `TConfig` with full schema inference from `definePlugin`. - Stateless — no internal closures keep references after the hook returns; the `services` Map lifetime is owned by the registry. - No `getPluginConfig(name)` cross-plugin coupling (unlike upstream). Reading another plugin's config goes through a typed service. ## Usage: Basic A plugin hook reading its own config and registering a service: ```ts import { definePlugin } from "@suaveplan/api-plugin"; declare module "@suaveplan/api-plugin/service-registry" { interface ServiceRegistry { http: { port: number } } } export const httpPlugin = definePlugin<{ port: number }>({ name: "http", version: "0.1.0", defaults: { port: 3000 }, onInit(ctx) { const cfg = ctx.getOwnConfig(); ctx.registerService("http", { port: cfg.port }); }, }); ``` ## Usage: Advanced A consumer plugin observing the abort signal during async setup: ```ts export const slowPlugin = definePlugin({ name: "slow", version: "0.1.0", dependencies: ["http"], async onReady(ctx) { const http = ctx.getService("http"); if (!http) return; ctx.logger.info("warming up"); await new Promise((resolve, reject) => { const onAbort = () => reject(new Error("aborted")); ctx.signal.addEventListener("abort", onAbort, { once: true }); setTimeout(() => { ctx.signal.removeEventListener("abort", onAbort); resolve(); }, 50); }); }, }); ``` ## API Reference | Export | Signature | |---|---| | `createPluginContext(options)` | `(CreatePluginContextOptions) => PluginContext` | | `CreatePluginContextOptions` | `{ logger, eventBus, signal, config, services }` | | `PluginContext` | `{ logger, eventBus, signal, getOwnConfig(), registerService, getService, hasService }` | ## Implementation Notes The shared `services` Map is intentionally not snapshotted or copied between phases — that would defeat the producer/consumer model. The registry passes the same reference into every call to `createPluginContext`, so registration order across phases is preserved. `registerService` casts the `K` parameter to `string` once when writing to the Map; everywhere else the API is typed against `ServiceRegistry[K]`. Because consumers augment via `declare module`, the cast is the only place the runtime "knows" services are string-keyed — every public site is typed. `signal` is read-only on the context; the registry holds the `AbortController` and decides when to abort (timeout / shutdown). Hooks must never construct their own signal and feed it back into the context.