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).
This commit is contained in:
Tianyi Cui
2026-06-11 10:53:32 +08:00
parent ae2e08b4d6
commit 72688a3888
69 changed files with 6659 additions and 0 deletions

97
vendor/cordis/src/context.ts vendored Normal file
View File

@@ -0,0 +1,97 @@
import { Dict } from 'cosmokit'
import { EventsService } from './events'
import { LoggerService } from './logger'
import { ReflectService } from './reflect'
import { InjectKey, RegistryService } from './registry'
import { getTraceable, symbols } from './utils'
import { Fiber } from './fiber'
/**
* Public shape of a Cordis context.
*
* The concrete `Context` class is proxied at runtime, so this interface is
* augmented by core services and plugins to describe the properties that may
* be read from `ctx`.
*/
export interface Context {
[symbols.isolate]: Dict<symbol>
[symbols.intercept]: Dict
/** @experimental */
root: this
baseUrl?: string
events: EventsService
logger: LoggerService
reflect: ReflectService
registry: RegistryService
}
/**
* Root and child dependency containers for Cordis plugins.
*
* A context is a proxy: normal property reads go through the service resolver,
* while `extend()`, `isolate()`, and `intercept()` create scoped child
* contexts without mutating their parent.
*/
export class Context {
static readonly effect: unique symbol = symbols.effect
static readonly filter: unique symbol = symbols.filter
static readonly isolate: unique symbol = symbols.isolate
static readonly intercept: unique symbol = symbols.intercept
/** Returns true for Cordis context proxies and context prototypes. */
static is(value: any): value is Context {
return !!value?.[Context.is as any]
}
static {
Context.is[Symbol.toPrimitive] = () => Symbol.for('cordis.is')
Context.prototype[Context.is as any] = true
}
/** Create the root context and install the built-in services. */
constructor() {
this[symbols.isolate] = Object.create(null)
this[symbols.intercept] = Object.create(null)
const self = new Proxy<this>(this, ReflectService.handler)
this.root = self
this.baseUrl = undefined
this.fiber = new Fiber(self, {}, Object.create(null), null, () => [])
this.reflect = new ReflectService(self)
this.registry = new RegistryService(self)
this.events = new EventsService(self)
this.logger = new LoggerService(self)
this.fiber._disposables.clear()
return self
}
[Symbol.for('nodejs.util.inspect.custom')]() {
return `Context <${this.fiber.name}>`
}
/** Create a child context with extra metadata on top of the current scope. */
extend(meta = {}): this {
const shadow = Reflect.getOwnPropertyDescriptor(this, symbols.shadow)?.value
const self = Object.create(getTraceable(this, this))
for (const prop of Reflect.ownKeys(meta)) {
Object.defineProperty(self, prop, Reflect.getOwnPropertyDescriptor(meta, prop)!)
}
if (!shadow) return self
return Object.assign(Object.create(self), { [symbols.shadow]: shadow })
}
/** Create a child context with an independent service scope for `name`. */
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. */
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) {
const intercept = Object.create(this[symbols.intercept])
intercept[name] = config
return this.extend({ [symbols.intercept]: intercept })
}
}

205
vendor/cordis/src/events.ts vendored Normal file
View File

@@ -0,0 +1,205 @@
import { defineProperty, Promisify } from 'cosmokit'
import { Context } from './context'
import { Fiber, FiberState } from './fiber'
import { DisposableList, symbols } from './utils'
/** Return whether an event result should stop a bail-style dispatch. */
export function isBailed(value: any) {
return value !== null && value !== false && value !== undefined
}
/** Extract the parameter tuple from a function type. */
export type Parameters<F> = F extends (...args: infer P) => any ? P : never
/** Extract the return type from a function type. */
export type ReturnType<F> = F extends (...args: any) => infer R ? R : never
/** Extract the explicit `this` type from a function type. */
export type ThisType<F> = F extends (this: infer T, ...args: any) => any ? T : never
/**
* Event dispatch strategy used by the event service.
*
* `emit` runs synchronous listeners without awaiting them, `parallel` awaits
* all listeners together, `serial` awaits them in order until one bails,
* `bail` stops on the first synchronous bail value, and `waterfall` composes
* listeners around a final `next` callback.
*/
export type DispatchMode = 'emit' | 'parallel' | 'serial' | 'bail' | 'waterfall'
declare module './context' {
export interface Context {
/* eslint-disable max-len */
parallel<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): Promise<void>
parallel<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): Promise<void>
emit<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): void
emit<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): void
serial<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): Promisify<ReturnType<Events[K]>>
serial<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): Promisify<ReturnType<Events[K]>>
bail<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
bail<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
waterfall<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
waterfall<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
on<K extends keyof Events>(name: K, listener: Events[K], options?: boolean | EventOptions): () => boolean
once<K extends keyof Events>(name: K, listener: Events[K], options?: boolean | EventOptions): () => boolean
/* eslint-enable max-len */
}
}
/** Options accepted by `ctx.on()` and `ctx.once()`. */
export interface EventOptions {
/** Add the listener before existing listeners for the same event. */
prepend?: boolean
/** Receive the event regardless of context filter checks. */
global?: boolean
}
/** Registered listener record stored by the event service. */
export interface Hook extends EventOptions {
ctx: Context
callback: (...args: any[]) => any
}
/**
* Event bus installed as `ctx.events` and mixed into every context.
*
* The service supports concurrent, synchronous, serial, bail, and waterfall
* dispatch and automatically disposes listeners with their owning fiber.
*/
export class EventsService {
_hooks: Record<keyof any, Hook[]> = {}
constructor(private ctx: Context) {
defineProperty(this, symbols.tracker, {
property: 'ctx',
noShadow: true,
})
this.on('internal/listener', function (this: Context, name, listener, options: EventOptions) {
if (name === 'internal/update' && !options.global) {
const hooks = this.fiber._hooks['internal/update'] ??= new DisposableList()
const method = options.prepend ? 'unshift' : 'push'
return hooks[method](listener)
}
})
this.on('internal/update', function (config, noSave, next) {
const cbs = [...this._hooks['internal/update'] || []]
const _next = () => {
const cb = cbs.shift() ?? next
return cb.call(this, config, noSave, _next)
}
return _next()
}, { global: true, prepend: true })
}
/** Resolve listeners for one dispatch and apply context filtering. */
dispatch(type: string, args: any[]) {
const thisArg = typeof args[0] === 'object' || typeof args[0] === 'function' ? args.shift() : null
const name: string = args.shift()
if (!name.startsWith('internal/')) {
this.emit('internal/dispatch', type, name, args, thisArg)
}
const filter = thisArg?.[Context.filter]
return (this._hooks[name] || [])
.filter(hook => hook.global || !filter || filter.call(thisArg, hook.ctx))
.map(hook => hook.callback.bind(thisArg))
}
/** Run listeners concurrently and wait for all of them. */
async parallel(...args: any[]) {
await Promise.all(this.dispatch('emit', args).map(cb => cb(...args)))
}
/** Run listeners synchronously without waiting for returned promises. */
emit(...args: any[]) {
this.dispatch('emit', args).map(cb => cb(...args))
}
/** Run listeners in order until one returns a bail value. */
async serial(...args: any[]) {
for (const cb of this.dispatch('serial', args)) {
const result = await cb(...args)
if (isBailed(result)) return result
}
}
/** Run listeners synchronously until one returns a bail value. */
bail(...args: any[]) {
for (const cb of this.dispatch('bail', args)) {
const result = cb(...args)
if (isBailed(result)) return result
}
}
/** Compose listeners around the final `next` callback. */
waterfall(...args: any[]) {
const cbs = this.dispatch('waterfall', args)
const inner = args.pop()
const next = () => {
const cb = cbs.shift() ?? inner
return cb(...args)
}
args.push(next)
return next()
}
register(label: string, hooks: Hook[], callback: any, options: EventOptions): () => void {
const method = options.prepend ? 'unshift' : 'push'
return this.ctx.fiber.effect(() => {
hooks[method]({ ctx: this.ctx, callback, ...options })
return () => this.unregister(hooks, callback)
}, label)
}
unregister(hooks: Hook[], callback: any) {
const index = hooks.findIndex(hook => hook.callback === callback)
if (index >= 0) {
hooks.splice(index, 1)
return true
}
}
/** Register an event listener owned by the current fiber. */
on(name: string | symbol, listener: (...args: any) => any, options?: boolean | EventOptions) {
if (typeof options !== 'object') {
options = { prepend: options }
}
// handle special events
this.ctx.fiber.assertActive()
listener = this.ctx.reflect.bind(listener)
const result = this.bail(this.ctx, 'internal/listener', name, listener, options)
if (result) return result
const hooks = this._hooks[name] ||= []
const label = `ctx.on(${typeof name === 'string' ? JSON.stringify(name) : name.toString()})`
return this.register(label, hooks, listener, options)
}
/** Register an event listener that disposes itself after the first call. */
once(name: string, listener: (...args: any) => any, options?: boolean | EventOptions) {
const dispose = this.on(name, function (...args: any[]) {
dispose()
return listener.apply(this, args)
}, options)
return dispose
}
}
/**
* Built-in framework events used by core services and extension points.
*
* Plugin and status events track fiber lifecycle, service events observe
* dependency registration, update/get/set/listener events allow core services
* to intercept runtime operations, and `internal/dispatch` exposes event-bus
* diagnostics before public events are delivered.
*/
export interface Events {
'internal/plugin'(fiber: Fiber): void
'internal/status'(fiber: Fiber, oldValue: FiberState): void
'internal/service'(this: Context, name: string, value: any): void
'internal/update'(this: Fiber, config: any, noSave: boolean, next: () => void): void
'internal/get'(ctx: Context, name: string, error: Error, next: () => any): any
'internal/set'(ctx: Context, name: string, value: any, error: Error, next: () => boolean): boolean
'internal/listener'(this: Context, name: string, listener: any, prepend: boolean): void
'internal/dispatch'(mode: DispatchMode, name: string, args: any[], thisArg: any): void
}

504
vendor/cordis/src/fiber.ts vendored Normal file
View File

@@ -0,0 +1,504 @@
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()
})
}
}

14
vendor/cordis/src/index.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
/** Core context type and root context implementation. */
export * from './context'
/** Event bus, dispatch modes, and event augmentation types. */
export * from './events'
/** Plugin fiber lifecycle, effects, and config validation helpers. */
export * from './fiber'
/** Logger facade, logger service, message, exporter, and formatting types. */
export * from './logger'
/** Plugin registry, dependency injection, and plugin entrypoint types. */
export * from './registry'
/** Base service class and service lifecycle symbols. */
export * from './service'
/** Shared internal helpers used by context, services, and plugin fibers. */
export * from './utils'

262
vendor/cordis/src/logger.ts vendored Normal file
View File

@@ -0,0 +1,262 @@
import { defineProperty, hyphenate } from 'cosmokit'
import { Context } from './context'
import { Fiber } from './fiber'
import { createCallable, joinPrototype, symbols, Tracker } from './utils'
declare module './context' {
interface Intercept {
logger: LoggerService.Intercept
}
}
/** Logger method name and severity category. */
export type LoggerType = 'error' | 'info' | 'warn' | 'debug'
/** Callable shape for one logger severity method. */
export type LoggerMethod = (format: any, ...param: any[]) => void
/** Formatter used to resolve a printf-style placeholder. */
export type Formatter = (value: any, exporter: Exporter, message: Message) => any
/** Numeric severity used when exporters decide whether to emit a message. */
export const enum LoggerLevel {
ERROR = 0,
INFO = 1,
WARN = 2,
DEBUG = 3,
}
/** Structured log record delivered to exporters. */
export interface Message {
sn: number
ts: number
name: string
type: LoggerType
level: number
args: any[]
fiber?: WeakRef<Fiber>
}
/** Sink that receives structured log messages. */
export interface Exporter {
colors?: number | false
maxLength?: number
levels?: Record<string, number>
formatters?: Record<string, Formatter>
export(message: Message): void
}
/** Built-in placeholder formatters used by `Logger.format()`. */
export const defaultFormatters: Record<string, Formatter> = {
s: (value) => String(value),
d: (value) => Math.trunc(Number(value)),
i: (value) => Math.trunc(Number(value)),
f: (value) => Number(value),
o: (value) => JSON.stringify(value),
O: (value) => JSON.stringify(value),
c: () => '',
C: (value, exporter, message) => {
return Logger.color(exporter, Logger.code(message.name, exporter.colors), value)
},
}
/** Options used when creating a named logger facade. */
export interface LoggerOptions {
name: string
meta?: Partial<Message>
level?: number
}
/** Logger facade identity, inherited message metadata, and optional minimum level. */
export interface Logger extends LoggerOptions {}
/** Logger facade severity methods. */
export interface Logger extends Record<LoggerType, LoggerMethod> {}
function isAggregateError(error: any): error is Error & { errors: Error[] } {
return error instanceof Error && Array.isArray(error['errors'])
}
/** Logger facade for one named subsystem. */
export class Logger {
static color(exporter: Exporter, code: number, value: any, decoration = '') {
if (!exporter.colors) return '' + value
return `\u001b[3${code < 8 ? code : '8;5;' + code}${exporter.colors >= 2 ? decoration : ''}m${value}\u001b[0m`
}
static code(name: string, level?: false | number) {
let hash = 0
for (let i = 0; i < name.length; i++) {
hash = ((hash << 3) - hash) + name.charCodeAt(i) + 13
hash |= 0
}
const colors = !level ? [] : level >= 2 ? c256 : c16
return colors[Math.abs(hash) % colors.length]
}
static format(exporter: Exporter, message: Message): string {
const args = message.args.slice()
if (args[0] instanceof Error) {
args[0] = args[0].stack || args[0].message
args.unshift('%s')
} else if (typeof args[0] !== 'string') {
args.unshift('%o')
}
let format: string = args.shift()
format = format.replace(/%([a-zA-Z%])/g, (match, char) => {
if (match === '%%') return '%'
const formatter = exporter.formatters?.[char] ?? defaultFormatters[char]
if (typeof formatter === 'function') {
const value = args.shift()
return formatter(value, exporter, message)
}
return match
})
const oFormatter = exporter.formatters?.o ?? defaultFormatters.o
for (let arg of args) {
if (typeof arg === 'object' && arg) {
arg = oFormatter(arg, exporter, message)
}
format += ' ' + arg
}
const { maxLength = 10240 } = exporter
return format.split(/\r?\n/g).map(line => {
return line.slice(0, maxLength) + (line.length > maxLength ? '...' : '')
}).join('\n')
}
constructor(options: LoggerOptions, private service: LoggerService) {
Object.assign(this, options)
this.error = this._method('error', LoggerLevel.ERROR)
this.info = this._method('info', LoggerLevel.INFO)
this.warn = this._method('warn', LoggerLevel.WARN)
this.debug = this._method('debug', LoggerLevel.DEBUG)
}
private _method(type: LoggerType, level: number): LoggerMethod {
return (...args: any[]) => {
if (args.length === 1 && args[0] instanceof Error) {
if (args[0].cause) {
this[type](args[0].cause)
} else if (isAggregateError(args[0])) {
args[0].errors.forEach(error => this[type](error))
return
}
}
const sn = ++this.service._snMessage
const ts = Date.now()
for (const exporter of this.service.exporters.values()) {
const targetLevel = exporter.levels?.[this.name] ?? exporter.levels?.default ?? this.level ?? LoggerLevel.INFO
if (targetLevel < level) continue
const message: Message = { sn, ts, type, level, name: this.name, ...this.meta, args }
exporter.export(message)
}
}
}
}
/** ANSI 16-color palette indexes used for logger name coloring. */
export const c16 = [6, 2, 3, 4, 5, 1]
/** ANSI 256-color palette indexes used for logger name coloring. */
export const c256 = [
20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45, 56, 57, 62,
63, 68, 69, 74, 75, 76, 77, 78, 79, 80, 81, 92, 93, 98, 99, 112, 113,
129, 134, 135, 148, 149, 160, 161, 162, 163, 164, 165, 166, 167, 168,
169, 170, 171, 172, 173, 178, 179, 184, 185, 196, 197, 198, 199, 200,
201, 202, 203, 204, 205, 206, 207, 208, 209, 214, 215, 220, 221,
]
/** Logger service configuration merged from context intercepts. */
export namespace LoggerService {
export interface Intercept {
name?: string
level?: number
}
}
/** Callable `ctx.logger` service shape. */
export interface LoggerService extends Record<LoggerType, LoggerMethod> {
(name?: string): Logger
}
/**
* Built-in logging service.
*
* Call `ctx.logger()` to create a named logger, or call `ctx.logger.info()`
* directly to log with the current fiber-derived name.
*/
export class LoggerService {
bufferSize = 1000
buffer: Message[] = []
ctx!: Context
_snMessage = 0
_snExporter = 0
exporters = new Map<number, Exporter>()
constructor(ctx: Context) {
const tracker: Tracker = {
property: 'ctx',
noShadow: true,
}
const self = createCallable('logger', joinPrototype(Object.getPrototypeOf(this), Function.prototype), tracker) as unknown as LoggerService
Object.assign(self, this)
self.ctx = ctx
defineProperty(self, symbols.tracker, tracker)
self.exporter({
colors: 3,
export: (message) => {
self.buffer.push(message)
if (self.buffer.length > self.bufferSize) {
self.buffer = self.buffer.slice(-self.bufferSize)
}
},
})
return self
}
/** Register an exporter and dispose it with the current fiber. */
exporter(exporter: Exporter) {
return this.ctx.effect(() => {
this.exporters.set(++this._snExporter, exporter)
return () => this.exporters.delete(this._snExporter)
}, 'ctx.logger.exporter()')
}
private _resolveConfig(): LoggerService.Intercept {
let intercept = this.ctx[symbols.intercept]
const configs: LoggerService.Intercept[] = []
while ('logger' in intercept) {
if (Object.hasOwn(intercept, 'logger')) {
configs.unshift(intercept['logger'])
}
intercept = Object.getPrototypeOf(intercept)
}
return Object.assign({}, ...configs)
}
[symbols.invoke](name?: string): Logger {
const config = this._resolveConfig()
const fiber = ((this.ctx as any)[symbols.shadow] ?? this.ctx).fiber
name ??= config.name
name ??= hyphenate(fiber.name)
return new Logger({
name,
level: config.level,
meta: { fiber: new WeakRef(fiber) },
}, this)
}
static {
for (const type of ['error', 'info', 'warn', 'debug'] as const) {
;(LoggerService.prototype as any)[type] = function (this: LoggerService, ...args: any[]) {
return (this as any)()[type](...args)
}
}
}
}

287
vendor/cordis/src/reflect.ts vendored Normal file
View File

@@ -0,0 +1,287 @@
import { defineProperty, Dict, isNullable } from 'cosmokit'
import { Context } from './context'
import { getTraceable, symbols, withProps } from './utils'
import { Fiber, FiberState } from './fiber'
declare module './context' {
interface Context {
get<K extends string & keyof this>(name: K, strict?: boolean): undefined | this[K]
get(name: string, strict?: boolean): any
set<K extends string & keyof this>(name: K, value: undefined | this[K]): void
set(name: string, value: any): void
provide<K extends string & keyof this>(name: K, value: undefined | this[K]): () => void
provide(name: string, value?: any): () => void
accessor(name: string, options: Omit<Property.Accessor, 'type'>): void
mixin<K extends string & keyof this>(name: K, mixins: (keyof this & keyof this[K])[] | Dict<string>): void
mixin<T extends {}>(source: T, mixins: (keyof this & keyof T)[] | Dict<string>): void
}
}
function enhanceError(error: Error) {
const lines = error.stack!.split('\n')
lines.splice(0, 2, `Error: ${error.message}`)
error.stack = lines.join('\n')
return error
}
const RESERVED_WORDS = ['prototype', 'then']
// - is a symbol
// - is a reserved word (prototype, then)
// - is a number string (0, 1, 2, ...)
// - starts with `_`
function isSpecialProperty(prop: string | symbol): prop is symbol {
return typeof prop === 'symbol'
|| RESERVED_WORDS.includes(prop)
|| parseInt(prop).toString() === prop
|| prop.startsWith('_')
}
/** Context property definition known by the reflection service. */
export type Property = Property.Service | Property.Accessor
/** Property definition variants understood by `ReflectService`. */
export namespace Property {
/** Service property backed by a provided implementation. */
export interface Service {
type: 'service'
}
/** Computed context property backed by custom get/set hooks. */
export interface Accessor {
type: 'accessor'
get: (this: Context, receiver: any, error: Error) => any
set?: (this: Context, value: any, receiver: any, error: Error) => boolean
}
}
/** Concrete service implementation record stored in the root reflect service. */
export interface Impl {
name: string
fiber: Fiber
value?: any
check?: () => boolean
}
/**
* Reflection and service-resolution layer installed as `ctx.reflect`.
*
* This service powers the context proxy, service registration, accessors, and
* the mixins that expose core service methods directly on `ctx`.
*/
export class ReflectService {
static handler: ProxyHandler<Context> = {
get: (target, prop, ctx: Context) => {
if (isSpecialProperty(prop)) {
return Reflect.get(target, prop, ctx)
}
if (Reflect.has(target, prop)) {
return getTraceable(ctx, Reflect.get(target, prop, ctx))
}
const error = new Error(`cannot get property "${prop}" without inject`)
try {
const def = target.reflect.props[prop]
if (def?.type === 'accessor') {
return def.get.call(ctx, ctx[symbols.receiver], error)
}
if (!ctx.fiber.runtime) return ctx.reflect.get(prop, false)
return ctx.events.waterfall('internal/get', ctx, prop, error, () => {
const key = target[symbols.isolate][prop]
let fiber = (ctx[symbols.shadow] as Context ?? ctx).fiber
while (true) {
const impl = fiber.store?.[prop]
if (impl) return getTraceable(ctx, impl.value)
if (prop in fiber.inject) {
error.message = `cannot get required service "${prop}" in inactive context`
throw error
}
if (!fiber.runtime) throw error
if (fiber.parent[symbols.isolate][prop] !== key) throw error
fiber = fiber.parent.fiber
}
})
} catch (e: any) {
throw e === error ? enhanceError(e) : e
}
},
set: (target, prop, value, ctx: Context) => {
if (isSpecialProperty(prop)) {
return Reflect.set(target, prop, value, ctx)
}
const error = new Error(`cannot set property "${prop}" without provide`)
const def = target.reflect.props[prop]
if (!def) {
if (!ctx.fiber.runtime) return Reflect.set(target, prop, value, ctx)
throw enhanceError(error)
}
try {
if (def.type === 'accessor') {
if (!def.set) return false
return def.set.call(ctx, value, ctx[symbols.receiver], error)
}
return ctx.events.waterfall('internal/set', ctx, prop, value, error, () => {
return ctx.reflect.set(prop, value, error)
})
} catch (e: any) {
throw e === error ? enhanceError(e) : e
}
},
has: (target, prop) => {
if (isSpecialProperty(prop)) {
return Reflect.has(target, prop)
}
if (Reflect.has(target, prop)) return true
return !!target.reflect.props[prop]
},
}
public store: Dict<Impl, symbol> = Object.create(null)
public props: Dict<Property> = Object.create(null)
constructor(public ctx: Context) {
defineProperty(this, symbols.tracker, {
property: 'ctx',
noShadow: true,
})
this.mixin('reflect', ['get', 'set', 'provide', 'accessor', 'mixin'])
this.mixin('fiber', ['runtime', 'effect'])
this.mixin('registry', ['inject', 'plugin'])
this.mixin('events', ['on', 'once', 'parallel', 'emit', 'serial', 'bail', 'waterfall'])
}
get(name: string, strict = true) {
return getTraceable(this.ctx, this._getImpl(name, strict)?.value)
}
_getImpl(name: string, strict = true) {
const key = this.ctx[symbols.isolate][name]
const impl = key && this.store[key]
if (!impl) return
if (strict && impl.fiber.state !== FiberState.ACTIVE) return
return impl
}
set(name: string, value: any, error?: Error) {
const key = this.ctx[symbols.isolate][name]
const impl = this.store[key]
if (!impl) {
throw new Error(`cannot set property "${name}" without provide`)
}
if (impl.fiber !== this.ctx.fiber) {
throw new Error(`cannot set property "${name}" in multiple fibers`)
}
impl.value = value
return true
}
provide(name: string, value?: any, check?: () => boolean) {
return this.ctx.fiber.effect(() => {
if (!this.props[name]) {
this.props[name] ??= { type: 'service' }
} else if (this.props[name].type !== 'service') {
throw new Error(`property "${name}" is already declared as ${this.props[name].type}`)
}
this.props[name] = { type: 'service' }
this.ctx.root[symbols.isolate][name] ??= Symbol(name)
const key = this.ctx[symbols.isolate][name]
const impl: Impl = { name, value, fiber: this.ctx.fiber, check }
if (this.store[key]) {
throw new Error(`service "${name}" has been registered at <${this.store[key].fiber.name}>`)
}
this.store[key] = impl
this.ctx.fiber.store![name] = impl
if (this.ctx.fiber.state === FiberState.ACTIVE) {
this.notify([name])
}
return async () => {
delete this.store[key]
const fibers = this.notify([name])
await Promise.allSettled(fibers.map(fiber => fiber.await()))
// ensure self access before dependencies cleanup
delete this.ctx.fiber.store![name]
}
}, `ctx.provide(${JSON.stringify(name)})`)
}
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()) {
for (const fiber of runtime.fibers) {
let hasUpdate = false
for (const name of names) {
if (!(name in fiber.inject)) continue
if (!filter(fiber.ctx, name)) continue
hasUpdate = true
fiber._checkImpl(name)
}
if (!hasUpdate) continue
fiber._refresh()
fibers.push(fiber)
}
}
return fibers
}
accessor(name: string, options: Omit<Property.Accessor, 'type'>) {
return this.ctx.fiber.effect(() => {
if (name in this.props) {
throw new Error(`property "${name}" is already declared as ${this.props[name].type}`)
}
this.props[name] = { type: 'accessor', ...options }
return () => delete this.props[name]
}, `ctx.accessor(${JSON.stringify(name)})`)
}
mixin(source: any, mixins: string[] | Dict<string>) {
const self = this
return this.ctx.fiber.effect(function* () {
const entries = Array.isArray(mixins) ? mixins.map(key => [key, key]) : Object.entries(mixins)
const getTarget = (ctx: Context, error: Error) => {
// TODO enhance error message
return ctx[source]
}
for (const [key, value] of entries) {
yield self.accessor(value, {
get(receiver, error) {
const service = getTarget(this, error)
if (isNullable(service)) return service
const mixin = receiver ? withProps(receiver, service) : service
const value = Reflect.get(service, key, mixin)
if (typeof value !== 'function') return value
return value.bind(mixin ?? service)
},
set(value, receiver, error) {
const service = getTarget(this, error)
const mixin = receiver ? withProps(receiver, service) : service
return Reflect.set(service, key, value, mixin)
},
})
}
}, `ctx.mixin(${JSON.stringify(source)})`)
}
trace<T>(value: T) {
return getTraceable(this.ctx, value)
}
bind<T extends Function>(callback: T) {
return new Proxy(callback, {
apply: (target, thisArg, args) => {
return Reflect.apply(target, this.trace(thisArg), args.map(arg => this.trace(arg)))
},
construct: (target, args, newTarget) => {
return Reflect.construct(target, args.map(arg => this.trace(arg)), newTarget)
},
})
}
}

249
vendor/cordis/src/registry.ts vendored Normal file
View File

@@ -0,0 +1,249 @@
import { defineProperty, Dict } from 'cosmokit'
import { StandardSchemaV1 } from '@standard-schema/spec'
import { Context } from './context'
import { Fiber } from './fiber'
import { buildOuterStack, DisposableList, symbols, withProps } from './utils'
function isApplicable(object: Plugin) {
return object && typeof object === 'object' && typeof object.apply === 'function'
}
/**
* Service dependency declaration accepted by plugins and the `@Inject`
* decorator.
*
* Array form requests services without intercept config. Object form maps each
* service name to optional intercept config for the plugin context.
*/
export type Inject<M = Dict> = (keyof M)[] | { [K in keyof M]?: M[K] }
/** Context keys that correspond to services with typed intercept config. */
export type InjectKey = keyof {
[K in keyof Context & string as Context[K] extends { [symbols.config]: any } ? K : never]: any
}
/**
* Decorator for declaring service dependencies on classes or class methods.
*
* On classes it contributes to the plugin's static `inject` map. On methods it
* delays the method call until the declared services are available.
*/
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') {
if (!Object.hasOwn(value, 'inject')) {
defineProperty(value, 'inject', Object.create(Object.getPrototypeOf(value).inject ?? null))
defineProperty(value.inject, symbols.checkProto, true)
}
value.inject[name] = config
} else if (decorator.kind === 'method') {
const inject = (value[symbols.metadata] ??= {}).inject ??= Object.create(null)
inject[name] = config
decorator.addInitializer(function () {
const property = this[symbols.tracker]?.property
;(this[symbols.initHooks] ??= []).push(() => {
(this.ctx as Context).inject(inject, (ctx) => {
return value.call(property ? withProps(this, { [property]: ctx }) : this)
})
})
})
} else {
throw new Error('@Inject() can only be used on class or class methods')
}
}
}
/** Utilities for normalizing plugin dependency declarations. */
export namespace Inject {
/** Convert array/object/class-inherited inject metadata into a plain map. */
export function resolve(inject: Inject | null | undefined, result: Dict = Object.create(null)) {
if (!inject) return result
if (Array.isArray(inject)) {
for (const name of inject) {
result[name] = null
}
} else if (Reflect.has(inject, symbols.checkProto)) {
Object.assign(result, resolve(Object.getPrototypeOf(inject)))
for (const name of Object.keys(inject)) {
result[name] = inject[name] ?? null
}
} else {
for (const name of Object.keys(inject)) {
result[name] = inject[name] ?? null
}
}
return result
}
}
/** Supported plugin entrypoint shapes. */
export type Plugin<T = any> =
| Plugin.Function<T>
| Plugin.Constructor<T>
| Plugin.Object<T>
/** Types associated with plugin entrypoints and runtime records. */
export namespace Plugin {
/** Shared metadata understood by the plugin registry and related tooling. */
export interface Base<T = any> {
name?: string
Config?: StandardSchemaV1<any, T>
inject?: Inject
provide?: string | string[]
intercept?: Dict<boolean>
}
export interface Transform<S, T> {
/** Marks the transform object as a schema/config transform. */
schema?: true
/** Convert user-facing config to runtime config. */
Config: (config: S) => T
}
/** Function plugin called with `(ctx, config)`. */
export interface Function<T = any> extends Base<T> {
(ctx: Context, config: T): any
}
/** Class plugin constructed with `(ctx, config)`. */
export interface Constructor<T = any> extends Base<T> {
new (ctx: Context, config: T): any
}
/** Object plugin with an `apply(ctx, config)` method. */
export interface Object<T = any> extends Base<T> {
apply(ctx: Context, config: T): any
}
/** Mutable registry record shared by all fibers of one plugin callback. */
export interface Runtime {
name?: string
fibers: DisposableList<Fiber>
callback: globalThis.Function
Config?: StandardSchemaV1
}
}
type Spread<T> = undefined extends T ? [config?: T] : [config: T]
type GetPluginParameters<P> =
| P extends (ctx: Context, ...args: infer R) => any
? R
: P extends new (ctx: Context, ...args: infer R) => any
? R
: P extends { apply(ctx: Context, ...args: infer R): any }
? R
: never
type GetPluginConfig<P> =
| P extends Plugin.Transform<infer S, any>
? S
: GetPluginParameters<P>[0]
declare module './context' {
export interface Context {
inject(deps: Inject, callback: Plugin.Function<void>): Fiber & PromiseLike<Fiber>
plugin<P extends Plugin>(plugin: P, ...args: Spread<GetPluginConfig<P>>): Fiber & PromiseLike<Fiber>
}
}
/**
* Plugin registry installed as `ctx.registry` and mixed into every context.
*
* It normalizes plugin shapes, tracks plugin runtimes, starts fibers, and
* exposes map-like inspection over active plugin callbacks.
*/
export class RegistryService {
private _counter = 0
private _internal = new Map<Function, Plugin.Runtime>()
constructor(public ctx: Context) {
defineProperty(this, symbols.tracker, {
property: 'ctx',
noShadow: true,
})
}
get counter() {
return ++this._counter
}
get size() {
return this._internal.size
}
/** Resolve a supported plugin shape to its executable callback. */
resolve(plugin: Plugin): Function | undefined {
// plugin.apply may throw
try {
if (typeof plugin === 'function') return plugin
if (isApplicable(plugin)) return plugin.apply
} catch {}
}
get(plugin: Plugin) {
const key = this.resolve(plugin)
return key && this._internal.get(key)
}
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. */
delete(plugin: Plugin) {
const key = this.resolve(plugin)
const runtime = key && this._internal.get(key)
if (!runtime) return
this._internal.delete(key)
for (const fiber of runtime.fibers) {
fiber.dispose()
}
return runtime
}
keys() {
return this._internal.keys()
}
values() {
return this._internal.values()
}
entries() {
return this._internal.entries()
}
forEach(callback: (value: Plugin.Runtime, key: Function) => void) {
return this._internal.forEach(callback)
}
/** Start a callback once the requested dependencies are available. */
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. */
plugin(plugin: Plugin, config?: any, getOuterStack = buildOuterStack()) {
// check if it's a valid plugin
const callback = this.resolve(plugin)
if (!callback) throw new Error('invalid plugin, expect function or object with an "apply" method, received ' + typeof plugin)
this.ctx.fiber.assertActive()
let runtime = this._internal.get(callback)
if (!runtime) {
let name = plugin.name
if (name === 'apply') name = undefined
runtime = { name, callback, fibers: new DisposableList(), Config: plugin.Config }
this._internal.set(callback, runtime)
}
const fiber = new Fiber(this.ctx, config, Inject.resolve(plugin.inject), runtime, getOuterStack)
const wrapped = Object.create(fiber) as Fiber & PromiseLike<Fiber>
wrapped.then = (onFulfilled, onRejected) => {
return fiber.await().then(onFulfilled, onRejected)
}
return wrapped
}
}

88
vendor/cordis/src/service.ts vendored Normal file
View File

@@ -0,0 +1,88 @@
import { defineProperty } from 'cosmokit'
import { Context } from './context'
import { createCallable, joinPrototype, symbols, Tracker } from './utils'
/**
* Base class for services that expose a named API on `ctx`.
*
* Subclasses call `super(ctx, name)` from their constructor. The service is
* registered immediately and is automatically removed with the owning fiber.
*/
export abstract class Service<out T = never> {
static readonly init: unique symbol = symbols.init
static readonly check: unique symbol = symbols.check
static readonly config: unique symbol = symbols.config
static readonly invoke: unique symbol = symbols.invoke
static readonly extend: unique symbol = symbols.extend
static readonly tracker: unique symbol = symbols.tracker
static readonly resolveConfig: unique symbol = symbols.resolveConfig
declare [symbols.config]: T
public name!: string
/** Register this instance as `name` in the current context. */
constructor(protected ctx: Context, name: string) {
name ??= this.constructor['provide'] as string
let self = this
const tracker: Tracker = {
associate: name,
property: 'ctx',
}
if (self[symbols.invoke]) {
self = createCallable(name, joinPrototype(Object.getPrototypeOf(this), Function.prototype), tracker)
}
self.ctx = ctx
self.name = name
defineProperty(self, symbols.tracker, tracker)
self.ctx.reflect.provide(name, self, this[symbols.check])
return self
}
protected [symbols.filter](ctx: Context) {
return ctx[symbols.isolate][this.name] === this.ctx[symbols.isolate][this.name]
}
protected [symbols.extend](props?: any) {
let self: any
if (this[Service.invoke]) {
self = createCallable(this.name, this, this[symbols.tracker])
} else {
self = Object.create(this)
}
return Object.assign(self, props)
}
/** Merge intercept config from ancestors with optional base and head values. */
[symbols.resolveConfig](base?: T, head?: T): T {
let intercept = this.ctx[Context.intercept]
const configs: any[] = []
while (this.name in intercept) {
if (Object.hasOwn(intercept, this.name)) {
configs.unshift(intercept[this.name])
}
intercept = Object.getPrototypeOf(intercept)
}
if (base) configs.unshift(base)
if (head) configs.push(head)
if (this['Config']?.merge) {
return this['Config'].merge(...configs)
} else {
return Object.assign({}, ...configs)
}
}
static [Symbol.hasInstance](instance: any) {
if (!instance) return false
let constructor = instance.constructor
while (constructor) {
// constructor may be a proxy
constructor = constructor.prototype?.constructor
if (constructor === this) return true
constructor &&= Object.getPrototypeOf(constructor)
}
return false
}
}

287
vendor/cordis/src/utils.ts vendored Normal file
View File

@@ -0,0 +1,287 @@
import { defineProperty } from 'cosmokit'
import type { Context, Service } from '.'
/** Ordered collection of disposable values with O(1) deletion by value. */
export class DisposableList<T extends WeakKey> {
private sn = 0
private map = new Map<number, T>()
private weak = new WeakMap<T, number>()
get length() {
return this.map.size
}
push(value: T) {
const sn = ++this.sn
this.map.set(sn, value)
this.weak.set(value, sn)
return () => this.map.delete(sn)
}
delete(value: T) {
const sn = this.weak.get(value)
if (!sn) return false
return this.map.delete(sn)
}
clear() {
const values = [...this.map.values()]
this.map.clear()
return values.reverse()
}
[Symbol.iterator]() {
return this.map.values()
}
[Symbol.for('nodejs.util.inspect.custom')]() {
return [...this]
}
}
/** Metadata used by traceable proxies to rebind `ctx` and associated services. */
export interface Tracker {
associate?: string
property?: string
noShadow?: boolean
}
/** Shared symbols used to avoid public property-name collisions. */
export const symbols = {
// internal symbols
shadow: Symbol.for('cordis.shadow'),
receiver: Symbol.for('cordis.receiver'),
original: Symbol.for('cordis.original'),
metadata: Symbol.for('cordis.metadata'),
initHooks: Symbol.for('cordis.initHooks'),
checkProto: Symbol.for('cordis.checkProto'),
// context symbols
effect: Symbol.for('cordis.effect') as typeof Context.effect,
filter: Symbol.for('cordis.filter') as typeof Context.filter,
isolate: Symbol.for('cordis.isolate') as typeof Context.isolate,
intercept: Symbol.for('cordis.intercept') as typeof Context.intercept,
// service symbols
init: Symbol.for('cordis.init') as typeof Service.init,
check: Symbol.for('cordis.check') as typeof Service.check,
config: Symbol.for('cordis.config') as typeof Service.config,
invoke: Symbol.for('cordis.invoke') as typeof Service.invoke,
extend: Symbol.for('cordis.extend') as typeof Service.extend,
tracker: Symbol.for('cordis.tracker') as typeof Service.tracker,
resolveConfig: Symbol.for('cordis.resolveConfig') as typeof Service.resolveConfig,
}
const GeneratorFunction = function* () {}.constructor
const AsyncGeneratorFunction = async function* () {}.constructor
/** Return true when a plugin callback should be constructed with `new`. */
export function isConstructor(func: any): func is new (...args: any) => any {
// async function or arrow function
if (!func.prototype) return false
// generator function or malformed definition
// we cannot use below check because `mock.fn()` is proxied
// if (func.prototype.constructor !== func) return false
if (func instanceof GeneratorFunction) return false
// polyfilled AsyncGeneratorFunction === Function
if (AsyncGeneratorFunction !== Function && func instanceof AsyncGeneratorFunction) return false
return true
}
/** Merge two prototype chains while preserving descriptors from `proto1`. */
export function joinPrototype(proto1: {}, proto2: {}) {
if (proto1 === Object.prototype) return proto2
const result = Object.create(joinPrototype(Object.getPrototypeOf(proto1), proto2))
for (const key of Reflect.ownKeys(proto1)) {
Object.defineProperty(result, key, Object.getOwnPropertyDescriptor(proto1, key)!)
}
return result
}
/** Return true for non-null objects and functions. */
export function isObject(value: any): value is {} {
return value && (typeof value === 'object' || typeof value === 'function')
}
/** Find a property descriptor by walking an object's prototype chain. */
export function getPropertyDescriptor(target: any, prop: string | symbol) {
let proto = target
while (proto) {
const desc = Reflect.getOwnPropertyDescriptor(proto, prop)
if (desc) return desc
proto = Object.getPrototypeOf(proto)
}
}
/** Wrap services/functions so method calls see the caller's active context. */
export function getTraceable<T>(ctx: Context, value: T): T {
if (!isObject(value)) return value
if (Object.hasOwn(value, symbols.shadow)) {
return Object.getPrototypeOf(value)
}
const tracker = value[symbols.tracker]
if (!tracker) return value
return createTraceable(ctx, value, tracker)
}
/** Return a proxy that overlays readonly or writable properties onto a target. */
export function withProps(target: any, props?: {}) {
if (!props) return target
return new Proxy(target, {
get: (target, prop, receiver) => {
if (prop in props && prop !== 'constructor') return Reflect.get(props, prop, receiver)
return Reflect.get(target, prop, receiver)
},
set: (target, prop, value, receiver) => {
if (prop in props && prop !== 'constructor') return Reflect.set(props, prop, value, receiver)
return Reflect.set(target, prop, value, receiver)
},
})
}
function withProp(target: any, prop: string | symbol, value: any) {
return withProps(target, Object.defineProperty(Object.create(null), prop, {
value,
writable: false,
}))
}
function createShadow(ctx: Context, target: any, property: string | undefined, receiver: any) {
if (!property) return receiver
const origin = Reflect.getOwnPropertyDescriptor(target, property)?.value
if (!origin) return receiver
return withProp(receiver, property, ctx.extend({ [symbols.shadow]: origin }))
}
function createShadowMethod(ctx: Context, value: any, outer: any, shadow: {}) {
return new Proxy(value, {
apply: (target, thisArg, args) => {
if (thisArg === outer) thisArg = shadow
return getTraceable(ctx, Reflect.apply(target, thisArg, args))
},
})
}
function createTraceable(ctx: Context, value: any, tracker: Tracker) {
// noShadow services are identity-aware (e.g. logger uses the origin fiber to
// derive its name): keep the shadow ctx so they can read [symbols.shadow]
// and resolve the origin. Non-noShadow services strip — their side effects
// bind to caller, not origin.
if (ctx[symbols.shadow] && !tracker.noShadow) {
ctx = Object.getPrototypeOf(ctx)
}
const proxy = new Proxy(value, {
get: (target, prop, receiver) => {
if (prop === symbols.original) return target
if (prop === tracker.property) return ctx
if (typeof prop === 'symbol') {
return Reflect.get(target, prop, receiver)
}
if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) {
return Reflect.get(ctx, `${tracker.associate}.${prop}`, withProp(ctx, symbols.receiver, receiver))
}
let shadow: any, innerValue: any
const desc = getPropertyDescriptor(target, prop)
if (desc && 'value' in desc) {
innerValue = desc.value
} else {
shadow = createShadow(ctx, target, tracker.property, receiver)
innerValue = Reflect.get(target, prop, shadow)
}
const innerTracker = innerValue?.[symbols.tracker]
if (innerTracker) {
return createTraceable(ctx, innerValue, innerTracker)
} else if (!tracker.noShadow && typeof innerValue === 'function') {
shadow ??= createShadow(ctx, target, tracker.property, receiver)
return createShadowMethod(ctx, innerValue, receiver, shadow)
} else {
return innerValue
}
},
set: (target, prop, value, receiver) => {
if (prop === symbols.original) return false
if (prop === tracker.property) return false
if (typeof prop === 'symbol') {
return Reflect.set(target, prop, value, receiver)
}
if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) {
return Reflect.set(ctx, `${tracker.associate}.${prop}`, value, withProp(ctx, symbols.receiver, receiver))
}
const shadow = createShadow(ctx, target, tracker.property, receiver)
return Reflect.set(target, prop, value, shadow)
},
apply: (target, thisArg, args) => {
return applyTraceable(proxy, target, thisArg, args)
},
})
return proxy
}
function applyTraceable(proxy: any, value: any, thisArg: any, args: any[]) {
if (!value[symbols.invoke]) return Reflect.apply(value, thisArg, args)
return value[symbols.invoke].apply(proxy, args)
}
/** Create a callable service object that dispatches through `symbols.invoke`. */
export function createCallable(name: string, proto: {}, tracker: Tracker) {
const self = function (...args: any[]) {
const proxy = createTraceable(self['ctx'], self, tracker)
return applyTraceable(proxy, self, this, args)
}
defineProperty(self, 'name', name)
return Object.setPrototypeOf(self, proto)
}
interface StackInfo {
offset: number
error: Error
}
function handleError(info: StackInfo, reason: any, getOuterStack: () => string[]): never {
const innerLines = info.error.stack!.split('\n')
// malformed error
if (typeof reason?.stack !== 'string') {
const outerError = new Error(reason)
const lines = outerError.stack!.split('\n')
lines.splice(1, Infinity, ...getOuterStack())
outerError.stack = lines.join('\n')
throw outerError
}
// long stack trace
const lines: string[] = reason.stack.split('\n')
let index = lines.indexOf(innerLines[2])
if (index === -1) throw reason
index -= info.offset
while (index > 0) {
if (!lines[index - 1].endsWith(' (<anonymous>)')) break
index -= 1
}
lines.splice(index, Infinity, ...getOuterStack())
reason.stack = lines.join('\n')
throw reason
}
/** Run a callback and splice outer call-site frames into thrown async errors. */
export function composeError<T>(callback: (info: StackInfo) => T, getOuterStack = buildOuterStack()): T {
const info: StackInfo = { offset: 1, error: new Error() }
try {
const result: any = callback(info)
if (isObject(result) && 'then' in result) {
return (result as any).then(undefined, (reason) => handleError(info, reason, getOuterStack)) as T
} else {
return result
}
} catch (reason: any) {
handleError(info, reason, getOuterStack)
}
}
/** Capture a lazy stack-frame supplier for later error composition. */
export function buildOuterStack(offset = 0) {
const outerError = new Error()
return () => outerError.stack!.split('\n').slice(3 + offset)
}