Files
deepseek-harness/vendor/cordis/src/registry.ts
Tianyi Cui 07f4047ff0 Use explicit ts specifiers for declarations
Restore explicit .ts relative specifiers in source and enable rewriteRelativeImportExtensions so emitted JS uses .js while declarations keep explicit .ts specifiers. Add a NodeNext declaration-consumer gate to prevent extensionless declaration regressions.
2026-06-22 06:11:00 +08:00

250 lines
7.8 KiB
TypeScript

import { defineProperty, Dict } from 'cosmokit'
import { StandardSchemaV1 } from '@standard-schema/spec'
import { Context } from './context.ts'
import { Fiber } from './fiber.ts'
import { buildOuterStack, DisposableList, symbols, withProps } from './utils.ts'
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.ts' {
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
}
}