Просмотр исходного кода

vendor(cordis): document the full plugin-author surface (@param/@returns everywhere)

Comment-only enrichment across cordis/src/*.ts — Context, EventsService (+ the
ctx merges), Fiber, RegistryService, ReflectService, Service, logger — so the
website API generator can render a complete reference and hard-error on any
future undocumented member (vendor sync included). Logged as local
modification 6 in vendor/README.md; retire it when upstreamed to the fork.
INHERITED_SERVICES/EVENTS source pointers refreshed for the shifted lines;
cordis catalogs regenerated.
lintianle 2 месяцев назад
Родитель
Сommit
83cb48441e

+ 8 - 8
docs/cordis-catalog/events.md

@@ -427,14 +427,14 @@ Source: [`packages/workflow/workflow/src/index.ts:62`](../../packages/workflow/w
 
 The framework events every plugin also sees, beyond the harness vocabulary above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of the event bus, without elevating framework internals to the harness tier's prominence.
 
-- `internal/plugin` — A plugin fiber was created. ([`vendor/cordis/src/events.ts:197`](../../vendor/cordis/src/events.ts))
-- `internal/status` — A fiber changed lifecycle state. ([`vendor/cordis/src/events.ts:198`](../../vendor/cordis/src/events.ts))
-- `internal/service` — Interception hook for a service binding (no core producer). ([`vendor/cordis/src/events.ts:199`](../../vendor/cordis/src/events.ts))
-- `internal/update` — Waterfall: a fiber config update is being applied. ([`vendor/cordis/src/events.ts:200`](../../vendor/cordis/src/events.ts))
-- `internal/get` — Waterfall: a service is being read from the store. ([`vendor/cordis/src/events.ts:201`](../../vendor/cordis/src/events.ts))
-- `internal/set` — Waterfall: a service is being written to the store. ([`vendor/cordis/src/events.ts:202`](../../vendor/cordis/src/events.ts))
-- `internal/listener` — A listener was registered. ([`vendor/cordis/src/events.ts:203`](../../vendor/cordis/src/events.ts))
-- `internal/dispatch` — An event is being dispatched to listeners. ([`vendor/cordis/src/events.ts:204`](../../vendor/cordis/src/events.ts))
+- `internal/plugin` — A plugin fiber was created. ([`vendor/cordis/src/events.ts:328`](../../vendor/cordis/src/events.ts))
+- `internal/status` — A fiber changed lifecycle state. ([`vendor/cordis/src/events.ts:330`](../../vendor/cordis/src/events.ts))
+- `internal/service` — Interception hook for a service binding (no core producer). ([`vendor/cordis/src/events.ts:332`](../../vendor/cordis/src/events.ts))
+- `internal/update` — Waterfall: a fiber config update is being applied. ([`vendor/cordis/src/events.ts:334`](../../vendor/cordis/src/events.ts))
+- `internal/get` — Waterfall: a service is being read from the store. ([`vendor/cordis/src/events.ts:336`](../../vendor/cordis/src/events.ts))
+- `internal/set` — Waterfall: a service is being written to the store. ([`vendor/cordis/src/events.ts:338`](../../vendor/cordis/src/events.ts))
+- `internal/listener` — A listener was registered. ([`vendor/cordis/src/events.ts:340`](../../vendor/cordis/src/events.ts))
+- `internal/dispatch` — An event is being dispatched to listeners. ([`vendor/cordis/src/events.ts:342`](../../vendor/cordis/src/events.ts))
 - `hmr/change` — A watched source file changed on disk. ([`vendor/hmr/src/index.ts:20`](../../vendor/hmr/src/index.ts))
 - `hmr/reload` — Plugins are being reloaded after a change. ([`vendor/hmr/src/index.ts:21`](../../vendor/hmr/src/index.ts))
 - `exit` — The process is exiting on a signal. ([`vendor/loader/src/index.ts:23`](../../vendor/loader/src/index.ts))

+ 4 - 4
docs/cordis-catalog/services.md

@@ -282,12 +282,12 @@ Source: [`packages/workflow/workflow/src/index.ts:210`](../../packages/workflow/
 
 The framework `ctx` surface every plugin also sees, beyond the harness services above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of what `ctx` offers, without elevating framework internals to the harness tier's prominence.
 
-- `ctx.on / ctx.once` — Register an event listener (disposable). ([`vendor/cordis/src/events.ts:29`](../../vendor/cordis/src/events.ts))
-- `ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall` — Dispatch an event (sync / awaited / first-bail / veto-chain). ([`vendor/cordis/src/events.ts:29`](../../vendor/cordis/src/events.ts))
-- `ctx.plugin / ctx.inject` — Load a plugin / declare required services. ([`vendor/cordis/src/registry.ts:144`](../../vendor/cordis/src/registry.ts))
+- `ctx.on / ctx.once` — Register an event listener (disposable). ([`vendor/cordis/src/events.ts:34`](../../vendor/cordis/src/events.ts))
+- `ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall` — Dispatch an event (sync / awaited / first-bail / veto-chain). ([`vendor/cordis/src/events.ts:34`](../../vendor/cordis/src/events.ts))
+- `ctx.plugin / ctx.inject` — Load a plugin / declare required services. ([`vendor/cordis/src/registry.ts:164`](../../vendor/cordis/src/registry.ts))
 - `ctx.effect` — Register a disposable side effect tied to the fiber. ([`vendor/cordis/src/fiber.ts:9`](../../vendor/cordis/src/fiber.ts))
 - `ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin` — Low-level service-store access and binding. ([`vendor/cordis/src/reflect.ts:7`](../../vendor/cordis/src/reflect.ts))
-- `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:35`](../../vendor/cordis/src/context.ts))
+- `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:42`](../../vendor/cordis/src/context.ts))
 - `ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger` — Ambient handles onto the running context graph. ([`vendor/cordis/src/context.ts:16`](../../vendor/cordis/src/context.ts))
 - `ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)` — Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts))
 - `ctx.loader` — The config Loader that booted the app (present under the loader). ([`vendor/loader/src/index.ts:30`](../../vendor/loader/src/index.ts))

+ 12 - 12
scripts/gen-cordis-catalog.ts

@@ -310,14 +310,14 @@ export function collectServices(scanRoot: string = root): ServiceEntry[] {
  * sibling check is N/A; keep them current on a vendor bump.
  */
 const INHERITED_EVENTS: InheritedEntry[] = [
-  { name: 'internal/plugin', summary: 'A plugin fiber was created.', source: 'vendor/cordis/src/events.ts:197' },
-  { name: 'internal/status', summary: 'A fiber changed lifecycle state.', source: 'vendor/cordis/src/events.ts:198' },
-  { name: 'internal/service', summary: 'Interception hook for a service binding (no core producer).', source: 'vendor/cordis/src/events.ts:199' },
-  { name: 'internal/update', summary: 'Waterfall: a fiber config update is being applied.', source: 'vendor/cordis/src/events.ts:200' },
-  { name: 'internal/get', summary: 'Waterfall: a service is being read from the store.', source: 'vendor/cordis/src/events.ts:201' },
-  { name: 'internal/set', summary: 'Waterfall: a service is being written to the store.', source: 'vendor/cordis/src/events.ts:202' },
-  { name: 'internal/listener', summary: 'A listener was registered.', source: 'vendor/cordis/src/events.ts:203' },
-  { name: 'internal/dispatch', summary: 'An event is being dispatched to listeners.', source: 'vendor/cordis/src/events.ts:204' },
+  { name: 'internal/plugin', summary: 'A plugin fiber was created.', source: 'vendor/cordis/src/events.ts:328' },
+  { name: 'internal/status', summary: 'A fiber changed lifecycle state.', source: 'vendor/cordis/src/events.ts:330' },
+  { name: 'internal/service', summary: 'Interception hook for a service binding (no core producer).', source: 'vendor/cordis/src/events.ts:332' },
+  { name: 'internal/update', summary: 'Waterfall: a fiber config update is being applied.', source: 'vendor/cordis/src/events.ts:334' },
+  { name: 'internal/get', summary: 'Waterfall: a service is being read from the store.', source: 'vendor/cordis/src/events.ts:336' },
+  { name: 'internal/set', summary: 'Waterfall: a service is being written to the store.', source: 'vendor/cordis/src/events.ts:338' },
+  { name: 'internal/listener', summary: 'A listener was registered.', source: 'vendor/cordis/src/events.ts:340' },
+  { name: 'internal/dispatch', summary: 'An event is being dispatched to listeners.', source: 'vendor/cordis/src/events.ts:342' },
   { name: 'hmr/change', summary: 'A watched source file changed on disk.', source: 'vendor/hmr/src/index.ts:20' },
   { name: 'hmr/reload', summary: 'Plugins are being reloaded after a change.', source: 'vendor/hmr/src/index.ts:21' },
   { name: 'exit', summary: 'The process is exiting on a signal.', source: 'vendor/loader/src/index.ts:23' },
@@ -328,12 +328,12 @@ const INHERITED_EVENTS: InheritedEntry[] = [
 ]
 
 export const INHERITED_SERVICES: InheritedEntry[] = [
-  { name: 'ctx.on / ctx.once', summary: 'Register an event listener (disposable).', source: 'vendor/cordis/src/events.ts:29' },
-  { name: 'ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall', summary: 'Dispatch an event (sync / awaited / first-bail / veto-chain).', source: 'vendor/cordis/src/events.ts:29' },
-  { name: 'ctx.plugin / ctx.inject', summary: 'Load a plugin / declare required services.', source: 'vendor/cordis/src/registry.ts:144' },
+  { name: 'ctx.on / ctx.once', summary: 'Register an event listener (disposable).', source: 'vendor/cordis/src/events.ts:34' },
+  { name: 'ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall', summary: 'Dispatch an event (sync / awaited / first-bail / veto-chain).', source: 'vendor/cordis/src/events.ts:34' },
+  { name: 'ctx.plugin / ctx.inject', summary: 'Load a plugin / declare required services.', source: 'vendor/cordis/src/registry.ts:164' },
   { name: 'ctx.effect', summary: 'Register a disposable side effect tied to the fiber.', source: 'vendor/cordis/src/fiber.ts:9' },
   { name: 'ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin', summary: 'Low-level service-store access and binding.', source: 'vendor/cordis/src/reflect.ts:7' },
-  { name: 'ctx.extend / ctx.isolate / ctx.intercept', summary: 'Derive a child context (scoped services / isolation / interception).', source: 'vendor/cordis/src/context.ts:35' },
+  { name: 'ctx.extend / ctx.isolate / ctx.intercept', summary: 'Derive a child context (scoped services / isolation / interception).', source: 'vendor/cordis/src/context.ts:42' },
   { name: 'ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger', summary: 'Ambient handles onto the running context graph.', source: 'vendor/cordis/src/context.ts:16' },
   { name: 'ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)', summary: 'Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick).', source: 'vendor/timer/src/index.ts:4' },
   { name: 'ctx.loader', summary: 'The config Loader that booted the app (present under the loader).', source: 'vendor/loader/src/index.ts:30' },

+ 1 - 0
vendor/README.md

@@ -35,6 +35,7 @@ Keep this log exhaustive — every divergence from upstream must be listed.
 3. **All `tsconfig.json` files**: regenerated to extend the repo-root `tsconfig.base.json`, emit TypeScript intermediates to `lib/types`, and declare project references.
 4. **Vendored TypeScript source internal specifiers**: changed local relative imports/exports from upstream's specifier shape to explicit `.ts` specifiers so TypeScript rewrites emitted JS to `.js` while declarations keep explicit, NodeNext-safe `.ts` specifiers. This includes `loader/src/config/isolate.ts` using `declare module './entry.ts'`.
 5. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**: ours, not upstream files — per-package build-shape overrides (dual ESM+CJS output; separate node/browser entries) for the repo-root tsdown build. They read the JS emitted under `lib/types` and then write the publish runtime entries under `lib/`. Like the regenerated tsconfigs, they are not part of the upstream sync surface.
+6. **`cordis/src/*.ts` JSDoc enrichment**: added `@param`/`@returns` tags and contract documentation (disposal semantics, waterfall veto, bail conditions, error cases) across the public plugin-author surface — `Context`, `EventsService`, `Fiber`, `RegistryService`, `ReflectService`, `Service`, `LoggerService` and their `declare module './context.ts'` overloads. Comment-only; no code changes. Motivation: the website API-reference generator renders these docs and hard-errors on undocumented members. Retire this entry when the enrichment is upstreamed to the fork.
 
 ## Sync procedure
 

+ 53 - 4
vendor/cordis/src/context.ts

@@ -14,14 +14,21 @@ import { Fiber } from './fiber.ts'
  * be read from `ctx`.
  */
 export interface Context {
+  /** Isolation map: service name → scope label. Lookups for a name resolve within its label. */
   [symbols.isolate]: Dict<symbol>
+  /** Intercept map: service name → config merged into that service's per-plugin config. */
   [symbols.intercept]: Dict
   /** @experimental */
   root: this
+  /** Base URL used to resolve relative plugin/module specifiers, if the runtime sets one. */
   baseUrl?: string
+  /** The event bus. Its methods are also mixed onto `ctx` (`ctx.on`, `ctx.emit`, ...). */
   events: EventsService
+  /** The logging service. Call `ctx.logger(name)` for a named logger. */
   logger: LoggerService
+  /** The reflection layer backing the context proxy (`ctx.get`, `ctx.provide`, ...). */
   reflect: ReflectService
+  /** The plugin registry. Its methods are mixed onto `ctx` (`ctx.plugin`, `ctx.inject`). */
   registry: RegistryService
 }
 
@@ -33,12 +40,24 @@ export interface Context {
  * contexts without mutating their parent.
  */
 export class Context {
+  /** Symbol key under which a disposer exposes its {@link EffectMeta} diagnostics tree. */
   static readonly effect: unique symbol = symbols.effect
+  /** Symbol key for a context's listener filter, consulted on every event dispatch. */
   static readonly filter: unique symbol = symbols.filter
+  /** Symbol key of the isolation map (see the `Context[symbols.isolate]` property). */
   static readonly isolate: unique symbol = symbols.isolate
+  /** Symbol key of the intercept map (see the `Context[symbols.intercept]` property). */
   static readonly intercept: unique symbol = symbols.intercept
 
-  /** Returns true for Cordis context proxies and context prototypes. */
+  /**
+   * Returns true for Cordis context proxies and context prototypes.
+   *
+   * Works across realms and across multiple copies of cordis, because the
+   * brand is keyed by a global symbol rather than by `instanceof`.
+   *
+   * @param value — the value to test.
+   * @returns `true` if `value` is a Cordis context, narrowing its type.
+   */
   static is(value: any): value is Context {
     return !!value?.[Context.is as any]
   }
@@ -68,7 +87,15 @@ export class Context {
     return `Context <${this.fiber.name}>`
   }
 
-  /** Create a child context with extra metadata on top of the current scope. */
+  /**
+   * Create a child context with extra metadata on top of the current scope.
+   *
+   * The child prototypally inherits every property of this context; own
+   * properties of `meta` shadow the inherited ones. The parent is not mutated.
+   *
+   * @param meta — own properties (including symbol keys) to define on the child.
+   * @returns a child context inheriting from this one.
+   */
   extend(meta = {}): this {
     const shadow = Reflect.getOwnPropertyDescriptor(this, symbols.shadow)?.value
     const self = Object.create(getTraceable(this, this))
@@ -79,14 +106,36 @@ export class Context {
     return Object.assign(Object.create(self), { [symbols.shadow]: shadow })
   }
 
-  /** Create a child context with an independent service scope for `name`. */
+  /**
+   * Create a child context with an independent service scope for `name`.
+   *
+   * Below the returned context, reads and writes of the service `name`
+   * resolve against the new label instead of the parent's, so a different
+   * implementation can be provided without affecting the parent scope.
+   * Passing the same `label` to two `isolate()` calls joins their scopes.
+   *
+   * @param name — the service name to isolate.
+   * @param label — scope label to join; defaults to a fresh unique symbol.
+   * @returns a child context whose `name` service resolves in the new scope.
+   */
   isolate(name: string, label?: symbol) {
     const shadow = Object.create(this[symbols.isolate])
     shadow[name] = label ?? Symbol(name)
     return this.extend({ [symbols.isolate]: shadow })
   }
 
-  /** Add service-specific intercept config for plugins started below this context. */
+  /**
+   * Add service-specific intercept config for plugins started below this
+   * context.
+   *
+   * Plugins loaded under the returned context see `config` merged into the
+   * service's resolved config (ancestor entries first; see
+   * `Service[symbols.resolveConfig]`). The parent context is not affected.
+   *
+   * @param name — the service name whose config to intercept.
+   * @param config — the intercept config to merge for that service.
+   * @returns a child context carrying the additional intercept entry.
+   */
   intercept<K extends InjectKey>(name: K, config: Context[K] extends { [symbols.config]: infer T } ? T : never): this
   intercept(name: string, config: any): this
   intercept(name: string, config: any) {

+ 147 - 9
vendor/cordis/src/events.ts

@@ -3,7 +3,12 @@ import { Context } from './context.ts'
 import { Fiber, FiberState } from './fiber.ts'
 import { DisposableList, symbols } from './utils.ts'
 
-/** Return whether an event result should stop a bail-style dispatch. */
+/**
+ * Return whether an event result should stop a bail-style dispatch.
+ *
+ * @param value — a listener's return value.
+ * @returns `true` unless `value` is `null`, `false`, or `undefined`.
+ */
 export function isBailed(value: any) {
   return value !== null && value !== false && value !== undefined
 }
@@ -28,17 +33,75 @@ export type DispatchMode = 'emit' | 'parallel' | 'serial' | 'bail' | 'waterfall'
 declare module './context.ts' {
   export interface Context {
     /* eslint-disable max-len */
+    /**
+     * Dispatch an event, running all listeners concurrently.
+     *
+     * @param name — the event name.
+     * @param args — arguments passed to every listener.
+     * @returns a promise resolving once every listener has settled.
+     */
     parallel<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): Promise<void>
+    /** Same as above, with an explicit `this` for listeners (also used for filtering). */
     parallel<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): Promise<void>
+    /**
+     * Dispatch an event synchronously, ignoring listener return values.
+     *
+     * @param name — the event name.
+     * @param args — arguments passed to every listener.
+     */
     emit<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): void
+    /** Same as above, with an explicit `this` for listeners (also used for filtering). */
     emit<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): void
+    /**
+     * Dispatch an event, awaiting listeners in order until one bails.
+     *
+     * @param name — the event name.
+     * @param args — arguments passed to each listener.
+     * @returns the first bail value (non-null, non-false, non-undefined), if any.
+     */
     serial<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): Promisify<ReturnType<Events[K]>>
+    /** Same as above, with an explicit `this` for listeners (also used for filtering). */
     serial<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): Promisify<ReturnType<Events[K]>>
+    /**
+     * Dispatch an event, calling listeners in order until one bails.
+     *
+     * @param name — the event name.
+     * @param args — arguments passed to each listener.
+     * @returns the first bail value (non-null, non-false, non-undefined), if any.
+     */
     bail<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
+    /** Same as above, with an explicit `this` for listeners (also used for filtering). */
     bail<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
+    /**
+     * Dispatch an event whose last argument is a `next` continuation.
+     *
+     * Each listener wraps the rest of the chain: calling `next()` invokes the
+     * next listener (finally the built-in behavior); not calling it vetoes.
+     *
+     * @param name — the event name.
+     * @param args — listener arguments; the final one is the innermost `next`.
+     * @returns the outermost listener's return value.
+     */
     waterfall<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
+    /** Same as above, with an explicit `this` for listeners (also used for filtering). */
     waterfall<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
+    /**
+     * Register an event listener owned by the current fiber.
+     *
+     * @param name — the event name to listen for.
+     * @param listener — called with the dispatch arguments.
+     * @param options — listener options; a boolean is shorthand for `prepend`.
+     * @returns a disposer removing the listener; `true` if it was still registered.
+     */
     on<K extends keyof Events>(name: K, listener: Events[K], options?: boolean | EventOptions): () => boolean
+    /**
+     * Same as `on()`, but the listener disposes itself after its first call.
+     *
+     * @param name — the event name to listen for.
+     * @param listener — called at most once with the dispatch arguments.
+     * @param options — listener options; a boolean is shorthand for `prepend`.
+     * @returns a disposer removing the listener; `true` if it was still registered.
+     */
     once<K extends keyof Events>(name: K, listener: Events[K], options?: boolean | EventOptions): () => boolean
     /* eslint-enable max-len */
   }
@@ -91,7 +154,13 @@ export class EventsService {
     }, { global: true, prepend: true })
   }
 
-  /** Resolve listeners for one dispatch and apply context filtering. */
+  /**
+   * Resolve listeners for one dispatch and apply context filtering.
+   *
+   * @param type — the dispatch mode, reported on `internal/dispatch`.
+   * @param args — the raw dispatch arguments; consumed up to the event name.
+   * @returns the matching listener callbacks, bound to the dispatch `this`.
+   */
   dispatch(type: string, args: any[]) {
     const thisArg = typeof args[0] === 'object' || typeof args[0] === 'function' ? args.shift() : null
     const name: string = args.shift()
@@ -104,17 +173,31 @@ export class EventsService {
       .map(hook => hook.callback.bind(thisArg))
   }
 
-  /** Run listeners concurrently and wait for all of them. */
+  /**
+   * Run listeners concurrently and wait for all of them.
+   *
+   * @param args — optional `this`, the event name, then listener arguments.
+   * @returns a promise resolving once every listener has settled.
+   */
   async parallel(...args: any[]) {
     await Promise.all(this.dispatch('emit', args).map(cb => cb(...args)))
   }
 
-  /** Run listeners synchronously without waiting for returned promises. */
+  /**
+   * Run listeners synchronously without waiting for returned promises.
+   *
+   * @param args — optional `this`, the event name, then listener arguments.
+   */
   emit(...args: any[]) {
     this.dispatch('emit', args).map(cb => cb(...args))
   }
 
-  /** Run listeners in order until one returns a bail value. */
+  /**
+   * Run listeners in order, awaiting each, until one returns a bail value.
+   *
+   * @param args — optional `this`, the event name, then listener arguments.
+   * @returns the first bail value (see {@link isBailed}), if any.
+   */
   async serial(...args: any[]) {
     for (const cb of this.dispatch('serial', args)) {
       const result = await cb(...args)
@@ -122,7 +205,12 @@ export class EventsService {
     }
   }
 
-  /** Run listeners synchronously until one returns a bail value. */
+  /**
+   * Run listeners synchronously until one returns a bail value.
+   *
+   * @param args — optional `this`, the event name, then listener arguments.
+   * @returns the first bail value (see {@link isBailed}), if any.
+   */
   bail(...args: any[]) {
     for (const cb of this.dispatch('bail', args)) {
       const result = cb(...args)
@@ -130,7 +218,16 @@ export class EventsService {
     }
   }
 
-  /** Compose listeners around the final `next` callback. */
+  /**
+   * Compose listeners around the final `next` callback.
+   *
+   * The last dispatch argument is treated as the innermost `next`. Listeners
+   * run outermost-first; a listener that does not call `next()` vetoes the
+   * rest of the chain, including the built-in behavior.
+   *
+   * @param args — optional `this`, the event name, listener arguments, then `next`.
+   * @returns the outermost listener's return value.
+   */
   waterfall(...args: any[]) {
     const cbs = this.dispatch('waterfall', args)
     const inner = args.pop()
@@ -142,6 +239,15 @@ export class EventsService {
     return next()
   }
 
+  /**
+   * Store a listener record as an effect on the current fiber.
+   *
+   * @param label — effect label shown in fiber diagnostics.
+   * @param hooks — the listener list for one event.
+   * @param callback — the listener to store.
+   * @param options — placement and filtering options.
+   * @returns a disposer that unregisters the listener.
+   */
   register(label: string, hooks: Hook[], callback: any, options: EventOptions): () => void {
     const method = options.prepend ? 'unshift' : 'push'
     return this.ctx.fiber.effect(() => {
@@ -150,6 +256,13 @@ export class EventsService {
     }, label)
   }
 
+  /**
+   * Remove a stored listener record.
+   *
+   * @param hooks — the listener list for one event.
+   * @param callback — the listener to remove.
+   * @returns `true` if the listener was found and removed.
+   */
   unregister(hooks: Hook[], callback: any) {
     const index = hooks.findIndex(hook => hook.callback === callback)
     if (index >= 0) {
@@ -158,7 +271,17 @@ export class EventsService {
     }
   }
 
-  /** Register an event listener owned by the current fiber. */
+  /**
+   * Register an event listener owned by the current fiber.
+   *
+   * The listener is removed automatically when the fiber unloads. Throws
+   * `CordisError('INACTIVE_EFFECT')` if the fiber is already disposed.
+   *
+   * @param name — the event name to listen for.
+   * @param listener — called with the dispatch arguments.
+   * @param options — listener options; a boolean is shorthand for `prepend`.
+   * @returns a disposer removing the listener; `true` if it was still registered.
+   */
   on(name: string | symbol, listener: (...args: any) => any, options?: boolean | EventOptions) {
     if (typeof options !== 'object') {
       options = { prepend: options }
@@ -175,7 +298,14 @@ export class EventsService {
     return this.register(label, hooks, listener, options)
   }
 
-  /** Register an event listener that disposes itself after the first call. */
+  /**
+   * Register an event listener that disposes itself after the first call.
+   *
+   * @param name — the event name to listen for.
+   * @param listener — called at most once with the dispatch arguments.
+   * @param options — listener options; a boolean is shorthand for `prepend`.
+   * @returns a disposer removing the listener; `true` if it was still registered.
+   */
   once(name: string, listener: (...args: any) => any, options?: boolean | EventOptions) {
     const dispose = this.on(name, function (...args: any[]) {
       dispose()
@@ -194,12 +324,20 @@ export class EventsService {
  * diagnostics before public events are delivered.
  */
 export interface Events {
+  /** A plugin fiber was created or its uid was cleared on disposal. */
   'internal/plugin'(fiber: Fiber): void
+  /** A fiber changed lifecycle state; receives the fiber and its previous state. */
   'internal/status'(fiber: Fiber, oldValue: FiberState): void
+  /** Interception hook for a service binding (no core producer). */
   'internal/service'(this: Context, name: string, value: any): void
+  /** Waterfall: a fiber config update is being applied; skip `next()` to veto. */
   'internal/update'(this: Fiber, config: any, noSave: boolean, next: () => void): void
+  /** Waterfall: a service is being read through the context proxy. */
   'internal/get'(ctx: Context, name: string, error: Error, next: () => any): any
+  /** Waterfall: a service is being written through the context proxy. */
   'internal/set'(ctx: Context, name: string, value: any, error: Error, next: () => boolean): boolean
+  /** Bail: a listener is being registered; a non-null result replaces registration. */
   'internal/listener'(this: Context, name: string, listener: any, prepend: boolean): void
+  /** An event is being dispatched to listeners (fired for non-internal events only). */
   'internal/dispatch'(mode: DispatchMode, name: string, args: any[], thisArg: any): void
 }

+ 107 - 10
vendor/cordis/src/fiber.ts

@@ -7,6 +7,7 @@ import { StandardSchemaV1 } from '@standard-schema/spec'
 
 declare module './context.ts' {
   export interface Context extends Pick<Fiber, 'effect'> {
+    /** The fiber (plugin runtime instance) that owns this context. */
     fiber: Fiber
   }
 }
@@ -17,6 +18,11 @@ const kValidationError = Symbol.for('ValidationError')
 export class ValidationError extends TypeError {
   name = 'ValidationError'
 
+  /**
+   * Build the aggregated message from schema issues.
+   *
+   * @param issues — the standard-schema issues, one message line each.
+   */
   constructor(issues: readonly StandardSchemaV1.Issue[]) {
     super(`invalid config:\n` + issues.map(issue => {
       if (issue.path) {
@@ -32,7 +38,14 @@ Object.defineProperty(ValidationError.prototype, kValidationError, {
   value: true,
 })
 
-/** Validate and normalize config for a plugin runtime before it starts. */
+/**
+ * Validate and normalize config for a plugin runtime before it starts.
+ *
+ * @param runtime — the plugin runtime whose `Config` schema to apply.
+ * @param config — the raw user config.
+ * @returns the validated config, or `config` unchanged if the runtime has no schema.
+ * @throws {ValidationError} when validation reports issues.
+ */
 export function resolveConfig(runtime: Plugin.Runtime, config: any) {
   if (!runtime.Config) return config
   // TODO: async validation
@@ -51,10 +64,21 @@ interface AsyncDisposable<T extends Awaitable<void> = Awaitable<void>> extends P
   (): T
 }
 
-/** Function returned by an effect to release resources during disposal. */
+/**
+ * Function returned by an effect to release resources during disposal.
+ *
+ * Disposers run in reverse registration order when the owning fiber unloads;
+ * they may be async, in which case unloading awaits them.
+ */
 export type Disposable<T = any> = () => T
 
-/** Effect body result accepted by `ctx.effect()` and plugin startup. */
+/**
+ * Effect body result accepted by `ctx.effect()` and plugin startup.
+ *
+ * Either a single disposer, a promise of one, or a (possibly async) iterable
+ * yielding several — generator effects register each yielded disposer as it
+ * is produced.
+ */
 export type Effect<T = any> =
   | SyncEffect<T>
   | AsyncEffect<T>
@@ -69,7 +93,9 @@ type AsyncEffect<T = any> =
 
 /** Tree node used to expose nested effect labels for diagnostics. */
 export interface EffectMeta {
+  /** Human-readable effect label, e.g. `ctx.on("event")` or `ctx.provide("name")`. */
   label: string
+  /** Metadata of nested effects registered while this effect ran. */
   children: EffectMeta[]
 }
 
@@ -80,7 +106,14 @@ interface EffectRunner<T> {
   getOuterStack: () => string[]
 }
 
-/** Lifecycle state for one plugin fiber. */
+/**
+ * Lifecycle state for one plugin fiber.
+ *
+ * `PENDING` — waiting for required services; `LOADING` — the plugin callback
+ * is running; `ACTIVE` — loaded and providing; `FAILED` — the callback or its
+ * config threw; `UNLOADING` — disposers are running; `DISPOSED` — the fiber
+ * was removed and cannot restart.
+ */
 export const enum FiberState {
   PENDING,
   LOADING,
@@ -92,6 +125,10 @@ export const enum FiberState {
 
 /** Framework error with a stable machine-readable code. */
 export class CordisError extends Error {
+  /**
+   * @param code — the stable error code; also the default message.
+   * @param message — optional human-readable override.
+   */
   constructor(public code: CordisError.Code, message?: string) {
     super(message ?? CordisError.Code[code])
   }
@@ -115,12 +152,19 @@ const INACTIVE = '__INACTIVE__'
  * cleanup for the plugin context returned by `ctx.plugin()`.
  */
 export class Fiber {
+  /** Unique id within the registry; 0 for the root fiber, `null` once disposed. */
   public uid: number | null
+  /** The context this fiber's plugin runs in (extends the parent context). */
   public readonly ctx: Context
+  /** The validated plugin config (updated by `update()`). */
   public config: any
+  /** Current lifecycle state; transitions emit `internal/status`. */
   public state = FiberState.PENDING
+  /** Dispose this fiber: unload the plugin, then settle once cleanup finished. */
   public readonly dispose: () => Promise<void>
+  /** Snapshot of required service implementations while loaded; `undefined` otherwise. */
   public store: Dict<Impl> | undefined
+  /** The in-flight load/unload transition, if one is currently running. */
   public inertia: Promise<void> | undefined
 
   public readonly _hooks: Dict<DisposableList<Function>> = Object.create(null)
@@ -133,6 +177,16 @@ export class Fiber {
   private _runner: EffectRunner<string>
   private _store: Dict<Impl> = Object.create(null)
 
+  /**
+   * Create a fiber. Plugin authors normally obtain fibers from `ctx.plugin()`
+   * rather than constructing them directly.
+   *
+   * @param parent — the context the plugin was loaded from.
+   * @param config — raw config, validated against the runtime's schema.
+   * @param inject — resolved dependency map (service name → intercept config).
+   * @param runtime — the shared plugin runtime, or `null` for the root fiber.
+   * @param getOuterStack — captures the caller stack for effect diagnostics.
+   */
   constructor(
     public parent: Context,
     config: any,
@@ -226,6 +280,7 @@ export class Fiber {
     }
   }
 
+  /** The plugin's display name, inherited from the nearest named ancestor, else `'root'`. */
   get name() {
     let fiber: Fiber = this
     do {
@@ -235,7 +290,12 @@ export class Fiber {
     return 'root'
   }
 
-  /** Throw if the fiber has already been disposed. */
+  /**
+   * Throw if the fiber has already been disposed.
+   *
+   * @returns nothing when the fiber is still active.
+   * @throws {CordisError} `INACTIVE_EFFECT` when the fiber's uid has been cleared.
+   */
   assertActive() {
     if (this.uid !== null) return
     throw new CordisError('INACTIVE_EFFECT')
@@ -287,8 +347,21 @@ export class Fiber {
     }, runner.getOuterStack)
   }
 
-  /** Register a cleanup-aware effect on this fiber. */
+  /**
+   * Register a cleanup-aware effect on this fiber.
+   *
+   * `execute` runs immediately; the disposers it produces are collected and
+   * run (in reverse order) either when the returned disposer is called or
+   * when the fiber unloads, whichever comes first. Calling the disposer twice
+   * is a no-op. Throws `CordisError('INACTIVE_EFFECT')` if the fiber is
+   * already disposed, and `TypeError` if `execute` returns an invalid shape.
+   *
+   * @param execute — the effect body; see {@link Effect} for accepted shapes.
+   * @param label — effect label shown in `getEffects()` diagnostics.
+   * @returns a disposer that tears the effect down and settles once done.
+   */
   effect(execute: () => SyncEffect, label?: string): Disposable<Promise<void>>
+  /** Same as above for async effects; the disposer is also awaitable. */
   effect(execute: () => Effect, label?: string): AsyncDisposable<Promise<void>>
   effect(execute: () => Effect, label = 'anonymous'): any {
     this.assertActive()
@@ -355,7 +428,11 @@ export class Fiber {
     return wrapper
   }
 
-  /** Return metadata for currently registered effects. */
+  /**
+   * Return metadata for currently registered effects.
+   *
+   * @returns one {@link EffectMeta} tree per labeled live effect.
+   */
   getEffects() {
     return [...this._disposables]
       .map<EffectMeta>(dispose => dispose[symbols.effect])
@@ -474,7 +551,12 @@ export class Fiber {
     })
   }
 
-  /** Wait for current lifecycle work and rethrow startup errors. */
+  /**
+   * Wait for current lifecycle work and rethrow startup errors.
+   *
+   * @returns this fiber, once it has settled into a stable state.
+   * @throws the config-validation or plugin-startup error, if any.
+   */
   async await() {
     while (this.inertia) {
       await this.inertia
@@ -483,7 +565,12 @@ export class Fiber {
     return this
   }
 
-  /** Dispose and immediately reload this plugin with its current config. */
+  /**
+   * Dispose and immediately reload this plugin with its current config.
+   *
+   * @returns a promise resolving once the reload settled.
+   * @throws {CordisError} `INACTIVE_EFFECT` when the fiber is already disposed.
+   */
   async restart() {
     this.assertActive()
     this._setEpoch(INACTIVE)
@@ -491,7 +578,17 @@ export class Fiber {
     await this.await()
   }
 
-  /** Validate and apply new config, then restart the plugin. */
+  /**
+   * Validate and apply new config, then restart the plugin.
+   *
+   * Runs the `internal/update` waterfall first, so update hooks (and HMR)
+   * can veto or replace the restart.
+   *
+   * @param config — the new raw config; validated before anything restarts.
+   * @param noSave — hint for persistence hooks not to write the change back.
+   * @returns nothing; the restart runs behind the `internal/update` waterfall.
+   * @throws {ValidationError} when the new config fails validation.
+   */
   update(config: any, noSave = false) {
     this.assertActive()
     config = resolveConfig(this.runtime!, config)

+ 9 - 1
vendor/cordis/src/logger.ts

@@ -62,8 +62,11 @@ export const defaultFormatters: Record<string, Formatter> = {
 
 /** Options used when creating a named logger facade. */
 export interface LoggerOptions {
+  /** The logger name shown with each message. */
   name: string
+  /** Message fields merged into every record from this logger. */
   meta?: Partial<Message>
+  /** Default maximum level exported when an exporter has no own threshold. */
   level?: number
 }
 
@@ -220,7 +223,12 @@ export class LoggerService {
     return self
   }
 
-  /** Register an exporter and dispose it with the current fiber. */
+  /**
+   * Register an exporter and dispose it with the current fiber.
+   *
+   * @param exporter — the sink that receives structured log messages.
+   * @returns a disposer that removes the exporter.
+   */
   exporter(exporter: Exporter) {
     return this.ctx.effect(() => {
       this.exporters.set(++this._snExporter, exporter)

+ 125 - 0
vendor/cordis/src/reflect.ts

@@ -5,14 +5,66 @@ import { Fiber, FiberState } from './fiber.ts'
 
 declare module './context.ts' {
   interface Context {
+    /**
+     * Read a service from the store without the inject requirement.
+     *
+     * @param name — the service name.
+     * @param strict — when `true` (default), only return implementations
+     * whose providing fiber is currently active.
+     * @returns the service value, or `undefined` when not (yet) provided.
+     */
     get<K extends string & keyof this>(name: K, strict?: boolean): undefined | this[K]
+    /** Same as above for service names outside the typed `Context` surface. */
     get(name: string, strict?: boolean): any
+    /**
+     * Overwrite a provided service's value.
+     *
+     * Only the fiber that provided the service may set it; setting an
+     * unprovided name throws.
+     *
+     * @param name — the service name.
+     * @param value — the new service value.
+     */
     set<K extends string & keyof this>(name: K, value: undefined | this[K]): void
+    /** Same as above for service names outside the typed `Context` surface. */
     set(name: string, value: any): void
+    /**
+     * Register a service implementation owned by the current fiber.
+     *
+     * The service becomes visible to dependents in the same isolation scope
+     * once the fiber is active; it is unregistered (waking dependents) when
+     * the returned disposer runs or the fiber unloads. Throws if the name is
+     * already provided in this scope or declared as an accessor.
+     *
+     * @param name — the service name.
+     * @param value — the service value.
+     * @returns a disposer that unregisters the service.
+     */
     provide<K extends string & keyof this>(name: K, value: undefined | this[K]): () => void
+    /** Same as above for service names outside the typed `Context` surface. */
     provide(name: string, value?: any): () => void
+    /**
+     * Define a computed context property backed by get/set hooks.
+     *
+     * The accessor is removed when the current fiber unloads. Throws if the
+     * name is already declared.
+     *
+     * @param name — the context property name.
+     * @param options — the `get` hook and optional `set` hook.
+     */
     accessor(name: string, options: Omit<Property.Accessor, 'type'>): void
+    /**
+     * Expose selected members of a service directly on `ctx`.
+     *
+     * Each mixed-in key becomes an accessor that forwards to the service
+     * (binding methods to it), so e.g. `ctx.on` forwards to `ctx.events.on`.
+     * Mixins are removed when the current fiber unloads.
+     *
+     * @param name — the context property holding the source service.
+     * @param mixins — keys to forward, or a source-key → ctx-key map.
+     */
     mixin<K extends string & keyof this>(name: K, mixins: (keyof this & keyof this[K])[] | Dict<string>): void
+    /** Same as above with a source object instead of a context property name. */
     mixin<T extends {}>(source: T, mixins: (keyof this & keyof T)[] | Dict<string>): void
   }
 }
@@ -44,22 +96,30 @@ export type Property = Property.Service | Property.Accessor
 export namespace Property {
   /** Service property backed by a provided implementation. */
   export interface Service {
+    /** Discriminator. */
     type: 'service'
   }
 
   /** Computed context property backed by custom get/set hooks. */
   export interface Accessor {
+    /** Discriminator. */
     type: 'accessor'
+    /** Compute the property value; `error` carries the caller stack for diagnostics. */
     get: (this: Context, receiver: any, error: Error) => any
+    /** Optional setter; return `false` to reject the write. */
     set?: (this: Context, value: any, receiver: any, error: Error) => boolean
   }
 }
 
 /** Concrete service implementation record stored in the root reflect service. */
 export interface Impl {
+  /** The service name. */
   name: string
+  /** The fiber that provided the service (owns its lifetime). */
   fiber: Fiber
+  /** The current service value. */
   value?: any
+  /** Optional availability predicate consulted before dependents may load. */
   check?: () => boolean
 }
 
@@ -70,6 +130,7 @@ export interface Impl {
  * the mixins that expose core service methods directly on `ctx`.
  */
 export class ReflectService {
+  /** Proxy traps implementing service resolution for every context object. */
   static handler: ProxyHandler<Context> = {
     get: (target, prop, ctx: Context) => {
       if (isSpecialProperty(prop)) {
@@ -143,7 +204,9 @@ export class ReflectService {
     },
   }
 
+  /** Service implementations, keyed by isolation label. */
   public store: Dict<Impl, symbol> = Object.create(null)
+  /** Declared context properties (services and accessors), by name. */
   public props: Dict<Property> = Object.create(null)
 
   constructor(public ctx: Context) {
@@ -158,6 +221,14 @@ export class ReflectService {
     this.mixin('events', ['on', 'once', 'parallel', 'emit', 'serial', 'bail', 'waterfall'])
   }
 
+  /**
+   * Read a service from the store without the inject requirement.
+   *
+   * @param name — the service name.
+   * @param strict — when `true`, only return implementations whose providing
+   * fiber is currently active.
+   * @returns the service value, or `undefined` when not (yet) provided.
+   */
   get(name: string, strict = true) {
     return getTraceable(this.ctx, this._getImpl(name, strict)?.value)
   }
@@ -170,6 +241,15 @@ export class ReflectService {
     return impl
   }
 
+  /**
+   * Overwrite a provided service's value.
+   *
+   * @param name — the service name.
+   * @param value — the new service value.
+   * @param error — carrier for the caller stack in diagnostics.
+   * @returns `true` on success.
+   * @throws when `name` was never provided, or was provided by another fiber.
+   */
   set(name: string, value: any, error?: Error) {
     const key = this.ctx[symbols.isolate][name]
     const impl = this.store[key]
@@ -183,6 +263,16 @@ export class ReflectService {
     return true
   }
 
+  /**
+   * Register a service implementation owned by the current fiber.
+   *
+   * See the `ctx.provide()` overload above for the full contract.
+   *
+   * @param name — the service name.
+   * @param value — the service value.
+   * @param check — optional availability predicate for dependents.
+   * @returns a disposer that unregisters the service.
+   */
   provide(name: string, value?: any, check?: () => boolean) {
     return this.ctx.fiber.effect(() => {
       if (!this.props[name]) {
@@ -213,6 +303,13 @@ export class ReflectService {
     }, `ctx.provide(${JSON.stringify(name)})`)
   }
 
+  /**
+   * Re-evaluate every fiber that requires one of the given services.
+   *
+   * @param names — the service names that changed.
+   * @param filter — restricts notification to matching isolation scopes.
+   * @returns the fibers whose dependency state was refreshed.
+   */
   notify(names: string[], filter = (ctx: Context, name: string) => ctx[symbols.isolate][name] === this.ctx[symbols.isolate][name]) {
     const fibers: Fiber[] = []
     for (const runtime of this.ctx.registry.values()) {
@@ -232,6 +329,13 @@ export class ReflectService {
     return fibers
   }
 
+  /**
+   * Define a computed context property backed by get/set hooks.
+   *
+   * @param name — the context property name.
+   * @param options — the `get` hook and optional `set` hook.
+   * @returns a disposer that removes the accessor.
+   */
   accessor(name: string, options: Omit<Property.Accessor, 'type'>) {
     return this.ctx.fiber.effect(() => {
       if (name in this.props) {
@@ -242,6 +346,15 @@ export class ReflectService {
     }, `ctx.accessor(${JSON.stringify(name)})`)
   }
 
+  /**
+   * Expose selected members of a service directly on `ctx`.
+   *
+   * See the `ctx.mixin()` overload above for the full contract.
+   *
+   * @param source — a context property name or a source object.
+   * @param mixins — keys to forward, or a source-key → ctx-key map.
+   * @returns a disposer that removes all created accessors.
+   */
   mixin(source: any, mixins: string[] | Dict<string>) {
     const self = this
     return this.ctx.fiber.effect(function* () {
@@ -270,10 +383,22 @@ export class ReflectService {
     }, `ctx.mixin(${JSON.stringify(source)})`)
   }
 
+  /**
+   * Attach this context's tracing wrapper to a value.
+   *
+   * @param value — the value to wrap.
+   * @returns the traceable wrapper (or the value itself when not applicable).
+   */
   trace<T>(value: T) {
     return getTraceable(this.ctx, value)
   }
 
+  /**
+   * Wrap a callback so calls trace `this` and arguments to this context.
+   *
+   * @param callback — the function to wrap.
+   * @returns a proxy delegating to `callback` with traced values.
+   */
   bind<T extends Function>(callback: T) {
     return new Proxy(callback, {
       apply: (target, thisArg, args) => {

+ 92 - 5
vendor/cordis/src/registry.ts

@@ -28,6 +28,11 @@ export type InjectKey = keyof {
  * On classes it contributes to the plugin's static `inject` map. On methods it
  * delays the method call until the declared services are available.
  */
+/**
+ * @param name — the required service name.
+ * @param config — optional intercept config applied for that service.
+ * @returns the class or method decorator.
+ */
 export function Inject<K extends InjectKey>(name: K, config?: Context[K] extends { [symbols.config]: infer T } ? T : never) {
   return function (value: any, decorator: ClassDecoratorContext<any> | ClassMethodDecoratorContext<any>) {
     if (decorator.kind === 'class') {
@@ -55,7 +60,13 @@ export function Inject<K extends InjectKey>(name: K, config?: Context[K] extends
 
 /** Utilities for normalizing plugin dependency declarations. */
 export namespace Inject {
-  /** Convert array/object/class-inherited inject metadata into a plain map. */
+  /**
+   * Convert array/object/class-inherited inject metadata into a plain map.
+   *
+   * @param inject — the declaration to normalize; `null`/`undefined` add nothing.
+   * @param result — the map to fill (service name → intercept config or `null`).
+   * @returns `result`.
+   */
   export function resolve(inject: Inject | null | undefined, result: Dict = Object.create(null)) {
     if (!inject) return result
     if (Array.isArray(inject)) {
@@ -86,10 +97,15 @@ export type Plugin<T = any> =
 export namespace Plugin {
   /** Shared metadata understood by the plugin registry and related tooling. */
   export interface Base<T = any> {
+    /** Display name used for fiber diagnostics and logger names. */
     name?: string
+    /** Standard-schema validator applied to config before the plugin starts. */
     Config?: StandardSchemaV1<any, T>
+    /** Services the plugin requires; it only loads while all are available. */
     inject?: Inject
+    /** Service name(s) the plugin provides (read by `Service` and by loaders). */
     provide?: string | string[]
+    /** Service names whose intercept config the plugin declares it consumes. */
     intercept?: Dict<boolean>
   }
 
@@ -117,9 +133,13 @@ export namespace Plugin {
 
   /** Mutable registry record shared by all fibers of one plugin callback. */
   export interface Runtime {
+    /** Display name copied from the first registered plugin shape. */
     name?: string
+    /** Every live fiber of this plugin (one per `ctx.plugin()` call). */
     fibers: DisposableList<Fiber>
+    /** The executable entrypoint all fibers share (registry identity key). */
     callback: globalThis.Function
+    /** Standard-schema validator applied to each fiber's config. */
     Config?: StandardSchemaV1
   }
 }
@@ -142,7 +162,25 @@ type GetPluginConfig<P> =
 
 declare module './context.ts' {
   export interface Context {
+    /**
+     * Run a callback once the requested services are available.
+     *
+     * Shorthand for `ctx.plugin({ inject, apply: callback })`: the callback
+     * is unloaded and re-run whenever a required service changes.
+     *
+     * @param deps — required services, as an array or a name → config map.
+     * @param callback — plugin body called with `(ctx, config)`.
+     * @returns the fiber; awaiting it settles once loading finished.
+     */
     inject(deps: Inject, callback: Plugin.Function<void>): Fiber & PromiseLike<Fiber>
+    /**
+     * Load a plugin in the current context.
+     *
+     * @param plugin — a function, class, or `{ apply }` object plugin.
+     * @param args — the plugin config, validated against its `Config` schema.
+     * @returns the fiber; awaiting it settles once loading finished
+     * (rejecting on config or startup errors).
+     */
     plugin<P extends Plugin>(plugin: P, ...args: Spread<GetPluginConfig<P>>): Fiber & PromiseLike<Fiber>
   }
 }
@@ -164,15 +202,22 @@ export class RegistryService {
     })
   }
 
+  /** Allocate the next fiber uid (increments on every read). */
   get counter() {
     return ++this._counter
   }
 
+  /** Number of registered plugin runtimes. */
   get size() {
     return this._internal.size
   }
 
-  /** Resolve a supported plugin shape to its executable callback. */
+  /**
+   * Resolve a supported plugin shape to its executable callback.
+   *
+   * @param plugin — a function, class, or `{ apply }` object plugin.
+   * @returns the callback identifying the plugin, or `undefined` if invalid.
+   */
   resolve(plugin: Plugin): Function | undefined {
     // plugin.apply may throw
     try {
@@ -181,17 +226,34 @@ export class RegistryService {
     } catch {}
   }
 
+  /**
+   * Look up the runtime record for a plugin.
+   *
+   * @param plugin — any supported plugin shape.
+   * @returns the runtime, or `undefined` when the plugin is not registered.
+   */
   get(plugin: Plugin) {
     const key = this.resolve(plugin)
     return key && this._internal.get(key)
   }
 
+  /**
+   * Check whether a plugin has a registered runtime.
+   *
+   * @param plugin — any supported plugin shape.
+   * @returns `true` when at least one fiber of the plugin exists.
+   */
   has(plugin: Plugin) {
     const key = this.resolve(plugin)
     return !!key && this._internal.has(key)
   }
 
-  /** Dispose every running fiber for a plugin and remove its runtime record. */
+  /**
+   * Dispose every running fiber for a plugin and remove its runtime record.
+   *
+   * @param plugin — any supported plugin shape.
+   * @returns the removed runtime, or `undefined` when none was registered.
+   */
   delete(plugin: Plugin) {
     const key = this.resolve(plugin)
     const runtime = key && this._internal.get(key)
@@ -203,28 +265,53 @@ export class RegistryService {
     return runtime
   }
 
+  /** Iterate the registered plugin callbacks. */
   keys() {
     return this._internal.keys()
   }
 
+  /** Iterate the registered plugin runtimes. */
   values() {
     return this._internal.values()
   }
 
+  /** Iterate `[callback, runtime]` pairs. */
   entries() {
     return this._internal.entries()
   }
 
+  /**
+   * Visit every registered runtime.
+   *
+   * @param callback — receives each runtime and its identifying callback.
+   */
   forEach(callback: (value: Plugin.Runtime, key: Function) => void) {
     return this._internal.forEach(callback)
   }
 
-  /** Start a callback once the requested dependencies are available. */
+  /**
+   * Start a callback once the requested dependencies are available.
+   *
+   * @param inject — required services, as an array or a name → config map.
+   * @param callback — plugin body called with `(ctx, config)`.
+   * @returns the fiber; awaiting it settles once loading finished.
+   */
   inject(inject: Inject, callback: Plugin.Function<void>) {
     return this.plugin({ inject, apply: callback, name: callback.name })
   }
 
-  /** Start a plugin in the current context and return its fiber. */
+  /**
+   * Start a plugin in the current context and return its fiber.
+   *
+   * Creates (or reuses) the plugin's runtime record, then starts a new fiber
+   * under the current context. Throws if `plugin` is not a supported shape or
+   * if the current fiber is already disposed.
+   *
+   * @param plugin — a function, class, or `{ apply }` object plugin.
+   * @param config — the plugin config, validated against its `Config` schema.
+   * @param getOuterStack — captures the caller stack for effect diagnostics.
+   * @returns the fiber; awaiting it settles once loading finished.
+   */
   plugin(plugin: Plugin, config?: any, getOuterStack = buildOuterStack()) {
     // check if it's a valid plugin
     const callback = this.resolve(plugin)

+ 29 - 2
vendor/cordis/src/service.ts

@@ -9,19 +9,36 @@ import { createCallable, joinPrototype, symbols, Tracker } from './utils.ts'
  * registered immediately and is automatically removed with the owning fiber.
  */
 export abstract class Service<out T = never> {
+  /** Symbol key of an instance method run after construction (class plugins). */
   static readonly init: unique symbol = symbols.init
+  /** Symbol key of the availability predicate passed to `ctx.provide()`. */
   static readonly check: unique symbol = symbols.check
+  /** Symbol key of the phantom intercept-config type parameter. */
   static readonly config: unique symbol = symbols.config
+  /** Symbol key of the call body making a service callable (e.g. `ctx.logger()`). */
   static readonly invoke: unique symbol = symbols.invoke
+  /** Symbol key of the helper deriving an extended service instance. */
   static readonly extend: unique symbol = symbols.extend
+  /** Symbol key of the tracker metadata used for context tracing. */
   static readonly tracker: unique symbol = symbols.tracker
+  /** Symbol key of the intercept-config resolution helper below. */
   static readonly resolveConfig: unique symbol = symbols.resolveConfig
 
   declare [symbols.config]: T
 
+  /** The service name this instance is registered under. */
   public name!: string
 
-  /** Register this instance as `name` in the current context. */
+  /**
+   * Register this instance as `name` in the current context.
+   *
+   * Calls `ctx.reflect.provide(name, this, this[Service.check])`, so the
+   * service is unregistered automatically when the owning fiber unloads.
+   * Services with a `[Service.invoke]` body return a callable instance.
+   *
+   * @param ctx — the context to register in (stored as `this.ctx`).
+   * @param name — the service name; defaults to the static `provide` field.
+   */
   constructor(protected ctx: Context, name: string) {
     name ??= this.constructor['provide'] as string
 
@@ -55,7 +72,17 @@ export abstract class Service<out T = never> {
     return Object.assign(self, props)
   }
 
-  /** Merge intercept config from ancestors with optional base and head values. */
+  /**
+   * Merge intercept config from ancestors with optional base and head values.
+   *
+   * Entries added closer to the root apply first; `base` is prepended and
+   * `head` appended. Uses `Config.merge` when the service declares one,
+   * otherwise a shallow `Object.assign`.
+   *
+   * @param base — lowest-precedence config merged before all intercepts.
+   * @param head — highest-precedence config merged after all intercepts.
+   * @returns the merged config.
+   */
   [symbols.resolveConfig](base?: T, head?: T): T {
     let intercept = this.ctx[Context.intercept]
     const configs: any[] = []