Files
deepseek-harness/vendor/cordis/src/fiber.ts
T
Tianyi Cui 72688a3888 Vendor Cordis framework packages as source
cordis 4.0.0-rc.6, plugin-loader, -include, -group, -timer, -hmr,
-logger-console, cosmokit 1.8.1, schemastery 3.18.0 — copied from the
cordis-workspace checkout, flattened under vendor/, original npm names,
private: true. vendor/README.md is the manifest: upstream repos +
commit SHAs, local-modification log, sync procedure.

Local modification: hmr's locale YAML imports and .i18n() call removed
(avoids a runtime YAML import hook we don't vendor).
2026-06-11 10:53:32 +08:00

505 lines
14 KiB
TypeScript

import { Awaitable, defineProperty, Dict, isNullable } from 'cosmokit'
import { Context } from './context'
import { Plugin } from './registry'
import { buildOuterStack, composeError, DisposableList, getTraceable, isConstructor, isObject, symbols } from './utils'
import { Impl } from './reflect'
import { StandardSchemaV1 } from '@standard-schema/spec'
declare module './context' {
export interface Context extends Pick<Fiber, 'effect'> {
fiber: Fiber
}
}
const kValidationError = Symbol.for('ValidationError')
/** Error raised when plugin configuration fails standard-schema validation. */
export class ValidationError extends TypeError {
name = 'ValidationError'
constructor(issues: readonly StandardSchemaV1.Issue[]) {
super(`invalid config:\n` + issues.map(issue => {
if (issue.path) {
return ` - ${issue.message} (at ${issue.path.join('.')})`
} else {
return ` - ${issue.message}`
}
}).join('\n'))
}
}
Object.defineProperty(ValidationError.prototype, kValidationError, {
value: true,
})
/** Validate and normalize config for a plugin runtime before it starts. */
export function resolveConfig(runtime: Plugin.Runtime, config: any) {
if (!runtime.Config) return config
// TODO: async validation
const result = runtime.Config['~standard'].validate(config)
if ('then' in result) {
throw new TypeError('Async config validation is not supported')
}
if (result.issues) {
throw new ValidationError(result.issues)
} else {
return result.value
}
}
interface AsyncDisposable<T extends Awaitable<void> = Awaitable<void>> extends PromiseLike<() => T> {
(): T
}
/** Function returned by an effect to release resources during disposal. */
export type Disposable<T = any> = () => T
/** Effect body result accepted by `ctx.effect()` and plugin startup. */
export type Effect<T = any> =
| SyncEffect<T>
| AsyncEffect<T>
type SyncEffect<T = any> =
| Disposable<T>
| Iterable<Disposable<T>, void, void>
type AsyncEffect<T = any> =
| Promise<Disposable<T>>
| AsyncIterable<Disposable<T>, void, void>
/** Tree node used to expose nested effect labels for diagnostics. */
export interface EffectMeta {
label: string
children: EffectMeta[]
}
interface EffectRunner<T> {
epoch: T
execute: () => any
collect: (dispose: Disposable) => void
getOuterStack: () => string[]
}
/** Lifecycle state for one plugin fiber. */
export const enum FiberState {
PENDING,
LOADING,
ACTIVE,
FAILED,
DISPOSED,
UNLOADING,
}
/** Framework error with a stable machine-readable code. */
export class CordisError extends Error {
constructor(public code: CordisError.Code, message?: string) {
super(message ?? CordisError.Code[code])
}
}
/** Cordis error code definitions. */
export namespace CordisError {
export type Code = keyof typeof Code
export const Code = {
INACTIVE_EFFECT: 'cannot create effect on inactive context',
} as const
}
const INACTIVE = '__INACTIVE__'
/**
* Runtime instance of one plugin application.
*
* A fiber tracks dependency state, validated config, lifecycle effects, and
* cleanup for the plugin context returned by `ctx.plugin()`.
*/
export class Fiber {
public uid: number | null
public readonly ctx: Context
public config: any
public state = FiberState.PENDING
public readonly dispose: () => Promise<void>
public store: Dict<Impl> | undefined
public inertia: Promise<void> | undefined
public readonly _hooks: Dict<DisposableList<Function>> = Object.create(null)
public readonly _disposables = new DisposableList<Disposable>()
// Same as `this.ctx`, but with a more specific type.
protected context: Context
private _error: any
private _runner: EffectRunner<string>
private _store: Dict<Impl> = Object.create(null)
constructor(
public parent: Context,
config: any,
public inject: Dict<any>,
public runtime: Plugin.Runtime | null,
getOuterStack: () => string[],
) {
const collect = (dispose: Disposable) => {
this._disposables.push(dispose)
}
if (runtime) {
this.uid = parent.registry.counter
this.ctx = this.context = parent.extend({ fiber: this })
const injectEntries = Object.entries(this.inject)
if (injectEntries.length) {
this.ctx[Context.intercept] = Object.create(parent[Context.intercept])
for (const [name, config] of injectEntries) {
if (isNullable(config)) continue
this.ctx[Context.intercept][name] = config
}
}
this._runner = {
epoch: INACTIVE,
getOuterStack,
execute: () => {
if (isConstructor(runtime.callback)) {
// eslint-disable-next-line new-cap
const instance = new runtime.callback(this.ctx, this.config)
for (const hook of instance?.[symbols.initHooks] ?? []) {
hook()
}
return instance?.[symbols.init]?.()
} else {
return runtime.callback(this.ctx, this.config)
}
},
collect,
}
this.context.emit('internal/plugin', this)
for (const name of Object.keys(this.inject)) {
this._checkImpl(name)
}
this.dispose = parent.fiber.effect(() => {
const remove = runtime.fibers.push(this)
try {
this.config = resolveConfig(runtime, config)
this._refresh()
} catch (error) {
this.ctx.logger.error(error)
this._error = error
}
return async () => {
this.uid = null
this.context.emit('internal/plugin', this)
if (this.ctx.registry.has(runtime.callback)) {
remove()
if (!runtime.fibers.length) {
this.ctx.registry.delete(runtime.callback)
}
}
this._setEpoch(INACTIVE)
// `this.inertia` itself should never reject — both `_reload` and
// `_unload` swallow their own work errors via `ctx.logger.error`.
// If it *does* reject, the only remaining cause is the logger
// itself failing, which we can't recover from in this exact spot
// (calling the logger again is what just failed). Let the
// rejection propagate; process-level crash is the honest outcome.
while (this.inertia) {
await this.inertia
}
}
}, 'ctx.plugin()')
} else {
this.uid = 0
this.ctx = this.context = parent
this.state = FiberState.ACTIVE
this.store = Object.create(null)
this._runner = {
epoch: '',
getOuterStack,
execute: () => {},
collect,
}
this.dispose = () => this.restart()
}
}
get name() {
let fiber: Fiber = this
do {
if (fiber.runtime?.name) return fiber.runtime.name
fiber = fiber.parent.fiber
} while (fiber !== fiber.parent.fiber)
return 'root'
}
/** Throw if the fiber has already been disposed. */
assertActive() {
if (this.uid !== null) return
throw new CordisError('INACTIVE_EFFECT')
}
private _execute<T>(runner: EffectRunner<T>) {
const oldEpoch = runner.epoch
return composeError((info) => {
const safeCollect = (dispose: void | Disposable) => {
if (typeof dispose === 'function') {
runner.collect(dispose)
} else if (!isNullable(dispose)) {
throw new TypeError('Invalid effect')
}
}
const effect: Effect = runner.execute()
if (typeof effect === 'function') {
return runner.collect(effect)
} else if (isNullable(effect)) {
// return
} else if (!isObject(effect)) {
throw new TypeError('Invalid effect')
} else if ('then' in effect) {
return effect.then(safeCollect)
} else if (Symbol.iterator in effect) {
info.error = new Error()
const iter = effect[Symbol.iterator]()
while (true) {
const result = iter.next()
safeCollect(result.value)
if (result.done) return
}
} else if (Symbol.asyncIterator in effect) {
const iter = effect[Symbol.asyncIterator]()
return (async () => {
// force async stack trace
await Promise.resolve()
info.error = new Error()
while (true) {
if (runner.epoch !== oldEpoch) return
const result = await iter.next()
safeCollect(result.value)
if (result.done) return
}
})()
} else {
throw new TypeError('Invalid effect')
}
}, runner.getOuterStack)
}
/** Register a cleanup-aware effect on this fiber. */
effect(execute: () => SyncEffect, label?: string): Disposable<Promise<void>>
effect(execute: () => Effect, label?: string): AsyncDisposable<Promise<void>>
effect(execute: () => Effect, label = 'anonymous'): any {
this.assertActive()
const disposables: Disposable[] = []
const dispose = () => {
let task!: void | Promise<void>
for (const dispose of disposables.splice(0).reverse()) {
if (task) {
task = task.then(dispose)
} else {
const result = dispose()
if (isObject(result) && 'then' in result) {
task = result as any
}
}
}
return task
}
const meta: EffectMeta = { label, children: [] }
const runner: EffectRunner<boolean> = {
execute,
epoch: true,
collect: (dispose) => {
disposables.push(dispose)
this._disposables.delete(dispose)
if (dispose[symbols.effect]) {
meta.children.push(dispose[symbols.effect])
}
},
getOuterStack: buildOuterStack(),
}
let task: void | Promise<void>
try {
task = this._execute(runner)
} catch (reason) {
dispose()
throw reason
}
// prevent unhandled rejection — both from `task` itself and from the
// disposer chain if it fails to settle cleanly.
task?.catch(dispose).catch((error) => this.ctx.logger.error(error))
const wrapper = defineProperty(() => {
if (!runner.epoch) return
runner.epoch = false
return task ? task.then(dispose) : dispose()
}, symbols.effect, meta) as AsyncDisposable
const disposeAsync = () => {
if (!runner.epoch) return
runner.epoch = false
return dispose()
}
wrapper.then = async (onFulfilled, onRejected) => {
return Promise.resolve(task)
.then(() => disposeAsync)
.then(onFulfilled, onRejected)
}
disposables.push(this._disposables.push(wrapper))
return wrapper
}
/** Return metadata for currently registered effects. */
getEffects() {
return [...this._disposables]
.map<EffectMeta>(dispose => dispose[symbols.effect])
.filter(Boolean)
}
private _getState() {
if (this.uid === null) return FiberState.DISPOSED
if (this._error) return FiberState.FAILED
if (this._runner.epoch !== INACTIVE) return FiberState.ACTIVE
return FiberState.PENDING
}
private _updateState(callback: () => void | FiberState) {
const oldState = this.state
this.state = callback() ?? this._getState()
if (oldState === this.state) return
// FIXME internal/fiber-info
this.context.emit('internal/status', this, oldState)
// only notify changes between ACTIVE and NON-ACTIVE states
if (oldState !== FiberState.ACTIVE && this.state !== FiberState.ACTIVE) return
for (const key of Reflect.ownKeys(this.ctx.reflect.store)) {
const impl = this.ctx.reflect.store[key as symbol]
if (impl.fiber !== this) continue
this.ctx.reflect.notify([impl.name])
}
}
_checkImpl(name: string) {
const impl = this.ctx.reflect._getImpl(name, true)
if (!impl) return delete this._store[name]
try {
if (impl.check && !impl.check.call(getTraceable(this.ctx, impl.value))) {
return delete this._store[name]
}
} catch (error) {
impl.fiber.ctx.logger.error(error)
return delete this._store[name]
}
this._store[name] = impl
}
_refresh() {
let epoch: string | boolean = false
epoch = ''
for (const name of Object.keys(this.inject)) {
const impl = this._store[name]
if (!impl) {
epoch = INACTIVE
break
}
epoch += ':' + impl.fiber.uid
}
this._setEpoch(epoch)
}
private _setEpoch(epoch: string) {
const oldEpoch = this._runner.epoch
if (epoch === oldEpoch) return
this._runner.epoch = epoch
if (this.inertia) return
this._updateState(() => {
if (epoch !== INACTIVE && oldEpoch === INACTIVE) {
this.inertia = this._reload()
return FiberState.LOADING
} else {
this.inertia = this._unload()
return FiberState.UNLOADING
}
})
}
private async _reload() {
this.store = { ...this._store }
const oldEpoch = this._runner.epoch
try {
await Promise.resolve()
await this._execute(this._runner)
} catch (reason) {
// impl guarantees that the error is non-null (?)
this.ctx.logger.error(reason)
this._error = reason
this._runner.epoch = INACTIVE
}
this._updateState(() => {
if (this._runner.epoch === oldEpoch) {
this.inertia = undefined
} else {
this.inertia = this._unload()
return FiberState.UNLOADING
}
})
}
private async _unload() {
await Promise.all(this._disposables.clear().map(async (dispose) => {
try {
await composeError(async (info) => {
await Promise.resolve()
info.error = new Error()
await dispose()
}, this._runner.getOuterStack)
} catch (reason) {
this.ctx.logger.error(reason)
}
}))
this.store = undefined
this._updateState(() => {
if (this._runner.epoch === INACTIVE) {
this.inertia = undefined
} else {
this.inertia = this._reload()
return FiberState.LOADING
}
})
}
/** Wait for current lifecycle work and rethrow startup errors. */
async await() {
while (this.inertia) {
await this.inertia
}
if (this._error) throw this._error
return this
}
/** Dispose and immediately reload this plugin with its current config. */
async restart() {
this.assertActive()
this._setEpoch(INACTIVE)
this._refresh()
await this.await()
}
/** Validate and apply new config, then restart the plugin. */
update(config: any, noSave = false) {
this.assertActive()
config = resolveConfig(this.runtime!, config)
this.context.waterfall(this, 'internal/update', config, noSave, () => {
this.config = config
this._error = undefined
return this.restart()
})
}
}