Initial Commit
This commit is contained in:
111
packages/api-stack/api/api-plugin/src/context/context.md
Normal file
111
packages/api-stack/api/api-plugin/src/context/context.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# context
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `context` module ships the runtime object every plugin lifecycle
|
||||
hook receives. `PluginContext<TConfig>` 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<K>` / `registerService<K>` 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<void>((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<TConfig>(options)` | `(CreatePluginContextOptions<TConfig>) => PluginContext<TConfig>` |
|
||||
| `CreatePluginContextOptions<TConfig>` | `{ logger, eventBus, signal, config, services }` |
|
||||
| `PluginContext<TConfig>` | `{ logger, eventBus, signal, getOwnConfig(), registerService<K>, getService<K>, 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.
|
||||
Reference in New Issue
Block a user