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