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:
Vendored
+249
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user