# `define-plugin` — author-facing plugin DX _(no submodules)_ ## Purpose `definePlugin` is the canonical entry point plugin authors call to declare their `name` / `version` / `requires` / `isolation` / `trustLevel` plus optional lifecycle hooks (`install`, `start`, `stop`, `uninstall`, `health`). It threads the literal-array `requires` through to the `PluginContext` and `RequestContext` so undeclared capability access becomes a compile-time TypeScript error — the type system, not a runtime guard, is what enforces the capability surface. The runtime work is light: validate `name` / `version` are non-empty, reject duplicate ids inside `requires`, and return a frozen record. The heavy lifting (manifest cross-check, capability resolution, FSM hand-off) lives in the host's lifecycle driver; `definePlugin` is the type-safety + ergonomics layer. ## Features - `const Caps extends ReadonlyArray` generic — captures literal-array `requires` so `ctx.` is narrowed exactly to the declared ids. - Synchronous validation for the two failure modes the type system cannot catch: empty `name`, empty `version`, duplicate id. - Frozen output record — safe to hash for the rollback journal. - Lightweight `DefinePluginError` (plain `Error` subclass) — no `SuaveplanError` dependency on the hot author-side path. - Type aliases `PluginContextOf

` / `RequestContextOf

` derive the per-hook context shape from the module record. ## Usage: Basic ```ts import { definePlugin } from "@suaveplan/api-host/define-plugin"; export default definePlugin({ name: "billing", version: "1.2.3", requires: ["db", "auth.session", "logger"] as const, isolation: "trusted", trustLevel: "first-party", async start(ctx) { ctx.capabilities.logger.info("billing: started"); }, }); ``` ## Usage: Advanced Derive request-context type for downstream router wiring: ```ts import { definePlugin, type RequestContextOf, } from "@suaveplan/api-host/define-plugin"; const plugin = definePlugin({ name: "billing", version: "1.2.3", requires: ["db", "auth.session"] as const, isolation: "isolated", trustLevel: "third-party", async install(ctx) { // ctx.capabilities.db is `AdminDatabaseLike` here. await ctx.capabilities.db.execute("INSERT INTO ..."); }, async start(ctx) { ctx.capabilities.db; // ok // ctx.capabilities.events; // <- compile error: "events" not declared }, }); type Req = RequestContextOf; // Req.capabilities.db is `TenantScopedDatabaseLike` here. ``` ## API Reference - `definePlugin(spec): PluginModule` — validates + freezes. - `DefinePluginSpec` — input shape with optional lifecycle hooks. - `PluginModule` — `Readonly>`. - `PluginContextOf

` / `RequestContextOf

` — per-module derived context types. - `DefinePluginError` — `code` ∈ `DEFINE_PLUGIN.INVALID_NAME` | `DEFINE_PLUGIN.INVALID_VERSION` | `DEFINE_PLUGIN.DUPLICATE_CAPABILITY`. ## Implementation Notes The `const Caps extends ReadonlyArray` constraint relies on TypeScript 5.0+ `const` generic inference — callers MUST write `requires: ["db"] as const` (or `satisfies CapabilityRequirements`) for the narrowing to work. Without `as const`, TypeScript widens the tuple to `string[]` and the capability map degrades to the full `CapabilityId` union. The validation is intentionally minimal: deeper manifest-vs-module cross-check is the host loader's job (Chunk B+) so author errors surface in two places — fast / type-only here, slow / runtime + manifest-aware there. The frozen output ensures the rollback journal can content-hash a stable representation.