Files
deepseek-harness/vendor/cordis/src/events.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

206 lines
8.3 KiB
TypeScript

import { defineProperty, Promisify } from 'cosmokit'
import { Context } from './context.ts'
import { Fiber, FiberState } from './fiber.ts'
import { DisposableList, symbols } from './utils.ts'
/** 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.ts' {
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
}