UNPKG

injektor

Version:

The simple dependency injection for Devebot

474 lines (464 loc) 12.3 kB
"use strict"; const chores = require("./chores"); const errors = require("./errors"); const Validator = require("./validator"); const debugx = require("./pinbug")("injektor:engine"); const util = require("util"); const noop = function () {}; const ACCEPTED_SEPARATORS = ["/", ":", "$"]; const NAME_PATTERN_TMPL = "^[a-zA-Z]{1}[a-zA-Z0-9&\\-_%s]*$"; const defaultConfig = { argumentSchemaLabel: "argumentSchema", argumentFieldsLabel: "argumentProperties", referenceArrayLabel: "referenceArray", namePatternTemplate: NAME_PATTERN_TMPL, nameResolvingStrictMode: false, allowLookupDuplicateNames: false, detectCyclicDependencies: true, separator: "/", logger: { debug: noop, trace: noop, error: noop } }; function Injektor(a = {}) { debugx.enabled && debugx(" + constructor start ..."); const b = {}; Object.keys(defaultConfig).forEach(function (c) { switch (c) { case "separator": if (ACCEPTED_SEPARATORS.indexOf(a[c]) >= 0) { b[c] = a[c]; } else { b[c] = defaultConfig[c]; } break; default: if (c in a) { b[c] = a[c]; } else { b[c] = defaultConfig[c]; } } }); const c = util.format(b.namePatternTemplate, b.separator); const d = new RegExp(c, "g"); debugx.enabled && debugx(" - namePattern: %s", c); const e = {}; const f = {}; const g = new Validator(); const h = { config: b, dependencies: e, namestore: f, validator: g }; /** * Get the dependency. If the call is the first time, copy it (for object) or * instantiate if (for constructor). */ function j(a, c, d) { c = c || {}; d = d || []; const f = s(a, c, d); if (typeof f === "string") { c = extractScope({ config: b }, f, c); } const g = e[f]; if (g == null) { const b = new errors.DependencyNotFoundError("No dependency registered with name: " + a); d.push(b); return undefined; } b.detectCyclicDependencies && q(f, c); g["cache"] = g["cache"] || k(f, c, d); b.detectCyclicDependencies && r(f, c); return g["cache"]; } /** * Converts the dependency from definition into an instance (object or service). */ function k(a, b, c) { let d; const f = e[a]; if (f["type"] == "service") { if (f["error"] == null) { try { const a = l(f["value"], b, c); if (c.length === 0) d = a; } catch (b) { debugx.enabled && debugx("convert(\"%s\") has failed: %s/%s", a, b.name, b.message); f["error"] = b; } } if (f["error"]) { const a = f["error"]; c.push(a); if (t(a)) throw a; } } else { d = f["value"]; } return d; } /** * Instantiates a constructor */ function l(a, b, c) { const d = function (b) { return a.apply(this, b); }; d.prototype = a.prototype; return new d(m(a, b, c)); } /** * Extracts the dependencies of a constructor */ function m(a, c, d) { if (a[b.argumentSchemaLabel]) { const e = a[b.argumentSchemaLabel]; if (e instanceof Object && !(e instanceof Array) && (e.properties == null || e.properties instanceof Object) && (e.properties == null || !(e.properties instanceof Array))) { const a = {}; const b = Object.keys(e.properties || {}); for (let e = 0; e < b.length; e++) { a[b[e]] = j(b[e], c, d); } const f = g.validate(a, e); if (!f.ok) { throw new errors.ValidatingArgumentError("Constructor argument validation is failed"); } return [a]; } else { throw new errors.InvalidArgumentSchemaError("Constructor has invalid argumentSchema descriptor"); } } else if (a[b.argumentFieldsLabel]) { const e = a[b.argumentFieldsLabel]; if (e instanceof Array) { const a = {}; for (let b = 0; b < e.length; b++) { a[e[b]] = j(e[b], c, d); } return [a]; } else { throw new errors.InvalidArgumentSchemaError("Constructor has invalid argumentFields descriptor"); } } else if (a[b.referenceArrayLabel]) { const e = a[b.referenceArrayLabel]; if (e instanceof Array) { const a = []; for (let b = 0; b < e.length; b++) { a.push(j(e[b], c, d)); } return a; } else { throw new errors.InvalidArgumentSchemaError("Constructor has invalid referenceArray descriptor"); } } else { const b = n(a); return b.map(function (a) { return j(a, c, d); }); } } function n(a) { const b = a.toString().replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg, "").match(/^function\s*[^(]*\(\s*([^)]*)\)/m)[1].split(/,/); if (b.length == 1 && b[0] == "") return []; return b; } function o(a) { if (typeof a === "function") { return { serviceInit: a }; } if (a instanceof Array) { if (a.length > 0) { a = a.slice(); const b = a.pop(); if (typeof b === "function") { let c = { serviceInit: b }; if (a.length > 0) { c.referenceNames = a; } return c; } } } return {}; } function p(a) { if (a.absoluteName != a.relativeName) { f[a.relativeName] = f[a.relativeName] || []; if (f[a.relativeName].indexOf(a.absoluteName) < 0) { f[a.relativeName].push(a.absoluteName); } } } function q(a, b) { b = b || {}; b.stacktrace = b.stacktrace || []; b.stacktrace.push(a); if (b.stacktrace.length == 1) { debugx.enabled && debugx(" - (%s) => %s", a, JSON.stringify(b.stacktrace)); } else { debugx.enabled && debugx(" - (%s) -> %s", a, JSON.stringify(b.stacktrace)); } const c = b.stacktrace.indexOf(a); if (c != b.stacktrace.length - 1) { throw new errors.DependencyCycleDetectedError(util.format("dependency cycle detected for \"%s\" at %s and %s in %s", a, b.stacktrace.length - 1, c, JSON.stringify(b.stacktrace))); } } function r(a, b) { b = b || {}; b.stacktrace = b.stacktrace || []; b.stacktrace.pop(); if (b.stacktrace.length > 0) { debugx.enabled && debugx(" - (%s) <- %s", a, JSON.stringify(b.stacktrace)); } else { debugx.enabled && debugx(" - (%s) <= %s", a, JSON.stringify(b.stacktrace)); } } function s(a, c, d) { c = c || {}; let g = null; if (c.scope) { const d = [c.scope, a].join(b.separator); if (e[d]) g = d; } if (g == null && e[a]) g = a; if (g == null && f[a] && f[a].length > 0) { if (f[a].length === 1 || b.nameResolvingStrictMode !== true) { g = f[a][0]; } if (b.allowLookupDuplicateNames != true && f[a].length > 1) { const b = new errors.DuplicatedRelativeNameError("name [" + a + "] is duplicated"); if (d instanceof Array) { d.push(b); } else { throw b; } } } return g; } function t(a) { return a instanceof errors.DependencyNotFoundError || a instanceof errors.DependencyCycleDetectedError; } /** * Registering an object as a dependency */ this.registerObject = function (a, c, f) { validateName({ namePattern: d }, a); const g = parseName({ config: b }, a, f); e[g.absoluteName] = { type: "object", value: c }; p(g); return this; }; /** * Registering a singleton-service constructor as a dependency */ this.defineService = function (a, c, f) { validateName({ namePattern: d }, a); const g = o(c); if (!g.serviceInit) { throw new errors.InvalidMethodArgumentError("invalid service descriptor"); } if (g.referenceNames) { g.serviceInit.referenceArray = g.referenceNames; } const h = parseName({ config: b }, a, f); e[h.absoluteName] = { type: "service", value: g.serviceInit }; p(h); return this; }; this.parseName = function (a, c) { const d = Object.assign({}, c); const e = d.exceptions; delete d.exceptions; return parseName({ config: b }, a, d, e); }; this.resolve = function (a, b) { const c = Object.assign({}, b); const d = c.exceptions; delete c.exceptions; return s(a, c, d); }; this.resolveName = this.resolve; this.suggest = function (a) { if (e[a]) return [a]; if (f[a]) { if (f[a].length === 0) { return []; } else { return f[a].slice(); } } return null; }; this.suggestName = this.suggest; /** * Lookups a dependency (object or service) from store */ this.lookup = function (a, b) { validateName({ namePattern: d }, a); let c = false; let e = []; // extract exceptions from options if (chores.isArray(b)) { c = true; e = b; b = {}; } else if (chores.isObject(b)) { c = chores.isArray(b.exceptions); e = b.exceptions = b.exceptions || []; } // retrieve object from storage const f = j(a, b, e); if (!c && e.length > 0) { chores.printExceptions(e); if (e.length === 1) throw e[0]; throw new errors.DependencyCombinationError("lookup() is failed", e); } return f; }; /** * Collects all dependencies of a service, pass them to and run the service */ this.invoke = function (a) { if (!chores.isArray(a)) { a = Array.prototype.slice.call(arguments); } var b = o(a); if (!b.serviceInit) { throw new errors.InvalidMethodArgumentError("invalid callback descriptor"); } b.serviceInit.referenceArray = b.referenceNames; var c = {}; var d = []; var e = m(b.serviceInit, c, d); if (d.length > 0) { chores.printExceptions(d); if (d.length === 1) throw d[0]; throw new errors.DependencyCombinationError("invoke() is failed", d); } return b.serviceInit.apply(null, e); }; Object.defineProperty(this, "separator", { get: function () { return b.separator; }, set: function (a) {} }); debugx.enabled && debugx(" - constructor end"); } function validateName(a, b) { let { namePattern: c } = a; if (typeof b !== "string" || b.length === 0) { throw new errors.InvalidMethodArgumentError("dependency name is empty or not a string"); } if (b.match(c) == null) { throw new errors.InvalidMethodArgumentError("dependency name does not match the pattern"); } } function extractScope(a, b, c) { c = c || {}; const d = chores.cloneObject(c); const e = splitScopeWithName(a, b); if (e.scope) { d.scope = e.scope; } return d; } function parseName(a, b, c) { const { config: d } = a; const e = []; c = c || {}; const f = splitScopeWithName(a, b); if (f.name) { e.push(f.scope, f.name); } else { e.push(b); } if (e.length === 1) { if (c.scope) e.unshift(c.scope); } if (e.length === 0 || e.length > 2) { throw new errors.InvalidMethodArgumentError("invalid name format"); } var g = { name: b, absoluteName: e.join(d.separator), relativeName: e[e.length - 1], scope: e[e.length - 2] }; return g; } let splitScopeWithName = function (a, b) { const { config: c } = a || {}; let { separator: d } = c; // const e = util.format("^(?<scope>.+)%s(?<name>[a-zA-Z]{1}[a-zA-Z0-9-_#]*)$", d); const f = new RegExp(e); // const g = b.match(f); const h = {}; if (g && g.groups) { for (const a in g.groups) { h[a] = g.groups[a]; } } return h; }; /* eslint-disable no-unused-vars */ function parseNameLegacy(a, b, c) { const { config: d } = a; c = c || {}; var e = b.split(d.separator); if (e.length === 1) { if (c.scope) e.unshift(c.scope); } if (e.length === 0 || e.length > 2) { throw new errors.InvalidMethodArgumentError("invalid name format"); } var f = { name: b, absoluteName: e.join(d.separator), relativeName: e[e.length - 1], scope: e[e.length - 2] }; return f; } module.exports = Injektor;