@elsikora/cladi
Version:
Zero-dependency TypeScript DI toolkit with typed tokens and scoped lifecycles.
111 lines (107 loc) • 4.88 kB
JavaScript
'use strict';
var error_class = require('../../../../infrastructure/class/base/error.class.js');
var get_utility = require('../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 = get_utility.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 error_class.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 error_class.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 error_class.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 error_class.BaseError("Injectable metadata dependency token must be a symbol", {
code: "INJECTABLE_METADATA_DEPS_TOKEN_INVALID",
context: { index: dependencyIndex },
source: "createAutowireProvider",
});
}
}
}
exports.createAutowireProvider = createAutowireProvider;
//# sourceMappingURL=autowire-provider.utility.js.map