awilix-manager
Version:
Wrapper over awilix to support more complex use-cases, such as async init and eager injection
204 lines (203 loc) • 7.83 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AwilixManager = void 0;
exports.asMockClass = asMockClass;
exports.asMockValue = asMockValue;
exports.asMockFunction = asMockFunction;
exports.asyncInit = asyncInit;
exports.eagerInject = eagerInject;
exports.getWithTags = getWithTags;
exports.getByPredicate = getByPredicate;
exports.asyncDispose = asyncDispose;
const awilix_1 = require("awilix");
function asMockClass(Type, opts) {
return (0, awilix_1.asClass)(Type, opts);
}
function asMockValue(value) {
return (0, awilix_1.asValue)(value);
}
function asMockFunction(fn, opts) {
return (0, awilix_1.asFunction)(fn, opts);
}
class AwilixManager {
config;
constructor(config) {
this.config = config;
if (config.strictBooleanEnforced) {
for (const entry of Object.entries(config.diContainer.registrations)) {
const [dependencyName, config] = entry;
if ('enabled' in config && config.enabled !== true && config.enabled !== false) {
throw new Error(`Invalid config for ${dependencyName}. "enabled" field can only be set to true or false, or omitted`);
}
}
}
}
async executeInit() {
if (this.config.eagerInject) {
eagerInject(this.config.diContainer);
}
if (this.config.asyncInit) {
await asyncInit(this.config.diContainer, {
enableDebugLogging: this.config.enableDebugLogging,
loggerFn: this.config.loggerFn,
});
}
}
async executeDispose() {
await asyncDispose(this.config.diContainer);
}
getWithTags(tags) {
return getWithTags(this.config.diContainer, tags);
}
getByPredicate(predicate) {
return getByPredicate(this.config.diContainer, predicate);
}
}
exports.AwilixManager = AwilixManager;
function isAsyncInitConfig(value) {
return (typeof value === 'object' && value !== null && ('method' in value || 'nonBlocking' in value));
}
function getAsyncInitMethod(asyncInit) {
if (isAsyncInitConfig(asyncInit)) {
// If method is not specified, default to true (use default asyncInit method)
return asyncInit.method ?? true;
}
return asyncInit;
}
function isNonBlocking(asyncInit) {
return isAsyncInitConfig(asyncInit) && asyncInit.nonBlocking === true;
}
async function asyncInit(diContainer, options = {}) {
const { enableDebugLogging, loggerFn = console.log } = options;
const dependenciesWithAsyncInit = Object.entries(diContainer.registrations)
.filter((entry) => {
return entry[1].asyncInit && entry[1].enabled !== false;
})
.sort((entry1, entry2) => {
const [key1, resolver1] = entry1;
const [key2, resolver2] = entry2;
const asyncInitPriority1 = resolver1.asyncInitPriority ?? 1;
const asyncInitPriority2 = resolver2.asyncInitPriority ?? 1;
if (asyncInitPriority1 !== asyncInitPriority2) {
return asyncInitPriority1 - asyncInitPriority2;
}
return key1.localeCompare(key2);
});
for (const [key, description] of dependenciesWithAsyncInit) {
if (enableDebugLogging) {
loggerFn(`asyncInit: ${key} - started`);
}
const resolvedValue = diContainer.resolve(key);
const method = getAsyncInitMethod(description.asyncInit);
const nonBlocking = isNonBlocking(description.asyncInit);
// Validate method existence synchronously before starting async init
validateAsyncInitMethod(resolvedValue, method, key);
const initPromise = executeAsyncInitMethod(resolvedValue, method, key, diContainer);
if (nonBlocking) {
// Fire-and-forget: don't await the promise
initPromise.then(() => {
if (enableDebugLogging) {
loggerFn(`asyncInit: ${key} - finished (non-blocking)`);
}
});
}
else {
await initPromise;
if (enableDebugLogging) {
loggerFn(`asyncInit: ${key} - finished`);
}
}
}
}
function validateAsyncInitMethod(resolvedValue, method, key) {
if (method === true) {
if (!('asyncInit' in resolvedValue)) {
throw new Error(`Method asyncInit does not exist on dependency ${key}`);
}
}
else if (typeof method === 'string') {
// custom method name
if (!(method in resolvedValue)) {
throw new Error(`Method ${method} for asyncInit does not exist on dependency ${key}`);
}
}
}
async function executeAsyncInitMethod(resolvedValue, method, _key, diContainer) {
// use default asyncInit method
if (method === true) {
await resolvedValue.asyncInit(diContainer.cradle);
}
else if (typeof method === 'function') {
// use function asyncInit
await method(resolvedValue, diContainer);
}
else if (typeof method === 'string') {
// use custom method name
await resolvedValue[method](diContainer.cradle);
}
}
function eagerInject(diContainer) {
const dependenciesWithEagerInject = Object.entries(diContainer.registrations).filter(([_key, description]) => {
return description.eagerInject && description.enabled !== false;
});
for (const [key, description] of dependenciesWithEagerInject) {
const resolvedComponent = diContainer.resolve(key);
if (typeof description.eagerInject === 'string') {
resolvedComponent[description.eagerInject]();
}
}
}
function getWithTags(diContainer, tags) {
const dependenciesWithTags = Object.entries(diContainer.registrations).filter(([_key, description]) => {
return (description.enabled !== false &&
tags.every((v) => description.tags && description.tags.includes(v)));
});
const resolvedComponents = {};
for (const [key] of dependenciesWithTags) {
resolvedComponents[key] = diContainer.resolve(key);
}
return resolvedComponents;
}
function getByPredicate(diContainer, predicate) {
const enabledDependencies = Object.entries(diContainer.registrations).filter(([_key, description]) => {
return description.enabled !== false;
});
const resolvedComponents = {};
for (const [key] of enabledDependencies) {
const resolvedElement = diContainer.resolve(key);
if (predicate(resolvedElement)) {
resolvedComponents[key] = resolvedElement;
}
}
return resolvedComponents;
}
async function asyncDispose(diContainer) {
const dependenciesWithAsyncDispose = Object.entries(diContainer.registrations)
.filter(([_key, description]) => {
return description.asyncDispose && description.enabled !== false;
})
.sort((entry1, entry2) => {
const [key1, resolver1] = entry1;
const [key2, resolver2] = entry2;
const asyncDisposePriority1 = resolver1.asyncDisposePriority ?? 1;
const asyncDisposePriority2 = resolver2.asyncDisposePriority ?? 1;
if (asyncDisposePriority1 !== asyncDisposePriority2) {
return asyncDisposePriority1 - asyncDisposePriority2;
}
return key1.localeCompare(key2);
});
for (const [key, description] of dependenciesWithAsyncDispose) {
const resolvedValue = diContainer.resolve(key);
const asyncDispose = description.asyncDispose;
if (typeof asyncDispose === 'function') {
await asyncDispose(resolvedValue);
continue;
}
if (asyncDispose === true) {
await resolvedValue.asyncDispose();
continue;
}
// @ts-expect-error
await resolvedValue[asyncDispose]();
}
}