import type { DependencyCls, DependencyKey, DependencyKind } from "./types"; type UnionToIntersection = (Union extends unknown ? (value: Union) => void : never) extends ( value: infer Intersection, ) => void ? Intersection : never; type DependencyToField = Dep extends DependencyCls ? { [K in Key]: Instance } : never; export type DependencyInstanceMap = [Deps[number]] extends [never] ? Record : UnionToIntersection>; const capitalize = (value: string) => value.slice(0, 1).toUpperCase() + value.slice(1); const createDependencyKey = ( refName: RefName, kind: Kind, ): DependencyKey => `${refName}${capitalize(kind)}` as DependencyKey; export class CommandContainer { static #instances = new Map(); static #resolving = new Set(); static get(dep: DependencyCls): T { const instance = CommandContainer.#instances.get(dep); if (instance) return instance as T; if (CommandContainer.#resolving.has(dep)) throw new Error(`Circular command dependency: ${dep.name}`); CommandContainer.#resolving.add(dep); try { const nextInstance = new dep(); CommandContainer.#instances.set(dep, nextInstance); return nextInstance; } finally { CommandContainer.#resolving.delete(dep); } } static clear() { CommandContainer.#instances.clear(); CommandContainer.#resolving.clear(); } } export const getDependencyKey = (dep: DependencyCls) => dep.dependencyKey; export const assertUniqueDependencies = (deps: readonly DependencyCls[]) => { const keys = new Map(); const classes = new Set(); for (const dep of deps) { if (classes.has(dep)) throw new Error(`Duplicate command dependency class: ${dep.name}`); classes.add(dep); const key = getDependencyKey(dep); const existing = keys.get(key); if (existing) throw new Error(`Duplicate command dependency key "${key}": ${existing.name}, ${dep.name}`); keys.set(key, dep); } }; export const injectDependencies = (target: object, deps: Deps) => { assertUniqueDependencies(deps); for (const dep of deps) { Object.defineProperty(target, getDependencyKey(dep), { configurable: true, enumerable: false, value: CommandContainer.get(dep), writable: false, }); } return target as typeof target & DependencyInstanceMap; }; export const runner = (refName: RefName) => { class RunnerBase { static readonly refName = refName; static readonly dependencyKind = "runner"; static readonly dependencyKey = createDependencyKey(refName, "runner"); } return RunnerBase as unknown as DependencyCls, DependencyKey>; }; export const script = ( refName: RefName, deps: Deps = [] as unknown as Deps, ) => { assertUniqueDependencies(deps); class ScriptBase { static readonly refName = refName; static readonly dependencyKind = "script"; static readonly dependencyKey = createDependencyKey(refName, "script"); constructor() { injectDependencies(this, deps); } } return ScriptBase as unknown as DependencyCls, DependencyKey>; };