first commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# service-registry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<T>(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<never, never>` 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.
|
||||
Reference in New Issue
Block a user