@carlosv2/glue
Version:
Dependency injection library that stays out of the way
214 lines (213 loc) • 9.6 kB
JavaScript
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);
}
}
}