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.
This commit is contained in:
lintianle
2026-07-16 18:12:36 +08:00
parent 6ce9f16030
commit 83cb48441e
11 changed files with 587 additions and 55 deletions
+107 -10
View File
@@ -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)