UNPKG

@elsikora/cladi

Version:

Zero-dependency TypeScript DI toolkit with typed tokens and scoped lifecycles.

109 lines (106 loc) 4.79 kB
import { BaseError } from '../../../../infrastructure/class/base/error.class.js'; import { getInjectableMetadata } from '../injectable-metadata/get.utility.js'; /** * Creates a class provider using explicit options and optional class metadata. * @param {Constructor<T>} useClass Class constructor to instantiate. * @param {ICreateAutowireProviderOptions<T>} options Explicit provider options. * @returns {IClassProvider<T>} Class provider definition. * @template T Provider instance type. */ function createAutowireProvider(useClass, options) { const metadata = getInjectableMetadata(useClass); const staticDependencies = useClass.inject; const dependencies = options.deps ?? metadata?.deps ?? staticDependencies ?? []; const afterResolve = options.afterResolve ?? createLifecycleMethodHook(useClass, options.token, metadata?.afterResolveMethod, "afterResolve"); const onDispose = options.onDispose ?? createLifecycleMethodHook(useClass, options.token, metadata?.onDisposeMethod, "onDispose"); const onInit = options.onInit ?? createLifecycleMethodHook(useClass, options.token, metadata?.onInitMethod, "onInit"); validateDependencies(dependencies); return { afterResolve, deps: dependencies, isMultiBinding: options.isMultiBinding ?? metadata?.isMultiBinding, lifecycle: options.lifecycle ?? metadata?.lifecycle, onDispose, onInit, provide: options.token, useClass, }; } /** * Resolves a lifecycle hook method from injectable metadata. * @param {Constructor<T>} useClass Provider class. * @param {Token<T>} token Provider token for diagnostics. * @param {string | undefined} methodName Decorated method name. * @param {"afterResolve" | "onDispose" | "onInit"} hookName Hook type. * @returns {((instance: T) => Promise<void> | void) | undefined} Lifecycle hook function. * @template T Provider instance type. */ function createLifecycleMethodHook(useClass, token, methodName, hookName) { if (!methodName) { return undefined; } const prototypeCandidate = useClass.prototype; const prototype = (typeof prototypeCandidate === "object" && prototypeCandidate !== null ? prototypeCandidate : {}); const prototypeMethod = prototype[methodName]; if (typeof prototypeMethod !== "function") { throw new BaseError("Injectable lifecycle method not found on class prototype", { code: "INJECTABLE_LIFECYCLE_METHOD_NOT_FOUND", context: { hook: hookName, methodName, token: describeToken(token), }, source: "createAutowireProvider", }); } return (instance) => { const instanceRecord = instance; const instanceMethod = instanceRecord[methodName]; if (typeof instanceMethod !== "function") { throw new BaseError("Injectable lifecycle method is not callable on resolved instance", { code: "INJECTABLE_LIFECYCLE_METHOD_INVALID", context: { hook: hookName, methodName, token: describeToken(token), }, source: "createAutowireProvider", }); } return instanceMethod.call(instance); }; } /** * Builds readable token description for diagnostics. * @param {Token<unknown>} token Provider token. * @returns {string} Token description. */ function describeToken(token) { return token.description ? `Symbol(${token.description})` : token.toString(); } /** * Validates dependency metadata for autowire provider creation. * @param {ReadonlyArray<Token<unknown>>} dependencies Dependency token list. */ function validateDependencies(dependencies) { for (let dependencyIndex = 0; dependencyIndex < dependencies.length; dependencyIndex += 1) { const hasDependencyAtIndex = Object.prototype.hasOwnProperty.call(dependencies, dependencyIndex); if (!hasDependencyAtIndex) { throw new BaseError("Injectable metadata has sparse dependency indexes", { code: "INJECTABLE_METADATA_DEPS_SPARSE", context: { index: dependencyIndex }, source: "createAutowireProvider", }); } const dependencyValue = dependencies[dependencyIndex]; if (typeof dependencyValue !== "symbol") { throw new BaseError("Injectable metadata dependency token must be a symbol", { code: "INJECTABLE_METADATA_DEPS_TOKEN_INVALID", context: { index: dependencyIndex }, source: "createAutowireProvider", }); } } } export { createAutowireProvider }; //# sourceMappingURL=autowire-provider.utility.js.map