# service-registry _(no submodules)_ ## Purpose The `service-registry` module defines the single shared interface that plugins augment to publish typed services to other plugins. Plugin A registers a `db` adapter; plugin B retrieves it with `ctx.getService("db")` and receives the augmented type — no cast, no `unknown`, no runtime shape-check. The interface ships **empty** so a consumer who has not augmented it cannot accidentally type-pun a service slot. This is the canonical solution to upstream's `getService(name)` cast hazard: there, `T` was a free type parameter, so a typo in the name or a mismatch between producer and consumer compiled cleanly and crashed at runtime. With module augmentation, the interface itself is the contract; both ends agree on every key. ## Features - `ServiceRegistry` — empty by default, augmented via `declare module`. - `ServiceKey = keyof ServiceRegistry` — `never` until augmented. - Zero runtime — types-only export. - Subpath export `@suaveplan/api-plugin/service-registry` for direct augmentation imports. ## Usage: Basic Declare your plugin's service slot once, anywhere in the consumer code that runs before the registry is created: ```ts import type { DbAdapter } from "./db-adapter.js"; declare module "@suaveplan/api-plugin/service-registry" { interface ServiceRegistry { db: DbAdapter; } } ``` Then registration and lookup are both type-safe: ```ts import { definePlugin } from "@suaveplan/api-plugin"; export const dbPlugin = definePlugin({ name: "db", version: "0.1.0", onInit(ctx) { ctx.registerService("db", createDbAdapter()); }, }); export const consumerPlugin = definePlugin({ name: "consumer", version: "0.1.0", dependencies: ["db"], onReady(ctx) { const db = ctx.getService("db"); // DbAdapter | undefined db?.query("SELECT 1"); }, }); ``` ## Usage: Advanced Multiple consumers may augment independently — TypeScript merges the interface across declarations: ```ts // package-a/services.d.ts declare module "@suaveplan/api-plugin/service-registry" { interface ServiceRegistry { cache: CacheAdapter; } } // package-b/services.d.ts declare module "@suaveplan/api-plugin/service-registry" { interface ServiceRegistry { auth: AuthAdapter; } } // Anywhere: both keys are now type-safe. ctx.getService("cache"); // CacheAdapter | undefined ctx.getService("auth"); // AuthAdapter | undefined ctx.getService("xyz"); // type error — not augmented ``` ## API Reference | Export | Kind | Notes | |---|---|---| | `ServiceRegistry` | interface | Empty extension surface — augment via `declare module`. | | `ServiceKey` | type | `keyof ServiceRegistry`; resolves to `never` without augmentation. | ## Implementation Notes Biome's `noEmptyInterface` rule would flag a literally-empty interface declaration. The module sidesteps the lint cleanly by extending a `Record` alias — semantically equivalent to `{}` for the augmentation pattern but no longer "empty" in the syntactic sense Biome inspects. The alias is private to the module; consumers only see the public `ServiceRegistry` interface and the `ServiceKey` helper. No runtime artefacts are emitted from this file — Vite/dts strip declaration-only modules during the library build. Subpath consumers benefit from a sub-1KB import that is entirely tree-shaken at consumer build time.