UNPKG

assemblerjs

Version:

A general purpose Dependency Injection library for node and browser.

920 lines (893 loc) 28.4 kB
import { switchCase, pipe, conditionally, isClass, isObject, clearInstance, isDefined, isOfType, forIn, forOf, proxifyIterable, isAsync, onlyAlphanumeric } from '@assemblerjs/core'; class AbstractAssemblage { static onRegister(s, t) {} } const ReflectParamTypes = 'design:paramtypes'; const ReflectPrefix = '__'; const ReflectSuffix = '__'; var ReflectFlags; (function(e) { e["IsAssemblage"] = "is_assemblage"; })(ReflectFlags || (ReflectFlags = {})); var ReflectValue; (function(e) { e["AssemblageDefinition"] = "assemblage:definition.value"; e["AssemblageContext"] = "assemblage:context.value"; })(ReflectValue || (ReflectValue = {})); const defineCustomMetadata = (t, o, n)=>{ Reflect.defineMetadata(`${ReflectPrefix}${t}${ReflectSuffix}`, o, n); }; const getOwnCustomMetadata = (t, o)=>{ return Reflect.getOwnMetadata(`${ReflectPrefix}${t}${ReflectSuffix}`, o); }; const getParamTypes = (e)=>{ return Reflect.getMetadata(ReflectParamTypes, e) || []; }; const isAssemblage = (s)=>{ return getOwnCustomMetadata(ReflectFlags.IsAssemblage, s) || false; }; const getAssemblageDefinition = (e)=>{ return getOwnCustomMetadata(ReflectValue.AssemblageDefinition, e); }; const getAssemblageContext = (e)=>{ return getOwnCustomMetadata(ReflectValue.AssemblageContext, e); }; const n = { singleton: { test: (r)=>typeof r === 'boolean' || typeof r === 'undefined', throw: ()=>{ throw new Error(`'singleton' property must be of type 'boolean' or 'undefined'.`); }, transform: (r)=>{ return typeof r === 'undefined' ? true : r ? true : false; } }, events: { test: (r)=>typeof r === 'undefined' || Array.isArray(r) && r.every((r)=>typeof r === 'string'), throw: ()=>{ throw new Error(`'events' property must be an array of strings or 'undefined'.`); }, transform: (r)=>r }, inject: { test: (r)=>typeof r === 'undefined' || Array.isArray(r) && r.every((r)=>Array.isArray(r) && r.length >= 1 && r.length <= 3), throw: ()=>{ throw new Error(`'inject' property must be an array of tuples of length 1, 2 or 3.`); }, transform: (r)=>r }, use: { test: (r)=>typeof r === 'undefined' || Array.isArray(r) && r.every((r)=>Array.isArray(r) && r.length == 2), throw: ()=>{ throw new Error(`'use' property must be an array of tuples of length 2.`); }, transform: (r)=>r }, tags: { test: (r)=>typeof r === 'undefined' || typeof r === 'string' || Array.isArray(r) && r.every((r)=>typeof r === 'string'), throw: ()=>{ throw new Error(`'tags' property must be a string or an array of strings.`); }, transform: (r)=>typeof r === 'string' ? [ r ] : r }, metadata: { test: (r)=>(typeof r === 'object' || typeof r === 'undefined') && !Array.isArray(r), throw: ()=>{ throw new Error(`'metadata' property must be of type 'object' or 'undefined'.`); }, transform: (r)=>r }, global: { test: (r)=>(typeof r === 'object' || typeof r === 'undefined') && !Array.isArray(r), throw: ()=>{ throw new Error(`'global' property must be of type 'object' or 'undefined'.`); }, transform: (r)=>r } }; const validateDefinition = (r)=>{ const t = { ...r }; for(const r in t){ if (!Object.keys(n).includes(r)) { throw new Error(`Property '${r}' is not a valid assemblage definition property.`); } } for(const r in n){ const e = n[r].test; const o = n[r].throw; const s = n[r].transform; if (!e(t[r])) { o(); } t[r] = s(t[r]); } return t; }; const getDefinition = (t)=>{ if (!isAssemblage(t)) { throw new Error(`Class '${t.name}' is not an assemblage.`); } return getOwnCustomMetadata(ReflectValue.AssemblageDefinition, t); }; const getDefinitionValue = (r, t)=>{ const e = getDefinition(t); return e[r]; }; const setDefinitionValue = (e, o, n)=>{ const s = getDefinition(n); s[e] = o; const i = validateDefinition(s); defineCustomMetadata(ReflectValue.AssemblageDefinition, i, n); return i; }; const i$2 = (e)=>{ return { identifier: e[0], concrete: e[0], configuration: {} }; }; const c$2 = (o)=>{ const i = ()=>isClass(o[0]) && isClass(o[1]); const c = ()=>isClass(o[0]) && isObject(o[1]); const s = ()=>pipe(conditionally({ if: ()=>i(), then: ()=>{ return { identifier: o[0], concrete: o[1], configuration: {} }; } }), conditionally({ if: ()=>c(), then: ()=>{ return { identifier: o[0], concrete: o[0], configuration: o[1] }; }, else: (e)=>e }))(); return s(); }; const s$2 = (e)=>{ return { identifier: e[0], concrete: e[1], configuration: e[2] }; }; const resolveInjectionTuple = (e)=>switchCase({ 1: ()=>i$2(e), 2: ()=>c$2(e), 3: ()=>s$2(e) }, ()=>{ throw new Error(`Injection tuple must be of length 1, 2 or 3.`); })(e.length); const resolveInstanceInjectionTuple = (e)=>{ return { identifier: e[0], concrete: e[0], instance: e[1], configuration: {} }; }; const Assemblage = (e)=>{ return (o)=>{ return decorateAssemblage(o, e); }; }; const decorateAssemblage = (s, m)=>{ const n = validateDefinition(m || {}); defineCustomMetadata(ReflectFlags.IsAssemblage, true, s); defineCustomMetadata(ReflectValue.AssemblageDefinition, n, s); return s; }; class AbstractListenerCollection { } class ListenerCollection { dispose() { clearInstance(this, ListenerCollection); } add(...t) { const n = (t)=>this.collection[t.channel].push(t.listener); const l = conditionally({ if: ()=>t.length === 2, then: ()=>{ return { channel: t[0], listener: t[1] }; }, else: ()=>{ const e = t[0]; return { channel: e[0], listener: e[1] }; } }); const c = conditionally({ if: (t)=>!isDefined(this.collection[t.channel]), then: (t)=>{ this.collection[t.channel] = []; n(t); }, else: (t)=>{ n(t); } }); pipe(l, c)(); return this; } remove(t, n) { const l = (e)=>this.collection[t].splice(e, 1); const c = conditionally({ if: ()=>this.collection[t] && this.collection[t].length === 0, then: ()=>delete this.collection[t] }); const o = conditionally({ if: ()=>isDefined(n), then: ()=>l(this.collection[t].indexOf(n)), else: ()=>delete this.collection[t] }); const r = conditionally({ if: (t)=>this.has(t), then: (t)=>this.collection[t] }); pipe(r, o, c)(); return this; } has(...t) { if (isOfType('string')(t[0])) { return Object.keys(this.collection).includes(t[0]); } else if (isOfType('function')(t[0])) { return Object.values(this.collection).flat().includes(t[0]); } return false; } get(...t) { if (isOfType('string')(t[0])) { return this.collection[t[0]]; } else if (isOfType('function')(t[0])) { return Object.values(this.collection).flat().filter((e)=>e === t[0]); } return []; } clear() { const t = forIn(this.collection); const e = (t)=>forOf(this.collection[t])((e)=>this.remove(t, e)); t((t)=>e(t)); return this; } get listeners() { return Object.values(this.collection).flat(); } get channels() { return Object.keys(this.collection); } get length() { return Object.values(this.collection).flat().length; } [Symbol.iterator]() { let t = -1; const e = this.collection ? Object.keys(this.collection) : []; return { next: ()=>({ value: e[++t], done: !(t in e) }) }; } constructor(){ this.collection = {}; const t = proxifyIterable(this, ListenerCollection); return t; } } class AbstractEventManager { } class EventManager { dispose() { this.listeners.dispose(); this.channels.clear(); clearInstance(this, EventManager); } addChannels(...e) { const n = forOf(e); n((e)=>{ const s = this.cleanChannel(e); if (this.channels.has(s)) { throw new Error(`Channel '${s}' already exists.`); } this.channels.add(s); }); return this; } removeChannels(...e) { const n = forOf(e); n((e)=>{ const s = this.cleanChannel(e); if (s !== '*' && this.channels.has(s)) { this.channels.delete(s); this.listeners.remove(s); this.onceListeners.remove(s); } }); return this; } on(e, s) { const n = this.cleanChannel(e); this.listeners.add(n, s); return this; } once(e, s) { const n = this.cleanChannel(e); this.onceListeners.add(n, s); return this; } off(e, s) { const n = this.cleanChannel(e); this.listeners.remove(n, s); return this; } emit(e, ...n) { const t = this.cleanChannel(e); if (this.channels.has(t)) { const e = this.onceListeners.get('*') || []; const i = this.listeners.get('*') || []; const h = this.onceListeners.get(t) || []; const r = this.listeners.get(t) || []; const o = forOf(e); const c = forOf(i); const l = forOf(h); const a = forOf(r); o((e)=>{ this.run(e, ...n); this.onceListeners.remove('*', e); }); c((e)=>{ this.run(e, ...n); }); l((e)=>{ this.run(e, ...n); this.onceListeners.remove(t, e); }); a((e)=>{ this.run(e, ...n); }); } return this; } run(e, ...s) { if (isAsync(e)) { const n = e; return n(...s).then(()=>Promise.resolve()); } e(...s); } cleanChannel(e) { return onlyAlphanumeric(e, '*', ':', '.', '-', '_'); } constructor(...e){ this.listeners = new ListenerCollection(); this.onceListeners = new ListenerCollection(); this.channels = new Set([ '*' ]); this.addChannels(...e); } } const registerEvents = (t, n)=>{ const o = t.concrete.prototype instanceof EventManager; if (o) { const e = n; const o = e.channels; for (const n of t.events){ if (!o.has(n)) e.addChannels(n); if (!t.privateContext.events.has(n)) t.privateContext.addChannels(n); } for (const e of t.events){ n.on(e, (...n)=>{ t.privateContext.emit(e, ...n); }); } } else { for (const e of t.events){ if (!t.privateContext.events.has(e)) t.privateContext.addChannels(e); } } }; const unregisterEvents = (t, n)=>{ const o = t.concrete.prototype instanceof EventManager; if (o) { const e = n; for (const n of t.events){ e.off(n); } e.removeChannels(...t.events); t.privateContext.removeChannels(...t.events); } else { for (const e of t.events){ if (t.privateContext.events.has(e)) { t.privateContext.removeChannels(e); } } } }; const Await = (t, e = 25)=>{ return (n, s, a)=>{ const i = a.value; a.value = async function() { return new Promise((n)=>{ if (this[t]) { i.apply(this); n(); } else { const s = setInterval(()=>{ if (this[t]) { clearInterval(s); i.apply(this); n(); } }, e); } }); }; }; }; var ReflectParamValue; (function(e) { e["UseIdentifier"] = "assemblage:use.param.value"; e["GlobalIdentifier"] = "assemblage:global.param.value"; })(ReflectParamValue || (ReflectParamValue = {})); var ReflectParamIndex; (function(e) { e["Context"] = "assembler:context.param.index"; e["Dispose"] = "assembler:dispose.param.index"; e["Definition"] = "assemblage:definition.param.index"; e["Configuration"] = "assemblage:configuration.param.index"; e["Use"] = "assemblage:use.param.index"; e["Global"] = "assemblage:global.param.index"; })(ReflectParamIndex || (ReflectParamIndex = {})); const i$1 = (t)=>()=>{ return (i, s, r)=>{ const c = getOwnCustomMetadata(t, i) || []; c.push(r); defineCustomMetadata(t, c, i); }; }; const s$1 = i$1(ReflectParamIndex.Context); const r$1 = i$1(ReflectParamIndex.Configuration); const c$1 = i$1(ReflectParamIndex.Definition); const e$1 = i$1(ReflectParamIndex.Dispose); const Use = (e)=>{ return (o, t, s)=>{ decorateUse(e, o, s); }; }; const decorateUse = (r, n, c)=>{ const U = getOwnCustomMetadata(ReflectParamIndex.Use, n) || []; U.push(c); defineCustomMetadata(ReflectParamIndex.Use, U, n); const i = getOwnCustomMetadata(ReflectParamValue.UseIdentifier, n) || {}; i[c] = r; defineCustomMetadata(ReflectParamValue.UseIdentifier, i, n); }; const Global = (o)=>{ return (t, l, r)=>{ decorateGlobal(o, t, r); }; }; const decorateGlobal = (e, a, n)=>{ const c = getOwnCustomMetadata(ReflectParamIndex.Global, a) || []; c.push(n); defineCustomMetadata(ReflectParamIndex.Global, c, a); const b = getOwnCustomMetadata(ReflectParamValue.GlobalIdentifier, a) || {}; b[n] = e; defineCustomMetadata(ReflectParamValue.GlobalIdentifier, b, a); }; const o = (o)=>{ return getOwnCustomMetadata(ReflectParamIndex.Context, o) || []; }; const r = (o)=>{ return getOwnCustomMetadata(ReflectParamIndex.Configuration, o) || []; }; const s = (o)=>{ return getOwnCustomMetadata(ReflectParamIndex.Definition, o) || []; }; const e = (o)=>{ return getOwnCustomMetadata(ReflectParamIndex.Dispose, o) || []; }; const c = (o)=>{ return getOwnCustomMetadata(ReflectParamIndex.Use, o) || []; }; const i = (o)=>{ return getOwnCustomMetadata(ReflectParamIndex.Global, o) || []; }; const getDecoratedParametersIndexes = (t)=>{ const n = o(t) || []; const u = s(t) || []; const m = r(t) || []; const a = e(t) || []; const f = c(t) || []; const p = i(t) || []; return { Context: n, Definition: u, Configuration: m, Dispose: a, Use: f, Global: p }; }; const createConstructorDecorator = (t)=>{ return (o)=>ConstructorDecorator(t, o); }; const ConstructorDecorator = (f, p)=>(l)=>{ const m = class extends l { constructor(...t){ super(...t); if (f) f.call(this, p); } }; Object.defineProperty(m, 'name', { value: l.name }); const d = Reflect.getOwnMetadata(ReflectParamTypes, l) || []; const D = getDecoratedParametersIndexes(l); const C = []; for(let e = 0; e < d.length; e++){ if (D.Context.includes(e)) { const n = getOwnCustomMetadata(ReflectParamIndex.Context, l) || []; n.push(e); defineCustomMetadata(ReflectParamIndex.Context, n, m); continue; } if (D.Definition.includes(e)) { const n = getOwnCustomMetadata(ReflectParamIndex.Definition, l) || []; n.push(e); defineCustomMetadata(ReflectParamIndex.Definition, n, m); continue; } if (D.Configuration.includes(e)) { const n = getOwnCustomMetadata(ReflectParamIndex.Configuration, l) || []; n.push(e); defineCustomMetadata(ReflectParamIndex.Configuration, n, m); continue; } if (D.Dispose.includes(e)) { const n = getOwnCustomMetadata(ReflectParamIndex.Dispose, l) || []; n.push(e); defineCustomMetadata(ReflectParamIndex.Dispose, n, m); C.push(d[e]); continue; } if (D.Use.includes(e)) { const t = getOwnCustomMetadata(ReflectParamValue.UseIdentifier, l); decorateUse(t[e], m, e); continue; } if (D.Global.includes(e)) { const t = getOwnCustomMetadata(ReflectParamValue.GlobalIdentifier, l); decorateGlobal(t[e], m, e); continue; } } return decorateAssemblage(m, getOwnCustomMetadata(ReflectValue.AssemblageDefinition, l)); }; const resolveInjectableParameters = (i)=>{ const s = []; const c = getParamTypes(i.concrete); const r = getDecoratedParametersIndexes(i.concrete); let u = 0; for (const n of c){ if (r.Context.includes(u)) { s.push(i.publicContext); u++; continue; } if (r.Configuration.includes(u)) { s.push(i.configuration); u++; continue; } if (r.Definition.includes(u)) { s.push(i.definition); u++; continue; } if (r.Dispose.includes(u)) { s.push(i.privateContext.dispose); u++; continue; } if (r.Use.includes(u)) { const n = getOwnCustomMetadata(ReflectParamValue.UseIdentifier, i.concrete); const t = n[u]; s.push(i.privateContext.require(t)); u++; continue; } if (r.Global.includes(u)) { const n = getOwnCustomMetadata(ReflectParamValue.GlobalIdentifier, i.concrete); const t = n[u]; s.push(i.privateContext.global(t)); u++; continue; } s.push(i.privateContext.require(n)); u++; } return s; }; const resolveDependencies = (e)=>{ const o = []; const i = getParamTypes(e); const s = getDecoratedParametersIndexes(e); let c = 0; for (const e of i){ if (s.Context.includes(c) || s.Configuration.includes(c) || s.Definition.includes(c) || s.Dispose.includes(c) || s.Use.includes(c) || s.Global.includes(c)) { c++; continue; } o.push(e); c++; } return o; }; class Injectable { static of(t, e, n) { return new Injectable(t, e, n); } dispose() { if (this.singletonInstance) { unregisterEvents(this, this.singletonInstance); callHook(this.singletonInstance, 'onDispose', this.publicContext, this.configuration); clearInstance(this.singletonInstance, this.concrete); } clearInstance(this, Injectable); } build(t) { if (this.singletonInstance) return this.singletonInstance; const e = resolveInjectableParameters(this); const n = new this.concrete(...e); registerEvents(this, n); if (this.isSingleton) { this.singletonInstance = n; this.privateContext.prepareInitHook(n, this.configuration); return this.singletonInstance; } let i = {}; if (this.configuration) { i = this.configuration; } if (t) { i = { ...i, ...t }; } callHook(n, 'onInit', this.publicContext, i); return n; } get dependencies() { return this.dependenciesIds; } get definition() { return getDefinition(this.concrete) || {}; } get isSingleton() { return getDefinitionValue('singleton', this.concrete); } get singleton() { return this.singletonInstance; } get injections() { return getDefinitionValue('inject', this.concrete) || []; } get objects() { return getDefinitionValue('use', this.concrete) || []; } get tags() { return getDefinitionValue('tags', this.concrete) || []; } get globals() { return getDefinitionValue('global', this.concrete); } get events() { return getDefinitionValue('events', this.concrete) || []; } constructor(t, o, r){ this.privateContext = o; this.publicContext = r; this.dependenciesIds = []; this.identifier = t.identifier; this.concrete = t.concrete; this.configuration = t.configuration; if (!isAssemblage(this.concrete)) { throw new Error(`Class '${this.concrete.name}' is not an Assemblage.`); } defineCustomMetadata(ReflectValue.AssemblageContext, this.publicContext, this.concrete); const c = forOf(this.injections); const h = forOf(this.objects); c((t)=>this.privateContext.register(t)); h((t)=>{ if (typeof t[0] === 'string' || typeof t[0] === 'symbol') { this.privateContext.use(t[0], t[1]); } else { this.privateContext.register(t, true); } }); this.dependenciesIds = resolveDependencies(this.concrete); if (this.globals) { for(const t in this.globals){ this.privateContext.addGlobal(t, this.globals[t]); } } if (t.instance) { this.singletonInstance = t.instance; } else if (this.isSingleton) ; } } const callHook = (e, o, n, t)=>{ return new Promise((i)=>{ const s = e[o]; if (s) { if (isAsync(s)) { s.bind(e)(n, t).then(()=>{ i(); }); return; } i(s.bind(e)(n, t)); } }); }; class Assembler extends EventManager { static build(e) { const i = new Assembler(); setDefinitionValue('singleton', true, e); const s = i.register([ e ]); const n = i.require(s.identifier); const r = i.initCache.find((e)=>e.instance === n); if (!r) { throw new Error('Root instance not found in assemblages cache.'); } const h = i.initCache.indexOf(r); i.initCache.splice(h, 1); for (const e of i.initCache){ callHook(e.instance, 'onInit', i.publicContext, e.configuration); } callHook(n, 'onInit', i.publicContext, s.configuration); for (const e of i.initCache.reverse()){ callHook(e.instance, 'onInited', i.publicContext, e.configuration); } callHook(n, 'onInited', i.publicContext, s.configuration); i.initCache.length = 0; return n; } dispose() { for (const [e, t] of this.injectables){ t.dispose(); } clearInstance(this, Assembler); } register(e, t = false) { const r = t === true ? resolveInstanceInjectionTuple(e) : resolveInjectionTuple(e); if (this.has(r.identifier)) { throw new Error(`An assemblage is already registered with identifier '${r.identifier.name}'.`); } const h = Injectable.of(r, this.privateContext, this.publicContext); this.injectables.set(h.identifier, h); callHook(h.concrete, 'onRegister', this.publicContext, h.configuration); return h; } use(e, t) { if (this.has(e)) { throw new Error(`A value is already registered with identifier '${String(e)}'.`); } this.objects.set(e, t); return t; } prepareInitHook(e, t) { this.initCache.push({ instance: e, configuration: t }); return this.initCache; } has(e) { if (typeof e === 'string' || typeof e === 'symbol') { return this.objects.has(e); } return this.injectables.has(e); } require(e, t) { switch(typeof e){ case 'string': case 'symbol': { if (!this.objects.has(e)) { throw new Error(`Injected object with identifier '${String(e)}' has not been registered.`); } return this.objects.get(e); } default: { if (!this.injectables.has(e)) { throw new Error(`Class with identifier '${e.name}' has not been registered or is a circular dependency.`); } const i = this.injectables.get(e); const s = i.build(t); return s; } } } concrete(e) { const t = this.injectables.get(e); if (t) return t.concrete; return; } tagged(...e) { const t = []; for (const i of e){ for (const [e, s] of this.injectables){ if (s.tags.includes(i)) t.push(s.build()); } } return t; } addGlobal(e, t) { if (this.globals.has(e)) { throw new Error(`Global value with key '${e}' has already been registered.`); } this.globals.set(e, t); } global(e) { return this.globals.get(e); } get size() { return this.injectables.size; } constructor(){ super(); this.injectables = new Map(); this.objects = new Map(); this.globals = new Map(); this.initCache = []; this.publicContext = { has: this.has.bind(this), require: this.require.bind(this), concrete: this.concrete.bind(this), tagged: this.tagged.bind(this), dispose: this.dispose.bind(this), global: this.global.bind(this), on: this.on.bind(this), once: this.once.bind(this), off: this.off.bind(this), events: this.channels }; this.privateContext = { ...this.publicContext, register: this.register.bind(this), use: this.use.bind(this), addGlobal: this.addGlobal.bind(this), prepareInitHook: this.prepareInitHook.bind(this), emit: this.emit.bind(this), addChannels: this.addChannels.bind(this), removeChannels: this.removeChannels.bind(this) }; } } class AbstractAssembler extends AbstractEventManager { } export { AbstractAssemblage, AbstractAssembler, AbstractEventManager, AbstractListenerCollection, Assemblage, Assembler, Await, r$1 as Configuration, ConstructorDecorator, s$1 as Context, c$1 as Definition, e$1 as Dispose, EventManager, Global, ListenerCollection, ReflectParamIndex, ReflectParamValue, Use, createConstructorDecorator, decorateAssemblage, decorateGlobal, decorateUse, getAssemblageContext, getAssemblageDefinition, getDecoratedParametersIndexes, isAssemblage };