UNPKG

@carlosv2/glue

Version:

Dependency injection library that stays out of the way

214 lines (213 loc) 9.6 kB
import { DefinitionContext } from './context/index.js'; import { DiError } from './error.js'; import { UnknownServiceTypeError } from './error/service.js'; import { getCallerFile, has, isArray, isDictionary, isFirstChar, isString, } from './utils.js'; export class Processor { fromPaths(pattern) { let fullPattern = pattern; if (!fullPattern.startsWith('/')) { const base = getCallerFile(); if (!base) { throw new DiError('The caller file could not be determined'); } fullPattern = this.getFullPath(base, pattern); } this.glob(fullPattern).forEach(this.fromPath.bind(this)); } fromPath(path) { this.fromContent(path, this.readContents(path)); } fromContent(path, content) { this.fromData(path, this.deserialise(path, content)); } fromData(path, content) { if (!isDictionary(content)) { throw new DiError(`The contents of the path \`${path}\` must represent an object`); } if (has('extends', content)) { if (!isArray(content['extends'])) { throw new DiError(`The extends section in the path \`${path}\` must represent an array`); } const context = new DefinitionContext(path, 'ignore', 'ignore'); content['extends'].forEach(extension => { if (!isString(extension)) { throw new DiError(`Each extended file must add as an string in the path \`${path}\``); } this.addExtend(this.getFullPath(context.getPath(), extension)); }); } if (has('parameters', content)) { if (!isDictionary(content['parameters'])) { throw new DiError(`The parameters section in the path \`${path}\` must represent an object`); } Object.entries(content['parameters']).forEach(([id, value]) => { const definition = this.serialise(path, value); const context = new DefinitionContext(path, id, definition); this.addParameter(id, this.parseValue(context, value)); }); } if (has('services', content)) { if (!isDictionary(content['services'])) { throw new DiError(`The services section in the path \`${path}\` must represent an object`); } Object.entries(content['services']).forEach(([id, value]) => { const definition = this.serialise(path, value); const context = new DefinitionContext(path, id, definition); if (isString(value)) { this.addAlias(id, this.parseAlias(context, value)); } else { this.addService(id, this.parseService(context, value)); } }); } } parseValue(context, value) { if (isArray(value)) { return this.processLiteralValue(context, value.map(item => this.parseValue(context, item))); } if (isDictionary(value)) { if (has('symbol', value)) { return this.parseService(context, value); } if (has('_symbol', value)) { value['symbol'] = value['_symbol']; } return this.processLiteralValue(context, Object.fromEntries(Object.entries(value).map(([key, item]) => [ key, this.parseValue(context, item), ]))); } if (isString(value)) { if (isFirstChar(value, '<')) { return this.processServiceValue(context, value.substring(1)); } else if (isFirstChar(value, '$')) { return this.processParameterValue(context, value.substring(1)); } else if (isFirstChar(value, '>')) { return this.processTagListValue(context, value.substring(1)); } else if (isFirstChar(value, '}')) { return this.processTagObjectValue(context, value.substring(1)); } else if (isFirstChar(value, '%')) { const [name, declaredType, ...values] = value.substring(1).split('/'); const type = declaredType !== null && declaredType !== void 0 ? declaredType : 's'; const fallback = values.length > 0 ? values.join('/') : undefined; if (type === 's') { return this.processEnvStrValue(context, name, fallback); } else if (type === 'b') { return this.processEnvBoolValue(context, name, fallback); } else if (type === 'n') { return this.processEnvNumValue(context, name, fallback); } else { throw new DiError('Unknown environment variable type while parsing', context); } } else if (isFirstChar(value, '!')) { return this.processEvalValue(context, value); } else if (isFirstChar(value, '(')) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_, path, name] = value.match(/^\(([^\)]+)(?:\)(.*))?$/); return this.processSymbolValue(context, path, name); } else if (isFirstChar(value, '[')) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_, pattern, name] = value.match(/^\[([^\]]+)(?:\](.*))?$/); const fullPattern = this.getFullPath(context.getPath(), pattern, false); return this.processLiteralValue(context, this.glob(fullPattern).map(path => { return this.processSymbolValue(context, path, name); })); } else { return this.processLiteralValue(context, value); } } return this.processLiteralValue(context, value); } parseAlias(context, value) { if (!isString(value)) { throw new DiError('A string definition was expected', context); } return this.processAlias(context, value); } parseTag(context, value) { if (isString(value)) { return { name: value }; } if (isDictionary(value)) { if (!has('name', value)) { throw new DiError('POJO tags must have the `name` key.', context); } return value; } throw new DiError('Tags must be defined as strings or POJOs.', context); } parseCall(context, value) { var _a; if (isString(value)) { return { method: this.parseValue(context, value), params: [] }; } if (isDictionary(value)) { if (!has('method', value)) { throw new DiError('POJO calls must have the `method` key.', context); } const method = this.parseValue(context, value['method']); const params = (_a = value['params']) !== null && _a !== void 0 ? _a : []; if (!isArray(params)) { throw new DiError('Service call parameters must be an array of values.', context); } return { method, params: params.map(param => this.parseValue(context, param)), }; } throw new DiError('Calls must be defined as strings or POJOs.', context); } parseService(context, value) { var _a, _b; if (!isDictionary(value)) { throw new DiError('A service can only be created from a POJO', context); } if (!has('symbol', value)) { throw new DiError('A service must declare the `symbol` property.', context); } const symbol = this.parseValue(context, value['symbol']); const scope = value['scope']; let tags = []; if (has('tags', value)) { if (!isArray(value['tags'])) { throw new DiError('Service tags must be an array of tags.', context); } tags = value['tags'].map(tag => this.parseTag(context, tag)); } let calls = []; if (has('calls', value)) { if (!isArray(value['calls'])) { throw new DiError('Service calls must be an array of calls.', context); } calls = value['calls'].map(call => this.parseCall(context, call)); } if (!has('factory', value) && !has('property', value)) { const args = ((_a = value['args']) !== null && _a !== void 0 ? _a : []).map(arg => this.parseValue(context, arg)); return this.processConstructorService(context, symbol, args, scope, tags, calls); } else if (has('factory', value) && !has('property', value)) { const factory = this.parseValue(context, value['factory']); const args = ((_b = value['args']) !== null && _b !== void 0 ? _b : []).map(arg => this.parseValue(context, arg)); return this.processFactoryService(context, symbol, factory, args, scope, tags, calls); } else if (!has('factory', value) && has('property', value)) { const property = this.parseValue(context, value['property']); return this.processPropertyService(context, symbol, property, scope, tags, calls); } else { throw new UnknownServiceTypeError(context); } } }