/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ export type Awaitable = T | PromiseLike; export type Disposable> = () => T; export type Inject = readonly string[] | Readonly>; export const enum FiberState { PENDING, LOADING, ACTIVE, FAILED, DISPOSED, UNLOADING, } export interface EffectMeta { readonly label: string; readonly children: readonly EffectMeta[]; } export interface StandardSchema { readonly '~standard': { validate( value: unknown, ): | { readonly value: unknown; readonly issues?: undefined } | { readonly issues: readonly { readonly message: string }[] } | Promise< | { readonly value: unknown; readonly issues?: undefined } | { readonly issues: readonly { readonly message: string }[] } >; }; } export type Plugin = Plugin.Function | Plugin.Constructor | Plugin.Object; export namespace Plugin { export interface Base { readonly name?: string; readonly inject?: Inject; readonly Config?: StandardSchema; } export type Function = Base & ((ctx: Context, config: T) => unknown); export type Constructor = Base & (new (ctx: Context, config: T) => unknown); export interface Object extends Base { apply(ctx: Context, config: T): unknown; } } export interface EventOptions { readonly prepend?: boolean; readonly global?: boolean; } interface ServiceImplementation { readonly name: string; readonly label: symbol; readonly fiber: Fiber; value: unknown; readonly check?: () => boolean; } interface Hook { readonly context: Context; readonly listener: (...args: unknown[]) => unknown; readonly global: boolean; } interface Accessor { readonly owner: Fiber; readonly get: (this: Context, receiver: unknown) => unknown; readonly set?: (this: Context, value: unknown, receiver: unknown) => boolean; } interface PluginRuntime { readonly callback: Function; readonly fibers: Set; readonly name?: string; readonly Config?: StandardSchema; } interface KernelState { readonly root: Context; readonly services: Map; readonly serviceLabels: Map; readonly runtimes: WeakMap; readonly listeners: Map; readonly accessors: Map; readonly fibers: Set; nextFiberId: number; closed: boolean; } const contextBrand = Symbol.for('maka.plugin-kernel.context'); const effectMeta = Symbol('maka.plugin-kernel.effect-meta'); const disposedFibers = new WeakSet(); export interface Logger { readonly name: string; error(value: unknown, ...values: unknown[]): void; warn(value: unknown, ...values: unknown[]): void; info(value: unknown, ...values: unknown[]): void; debug(value: unknown, ...values: unknown[]): void; } export interface LoggerService extends Logger { (name?: string): Logger; } export interface Context { readonly root: Context; readonly parent?: Context; readonly fiber: Fiber; readonly logger: LoggerService; [Context.filter]?: (listenerContext: Context) => boolean; } /** A non-owning capability view whose lifecycle and resources belong to exactly one Fiber. */ export class Context { static readonly effect = effectMeta; static readonly filter = Symbol('maka.plugin-kernel.filter'); readonly [contextBrand] = true; readonly #kernel: KernelState; readonly #parent?: Context; #fiber!: Fiber; readonly #isolation: Readonly>; readonly #intercepts: Readonly>; readonly #proxy: Context; static is(value: unknown): value is Context { return Boolean((value as { readonly [contextBrand]?: boolean } | undefined)?.[contextBrand]); } constructor(); constructor( kernel?: KernelState, parent?: Context, fiber?: Fiber, isolation?: Readonly>, intercepts?: Readonly>, meta?: object, ); constructor( kernel?: KernelState, parent?: Context, fiber?: Fiber, isolation?: Readonly>, intercepts?: Readonly>, meta: object = {}, ) { this.#parent = parent; this.#isolation = isolation ?? parent?._isolation() ?? freezeRecord(); this.#intercepts = intercepts ?? parent?._intercepts() ?? freezeRecord(); this.#kernel = kernel ?? ({} as KernelState); this.#proxy = new Proxy(this, contextProxy); if (kernel) { this.#fiber = fiber ?? parent?.fiber ?? kernel.root.fiber; } else { const rootFiber = Fiber.root(this.#proxy); this.#fiber = rootFiber; Object.assign(this.#kernel, { root: this.#proxy, services: new Map(), serviceLabels: new Map(), runtimes: new WeakMap(), listeners: new Map(), accessors: new Map(), fibers: new Set([rootFiber]), nextFiberId: 0, closed: false, } satisfies KernelState); } assignContextMetadata(this, meta); Object.defineProperty(this, 'logger', { enumerable: true, configurable: false, value: createLoggerService(() => this.fiber.name), }); return this.#proxy; } get root(): Context { return this.#kernel.root; } get parent(): Context | undefined { return this.#parent; } get fiber(): Fiber { return this.#fiber; } extend(meta: object = {}): this { this.#assertOpen(); return this.fiber.deriveContext(this.#proxy, this.#isolation, this.#intercepts, meta) as this; } isolate(name: string, label = Symbol(name)): this { validateServiceName(name); return this.fiber.deriveContext( this.#proxy, freezeRecord({ ...this.#isolation, [name]: label }), this.#intercepts, ) as this; } intercept(name: string, config: unknown): this { validateServiceName(name); const existing = this.#intercepts[name] ?? []; return this.fiber.deriveContext( this.#proxy, this.#isolation, freezeRecord({ ...this.#intercepts, [name]: Object.freeze([...existing, config]) }), ) as this; } plugin

(plugin: P, config?: unknown): Fiber { this.#assertOpen(); return this.fiber.mount(this.#proxy, plugin, config); } inject(inject: Inject, callback: Plugin.Function): Fiber { return this.plugin(Object.assign(callback, { inject })); } effect(execute: () => unknown, label = 'anonymous'): Disposable> { return this.fiber.own(execute, label); } provide(name: string, value?: unknown, check?: () => boolean): Disposable> { this.#assertOpen(); validateServiceName(name); const label = this.#label(name); if (this.#kernel.services.has(label)) { throw new Error(`Service is already provided in this scope: ${name}`); } return this.effect( () => { const implementation: ServiceImplementation = { name, label, value, fiber: this.fiber, check, }; this.#kernel.services.set(label, implementation); this.#notifyService(name, label); return async () => { if (this.#kernel.services.get(label) !== implementation) return; this.#kernel.services.delete(label); await Promise.allSettled(this.#notifyService(name, label).map((fiber) => fiber.await())); }; }, `ctx.provide(${JSON.stringify(name)})`, ); } get(name: string, strict = true): T | undefined { const implementation = this.#implementation(name); if (!implementation) return undefined; if (strict && implementation.fiber.state !== FiberState.ACTIVE) return undefined; if (implementation.check && !implementation.check.call(implementation.value)) return undefined; return ( implementation.value instanceof Service ? implementation.value._bind(this.#proxy) : implementation.value ) as T; } set(name: string, value: unknown): boolean { const implementation = this.#implementation(name); if (!implementation) throw new Error(`Cannot set missing Service: ${name}`); if (implementation.fiber !== this.fiber) { throw new Error(`Cannot mutate Service owned by another Fiber: ${name}`); } implementation.value = value; this.#notifyService(name, implementation.label); return true; } accessor( name: string, options: { readonly get: (this: Context, receiver: unknown) => unknown; readonly set?: (this: Context, value: unknown, receiver: unknown) => boolean; }, ): Disposable> { this.#assertAccessorAvailable(name); return this.effect( () => { const accessor = { owner: this.fiber, ...options }; this.#kernel.accessors.set(name, accessor); return () => { if (this.#kernel.accessors.get(name) === accessor) this.#kernel.accessors.delete(name); }; }, `ctx.accessor(${JSON.stringify(name)})`, ); } mixin( source: string | object, names: readonly string[] | Readonly>, ): void { const entries = Array.isArray(names) ? names.map((name) => [name, name] as const) : Object.entries(names); const targets = new Set(); for (const [, targetName] of entries) { if (targets.has(targetName)) throw new Error(`Context property already exists: ${targetName}`); targets.add(targetName); this.#assertAccessorAvailable(targetName); } for (const [sourceName, targetName] of entries) { this.accessor(targetName, { get(receiver) { const target = typeof source === 'string' ? this.get(source) : source; const value = Reflect.get(target as object, sourceName, receiver ?? target); return typeof value === 'function' ? value.bind(target) : value; }, set(value, receiver) { const target = typeof source === 'string' ? this.get(source) : source; return Reflect.set(target as object, sourceName, value, receiver ?? target); }, }); } } on( name: PropertyKey, listener: (...args: unknown[]) => unknown, options: boolean | EventOptions = {}, ): Disposable { this.#assertOpen(); const normalized = typeof options === 'boolean' ? { prepend: options } : options; const hook: Hook = { context: this.#proxy, listener, global: normalized.global === true }; const hooks = this.#kernel.listeners.get(name) ?? []; let active = true; const unregister = () => { if (!active) return false; active = false; const index = hooks.indexOf(hook); if (index >= 0) hooks.splice(index, 1); if (!hooks.length) this.#kernel.listeners.delete(name); return index >= 0; }; this.effect( () => { if (normalized.prepend) hooks.unshift(hook); else hooks.push(hook); this.#kernel.listeners.set(name, hooks); return unregister; }, `ctx.on(${String(name)})`, ); return unregister; } once( name: PropertyKey, listener: (...args: unknown[]) => unknown, options: boolean | EventOptions = {}, ): Disposable { let unregister: Disposable; unregister = this.on( name, (...args) => { unregister(); return listener(...args); }, options, ); return unregister; } emit(...input: unknown[]): void { const { hooks, args } = this.#dispatch(input); for (const hook of hooks) hook.listener(...args); } async parallel(...input: unknown[]): Promise { const { hooks, args } = this.#dispatch(input); const settled = await Promise.allSettled( hooks.map((hook) => Promise.resolve().then(() => hook.listener(...args))), ); const errors = settled .filter((result): result is PromiseRejectedResult => result.status === 'rejected') .map(({ reason }) => reason); if (errors.length) throw new AggregateError(errors); } async serial(...input: unknown[]): Promise { const { hooks, args } = this.#dispatch(input); for (const hook of hooks) { const result = await hook.listener(...args); if (result !== undefined && result !== null && result !== false) return result; } } bail(...input: unknown[]): unknown { const { hooks, args } = this.#dispatch(input); for (const hook of hooks) { const result = hook.listener(...args); if (result !== undefined && result !== null && result !== false) return result; } } waterfall(...input: unknown[]): unknown { const { hooks, args } = this.#dispatch(input); const terminal = args.pop(); if (typeof terminal !== 'function') throw new TypeError('Waterfall requires a terminal callback'); const callbacks = hooks.map(({ listener }) => listener); const next = (): unknown => { const callback = callbacks.shift() ?? terminal; return callback(...args, next); }; return next(); } interceptConfig(name: string): readonly unknown[] { return this.#intercepts[name] ?? []; } kernelFibers(): readonly Fiber[] { return Object.freeze([...this.#kernel.fibers]); } #dispatch(input: readonly unknown[]): { readonly hooks: readonly Hook[]; readonly args: unknown[]; } { const args = [...input]; const thisArg = Context.is(args[0]) ? (args.shift() as Context) : undefined; const name = args.shift(); if (typeof name !== 'string' && typeof name !== 'symbol') { throw new TypeError('Event name must be a string or symbol'); } const filter = thisArg?.[Context.filter]; const hooks = (this.#kernel.listeners.get(name) ?? []).filter( (hook) => hook.global || !filter || filter(hook.context), ); return { hooks, args }; } #implementation(name: string): ServiceImplementation | undefined { const label = this.#lookupLabel(name); return label ? this.#kernel.services.get(label) : undefined; } #lookupLabel(name: string): symbol | undefined { return this.#isolation[name] ?? this.#kernel.serviceLabels.get(name); } #label(name: string): symbol { const isolated = this.#isolation[name]; if (isolated) return isolated; let label = this.#kernel.serviceLabels.get(name); if (!label) { label = Symbol(name); this.#kernel.serviceLabels.set(name, label); } return label; } #notifyService(name: string, label: symbol): Fiber[] { return notifyService(this.#kernel, name, label); } #assertOpen(): void { if ( this.#kernel.closed || disposedFibers.has(this.fiber) || this.fiber.state === FiberState.DISPOSED || this.fiber.state === FiberState.UNLOADING ) { throw new Error('Plugin Context is disposed'); } } #assertAccessorAvailable(name: string): void { this.#assertOpen(); if ( Reflect.has(this, name) || hasAncestorProperty(this.parent, name) || this.#kernel.accessors.has(name) ) { throw new Error(`Context property already exists: ${name}`); } } _kernel(): KernelState { return this.#kernel; } _label(name: string): symbol { return this.#label(name); } _isolation(): Readonly> { return this.#isolation; } _intercepts(): Readonly> { return this.#intercepts; } } function assignContextMetadata(context: Context, meta: object): void { for (const key of Reflect.ownKeys(meta)) { const descriptor = Object.getOwnPropertyDescriptor(meta, key); if (!descriptor?.enumerable) continue; if (key === 'logger' || Reflect.has(context, key)) { throw new Error(`Context metadata cannot overwrite owned field: ${String(key)}`); } } Object.assign(context, meta); } const contextProxy: ProxyHandler = { get(target, property, receiver) { if (Reflect.has(target, property)) { const value = Reflect.get(target, property, target); return typeof value === 'function' && Object.hasOwn(Context.prototype, property) ? value.bind(target) : value; } for (let ancestor = target.parent; ancestor; ancestor = ancestor.parent) { if (Object.hasOwn(ancestor, property)) return Reflect.get(ancestor, property); } const accessor = target._kernel().accessors.get(property); if (accessor) return accessor.get.call(receiver as Context, receiver); if (typeof property === 'string') { const service = target.get(property); if (service !== undefined) return service; } }, set(target, property, value, receiver) { if (Reflect.has(target, property)) return Reflect.set(target, property, value, target); const accessor = target._kernel().accessors.get(property); if (accessor?.set) return accessor.set.call(receiver as Context, value, receiver); if (typeof property === 'string' && target.get(property, false) !== undefined) { return target.set(property, value); } return Reflect.set(target, property, value, receiver); }, has(target, property) { return ( Reflect.has(target, property) || target._kernel().accessors.has(property) || (typeof property === 'string' && target.get(property, false) !== undefined) || hasAncestorProperty(target.parent, property) ); }, }; function hasAncestorProperty(context: Context | undefined, property: PropertyKey): boolean { for (let ancestor = context; ancestor; ancestor = ancestor.parent) { if (Object.hasOwn(ancestor, property)) return true; } return false; } /** The sole lifecycle, child-instance, dependency, error, and resource owner at runtime. */ export class Fiber { readonly id: number; readonly context: Context; readonly parent?: Fiber; readonly plugin?: Plugin; readonly inject: Readonly>; state: FiberState; config: unknown; inertia?: Promise; error?: unknown; readonly #runtime?: PluginRuntime; readonly #children = new Set(); readonly #effects: Array> & { [effectMeta]?: EffectMeta }> = []; readonly #services = new Map(); #disposed = false; #cleanupFailed = false; #dependencyRefreshQueued = false; #disposeTask?: Promise; #transition: Promise = Promise.resolve(); static root(context: Context): Fiber { return new Fiber(context, undefined, undefined, {}, undefined, true); } constructor( parentContext: Context, plugin: Plugin | undefined, config: unknown, inject: Readonly>, runtime: PluginRuntime | undefined, root = false, ) { this.parent = root ? undefined : parentContext.fiber; this.plugin = plugin; this.config = config; this.inject = inject; this.#runtime = runtime; const kernel = parentContext._kernel(); this.id = root ? 0 : ++kernel.nextFiberId; this.state = root ? FiberState.ACTIVE : FiberState.PENDING; this.context = root ? parentContext : new Context(kernel, parentContext, this, undefined, undefined); if (!root) { kernel.fibers.add(this); runtime?.fibers.add(this); if (this.parent) this.parent.#children.add(this); this.refreshDependencies(); } } deriveContext( parent: Context = this.context, isolation?: Readonly>, intercepts?: Readonly>, meta: object = {}, ): Context { if (parent.fiber !== this) { throw new Error('Cannot derive a Context view from another Fiber'); } if ( this.#disposed || this.state === FiberState.DISPOSED || this.state === FiberState.UNLOADING ) { throw new Error('Cannot derive a Context view from an inactive Fiber'); } return new Context(parent._kernel(), parent, this, isolation, intercepts, meta); } mount

(context: Context, plugin: P, config?: unknown): Fiber { if (context.fiber !== this) { throw new Error('Cannot mount a child through a Context owned by another Fiber'); } if ( this.#disposed || this.state === FiberState.DISPOSED || this.state === FiberState.UNLOADING ) { throw new Error('Cannot mount a child on an inactive Fiber'); } const kernel = context._kernel(); const callback = resolvePlugin(plugin); let runtime = kernel.runtimes.get(plugin); if (!runtime) { runtime = { callback, fibers: new Set(), name: plugin.name, Config: plugin.Config, }; kernel.runtimes.set(plugin, runtime); } const fiber = new Fiber(context, plugin, config, normalizeInject(plugin.inject), runtime); return fiber; } get name(): string { return ( this.#runtime?.name || this.plugin?.name || (this.id === 0 ? 'root' : `plugin-${this.id}`) ); } requires(name: string): boolean { return Object.hasOwn(this.inject, name); } serviceLabel(name: string): symbol { return this.context._label(name); } refreshDependencies(): void { if (this.#disposed || !this.plugin) return; if (this.#dependencyRefreshQueued) return; this.#dependencyRefreshQueued = true; this.#enqueue(async () => { this.#dependencyRefreshQueued = false; await this.#refreshDependencies(); }); } async #refreshDependencies(): Promise { if (this.#disposed || !this.plugin) return; const next = new Map(); for (const name of Object.keys(this.inject)) { let implementation: unknown; try { implementation = this.context.get(name); } catch (error) { const errors = [error]; this.#services.clear(); if (this.state === FiberState.ACTIVE || this.state === FiberState.FAILED) { try { await this.#unload(FiberState.PENDING); } catch (cleanupError) { errors.push(cleanupError); } } this.error = errors.length === 1 ? error : new AggregateError(errors, `Fiber ${this.name} dependency check and cleanup failed`); this.#setState(FiberState.FAILED); return; } if (implementation === undefined) { this.#services.clear(); if (this.state === FiberState.ACTIVE || this.state === FiberState.FAILED) { await this.#unload(FiberState.PENDING); } else { this.#setState(FiberState.PENDING); } return; } next.set(name, implementation); } const changed = next.size !== this.#services.size || [...next].some(([name, value]) => this.#services.get(name) !== value); this.#services.clear(); for (const [name, value] of next) this.#services.set(name, value); if (this.state === FiberState.PENDING || this.state === FiberState.FAILED) { await this.#load(); } else if (this.state === FiberState.ACTIVE && changed) { await this.#unload(FiberState.PENDING); await this.#load(); } } own(execute: () => unknown, label = 'anonymous'): Disposable> { if (this.#disposed || this.state === FiberState.UNLOADING) { throw new Error('Cannot create an Effect on an inactive Fiber'); } const disposers: Disposable>[] = []; let disposeTask: Promise | undefined; const collect = (value: unknown): void => { if (typeof value === 'function') disposers.push(value as Disposable>); else if (value !== undefined && value !== null) { throw new TypeError('Plugin Effect must return a disposer'); } }; const run = async (): Promise => { const result = execute(); if (isAsyncIterable(result)) { for await (const value of result) collect(value); } else if (isIterable(result)) { for (const value of result) collect(value); } else { collect(await result); } }; const setupTask = run(); const dispose = Object.assign( () => { disposeTask ??= (async () => { await setupTask.catch(() => undefined); const errors: unknown[] = []; try { for (const cleanup of disposers.reverse()) { try { await cleanup(); } catch (error) { errors.push(error); } } } finally { const index = this.#effects.indexOf(dispose); if (index >= 0) this.#effects.splice(index, 1); } if (errors.length) throw new AggregateError(errors, `Effect ${label} cleanup failed`); })(); return disposeTask; }, { [effectMeta]: Object.freeze({ label, children: Object.freeze([]) }) }, ); this.#effects.push(dispose); void setupTask.catch(async (error) => { const errors = [error]; try { await dispose(); } catch (cleanupError) { errors.push(cleanupError); } this.error = errors.length === 1 ? error : new AggregateError(errors, `Effect ${label} setup and cleanup failed`); }); return dispose; } getEffects(): readonly EffectMeta[] { return Object.freeze( this.#effects.flatMap((dispose) => (dispose[effectMeta] ? [dispose[effectMeta]] : [])), ); } async await(): Promise { while (this.inertia) await this.inertia.catch(() => undefined); if (this.state === FiberState.FAILED) throw this.error; } async restart(): Promise { if (this.#disposed) throw new Error('Cannot restart a disposed Fiber'); await this.#enqueue(() => this.#restart()); } async update(config: unknown): Promise { if (this.#disposed) throw new Error('Cannot update a disposed Fiber'); await this.#enqueue(async () => { const previous = this.config; this.config = config; try { await this.#restart(); } catch (error) { this.config = previous; try { await this.#restart(); } catch (rollbackError) { throw new AggregateError( [error, rollbackError], `Fiber ${this.name} update and rollback failed`, ); } throw error; } }); } dispose(): Promise { if (this.#disposeTask) return this.#disposeTask; this.#disposed = true; disposedFibers.add(this); this.#disposeTask = this.#enqueue(async () => { try { await this.#unload(FiberState.DISPOSED); } finally { const kernel = this.context._kernel(); kernel.fibers.delete(this); this.#runtime?.fibers.delete(this); if (this.parent) this.parent.#children.delete(this); if (this.id === 0) kernel.closed = true; } }); return this.#disposeTask; } #enqueue(operation: () => Promise): Promise { const task = this.#transition.then(operation, operation); this.#transition = task.catch(() => undefined); const settled = task.finally(() => { if (this.inertia === settled) this.inertia = undefined; }); void settled.catch(() => undefined); this.inertia = settled; return settled; } async #restart(): Promise { if (this.#cleanupFailed) throw this.error; await this.#unload(FiberState.PENDING); if (this.#dependenciesAvailable()) await this.#load(); } async #load(): Promise { if (this.#disposed || !this.plugin || !this.#dependenciesAvailable()) return; if (this.#cleanupFailed) throw this.error; this.error = undefined; this.#setState(FiberState.LOADING); try { const config = await validateConfig(this.#runtime?.Config, this.config); const output = await invokePlugin(this.plugin, this.context, config); if (typeof output === 'function') this.own(() => output, `plugin:${this.name}`); else if ( output !== undefined && output !== null && !(typeof this.plugin === 'function' && isConstructor(this.plugin)) ) { throw new TypeError('Plugin must return a disposer or nothing'); } this.#setState(FiberState.ACTIVE); } catch (error) { const errors = [error]; try { await this.#disposeEffects(); } catch (cleanupError) { this.#cleanupFailed = true; errors.push(cleanupError); } this.error = errors.length === 1 ? error : new AggregateError(errors, `Fiber ${this.name} activation and cleanup failed`); this.#setState(FiberState.FAILED); throw this.error; } } async #unload(nextState: FiberState): Promise { if (this.state === FiberState.DISPOSED) return; this.#setState(FiberState.UNLOADING); const childResults = await Promise.allSettled( [...this.#children].reverse().map((child) => child.dispose()), ); const errors = childResults.flatMap((result) => result.status === 'rejected' ? [result.reason] : [], ); try { await this.#disposeEffects(); } catch (error) { errors.push(error); } if (errors.length) { this.#cleanupFailed = true; this.error = new AggregateError(errors, `Fiber ${this.name} cleanup failed`); this.#setState(nextState === FiberState.DISPOSED ? FiberState.DISPOSED : FiberState.FAILED); throw this.error; } this.#setState(nextState); } async #disposeEffects(): Promise { const errors: unknown[] = []; for (const dispose of [...this.#effects].reverse()) { try { await dispose(); } catch (error) { errors.push(error); } } if (errors.length) throw new AggregateError(errors, `Fiber ${this.name} Effect cleanup failed`); } #dependenciesAvailable(): boolean { return Object.keys(this.inject).every((name) => this.context.get(name) !== undefined); } #setState(state: FiberState): void { const previous = this.state; this.state = state; if (state === FiberState.ACTIVE) { notifyProvidedServices(this.context._kernel(), this); } if (previous !== state) this.context.emit('internal/status', this, previous); } } function notifyService(kernel: KernelState, name: string, label: symbol): Fiber[] { const fibers = serviceConsumers(kernel, name, label); for (const fiber of fibers) fiber.refreshDependencies(); return fibers; } function notifyProvidedServices(kernel: KernelState, provider: Fiber): void { const fibers = new Set(); for (const implementation of kernel.services.values()) { if (implementation.fiber !== provider) continue; for (const fiber of serviceConsumers(kernel, implementation.name, implementation.label)) { fibers.add(fiber); } } for (const fiber of fibers) fiber.refreshDependencies(); } function serviceConsumers(kernel: KernelState, name: string, label: symbol): Fiber[] { const fibers: Fiber[] = []; for (const fiber of kernel.fibers) { if (!fiber.requires(name) || fiber.serviceLabel(name) !== label) continue; fibers.push(fiber); } return fibers; } export abstract class Service { readonly name: string; readonly #contexts = new WeakMap(); constructor( protected readonly ctx: Context, name: string, ) { this.name = name; ctx.provide(name, this); } _bind(context: Context): this { const cached = this.#contexts.get(context); if (cached) return cached; const bound = new Proxy(this, { get: (target, property, receiver) => { if (property === 'ctx') return context; const value = Reflect.get(target, property, receiver); return typeof value === 'function' ? value.bind(receiver) : value; }, }); this.#contexts.set(context, bound); return bound; } protected resolveConfig(base?: T, head?: T): T { const values = [base, ...this.ctx.interceptConfig(this.name), head].filter( (value): value is T => value !== undefined, ); return Object.assign({}, ...values); } } function normalizeInject(inject: Inject | undefined): Readonly> { if (!inject) return freezeRecord(); if (Array.isArray(inject)) { return freezeRecord(Object.fromEntries(inject.map((name) => [name, null]))); } return freezeRecord(inject as Readonly>); } function freezeRecord(source?: Readonly>): Readonly> { return Object.freeze(Object.assign(Object.create(null) as Record, source)); } function resolvePlugin(plugin: Plugin): Function { if (typeof plugin === 'function') return plugin; if (plugin && typeof plugin.apply === 'function') return plugin.apply; throw new TypeError('Plugin must be a function, class, or object with apply()'); } async function invokePlugin(plugin: Plugin, context: Context, config: unknown): Promise { if (typeof plugin === 'function') { if (isConstructor(plugin)) return Reflect.construct(plugin, [context, config]); return (plugin as Plugin.Function)(context, config); } return plugin.apply(context, config); } function isConstructor(value: Function): boolean { return /^class\s/u.test(Function.prototype.toString.call(value)); } async function validateConfig( schema: StandardSchema | undefined, value: unknown, ): Promise { if (!schema) return value; const result = await schema['~standard'].validate(value); if ('issues' in result && result.issues) { throw new TypeError(result.issues.map(({ message }) => message).join('; ')); } return result.value; } function validateServiceName(name: string): void { if (!/^[A-Za-z][A-Za-z0-9._:-]*$/u.test(name)) throw new TypeError(`Invalid Service name: ${name}`); } function isIterable(value: unknown): value is Iterable { return Boolean(value && typeof (value as Iterable)[Symbol.iterator] === 'function'); } function isAsyncIterable(value: unknown): value is AsyncIterable { return Boolean( value && typeof (value as AsyncIterable)[Symbol.asyncIterator] === 'function', ); } function createLoggerService(name: () => string): LoggerService { const create = (explicit?: string): Logger => { const loggerName = explicit || name(); return { name: loggerName, error: (value, ...values) => console.error(`[${loggerName}]`, value, ...values), warn: (value, ...values) => console.warn(`[${loggerName}]`, value, ...values), info: (value, ...values) => console.info(`[${loggerName}]`, value, ...values), debug: (value, ...values) => console.debug(`[${loggerName}]`, value, ...values), }; }; const callable = ((explicit?: string) => create(explicit)) as LoggerService; Object.defineProperties(callable, { name: { value: 'logger' }, error: { value: (value: unknown, ...values: unknown[]) => create().error(value, ...values) }, warn: { value: (value: unknown, ...values: unknown[]) => create().warn(value, ...values) }, info: { value: (value: unknown, ...values: unknown[]) => create().info(value, ...values) }, debug: { value: (value: unknown, ...values: unknown[]) => create().debug(value, ...values) }, }); return callable; }