@teqfw/di 0.12.1 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.mjs +0 -0
- package/README.md +27 -0
- package/RELEASE.md +11 -1
- package/bin/release/clean.sh +2 -1
- package/dist/di.cjs.js +205 -0
- package/dist/di.esm.js +206 -0
- package/index.cjs +5 -0
- package/index.mjs +6 -0
- package/package.json +14 -4
- package/src/Api/ObjectKey.js +35 -0
- package/src/Composer.js +68 -0
- package/src/Container.js +142 -0
- package/src/Defs.js +25 -0
- package/src/DepId/Parser.mjs +68 -0
- package/src/Parser/Def.js +63 -0
- package/src/Parser/Old.js +108 -0
- package/src/Parser.js +65 -0
- package/src/PreProcessor/Replace.js +48 -0
- package/src/PreProcessor.js +45 -0
- package/src/Resolver.js +65 -0
- package/src/Spec/Parser.mjs +101 -0
- package/src/SpecAnalyser.js +82 -0
- package/teqfw.json +2 -1
- package/webpack.config.mjs +15 -0
- package/src/Back/Api/Dto/Plugin/Desc.mjs +0 -70
- package/src/Back/Api/Dto/Scanned.mjs +0 -12
- package/src/Back/Api/README.md +0 -1
- package/src/Back/Defaults.mjs +0 -11
- package/src/Back/Plugin/Scanner.mjs +0 -154
- package/src/Back/README.md +0 -1
- package/src/Shared/Api/Dto/Plugin/Desc/Autoload.mjs +0 -50
- package/src/Shared/Api/IProxy.mjs +0 -11
- package/src/Shared/Container.mjs +0 -333
- package/src/Shared/IdParser/Dto.mjs +0 -96
- package/src/Shared/IdParser.mjs +0 -187
- package/src/Shared/ModuleLoader.mjs +0 -38
- package/src/Shared/README.md +0 -1
- package/src/Shared/Resolver/FilepathNs.mjs +0 -51
- package/src/Shared/Resolver/LogicalNs.mjs +0 -136
- package/src/Shared/Resolver.mjs +0 -74
- package/src/Shared/SpecProxy.mjs +0 -110
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teqfw/di",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "Dependency Injection container based on logical namespaces for ES6 modules.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dependency injection",
|
|
7
7
|
"di",
|
|
8
8
|
"dynamic import",
|
|
9
|
-
"
|
|
10
|
-
"es6+",
|
|
9
|
+
"es6 modules",
|
|
11
10
|
"teqfw",
|
|
12
11
|
"tequila framework"
|
|
13
12
|
],
|
|
@@ -21,10 +20,21 @@
|
|
|
21
20
|
"email": "alex@flancer64.com",
|
|
22
21
|
"url": "https://github.com/flancer64"
|
|
23
22
|
},
|
|
24
|
-
"main": "src/
|
|
23
|
+
"main": "src/Container.js",
|
|
25
24
|
"type": "module",
|
|
26
25
|
"repository": {
|
|
27
26
|
"type": "git",
|
|
28
27
|
"url": "git+https://github.com/teqfw/di.git"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "rm -fr ./dist/ && webpack build"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"babel-eslint": "*",
|
|
34
|
+
"eslint": "*",
|
|
35
|
+
"esm": "*",
|
|
36
|
+
"mocha": "*",
|
|
37
|
+
"webpack": "^5.88.1",
|
|
38
|
+
"webpack-cli": "^5.1.4"
|
|
29
39
|
}
|
|
30
40
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a DTO that represents the structure of an ID for a runtime dependency.
|
|
3
|
+
*/
|
|
4
|
+
export default class TeqFw_Di_Api_ObjectKey {
|
|
5
|
+
/**
|
|
6
|
+
* The name of an export of the module.
|
|
7
|
+
* @type {string}
|
|
8
|
+
*/
|
|
9
|
+
exportName;
|
|
10
|
+
/**
|
|
11
|
+
* Composition type (see Defs.COMPOSE_): use the export as Factory (F) or return as-is (A).
|
|
12
|
+
* @type {string}
|
|
13
|
+
*/
|
|
14
|
+
composition;
|
|
15
|
+
/**
|
|
16
|
+
* Lifestyle type (see Defs.LIFE_): singleton (S) or instance (I).
|
|
17
|
+
* @type {string}
|
|
18
|
+
*/
|
|
19
|
+
life;
|
|
20
|
+
/**
|
|
21
|
+
* The code for ES6 module that can be converted to the path to this es6 module.
|
|
22
|
+
* @type {string}
|
|
23
|
+
*/
|
|
24
|
+
moduleName;
|
|
25
|
+
/**
|
|
26
|
+
* Object key value.
|
|
27
|
+
* @type {string}
|
|
28
|
+
*/
|
|
29
|
+
value;
|
|
30
|
+
/**
|
|
31
|
+
* List of wrappers to decorate the result.
|
|
32
|
+
* @type {string[]}
|
|
33
|
+
*/
|
|
34
|
+
wrappers = [];
|
|
35
|
+
}
|
package/src/Composer.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
*/
|
|
4
|
+
import Defs from './Defs.js';
|
|
5
|
+
import specAnalyser from './SpecAnalyser.js';
|
|
6
|
+
|
|
7
|
+
// FUNCS
|
|
8
|
+
|
|
9
|
+
// MAIN
|
|
10
|
+
export default class TeqFw_Di_Composer {
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
// VARS
|
|
14
|
+
let _debug = false;
|
|
15
|
+
|
|
16
|
+
// FUNCS
|
|
17
|
+
function log(msg) {
|
|
18
|
+
if (_debug) console.log(msg);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// INSTANCE METHODS
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @param {TeqFw_Di_Api_ObjectKey} key
|
|
26
|
+
* @param {Object} module
|
|
27
|
+
* @param {string[]} stack array of the parent objects to prevent dependency loop
|
|
28
|
+
* @param {TeqFw_Di_Container} container
|
|
29
|
+
* @return {Promise<*>}
|
|
30
|
+
*/
|
|
31
|
+
this.create = async function (key, module, stack, container) {
|
|
32
|
+
if (stack.includes(key.value))
|
|
33
|
+
throw new Error(`Circular dependency for '${key.value}'. Parents are: ${JSON.stringify(stack)}`);
|
|
34
|
+
if (key.exportName) {
|
|
35
|
+
// use export from the es6-module
|
|
36
|
+
const stackNew = [...stack, key.value];
|
|
37
|
+
const {[key.exportName]: exp} = module;
|
|
38
|
+
if (key.composition === Defs.COMPOSE_FACTORY) {
|
|
39
|
+
if (typeof exp === 'function') {
|
|
40
|
+
// create deps for factory function
|
|
41
|
+
const deps = specAnalyser(exp);
|
|
42
|
+
if (deps.length) log(`Deps for object '${key.value}' are: ${JSON.stringify(deps)}`);
|
|
43
|
+
const spec = {};
|
|
44
|
+
for (const dep of deps)
|
|
45
|
+
spec[dep] = await container.get(dep, stackNew);
|
|
46
|
+
// create a new object with the factory function
|
|
47
|
+
const res = (Defs.isClass(exp)) ? new exp(spec) : exp(spec);
|
|
48
|
+
if (res instanceof Promise)
|
|
49
|
+
return await res;
|
|
50
|
+
else
|
|
51
|
+
return res;
|
|
52
|
+
} else
|
|
53
|
+
// just clone the export
|
|
54
|
+
return Object.assign({}, exp);
|
|
55
|
+
} else
|
|
56
|
+
return exp;
|
|
57
|
+
} else {
|
|
58
|
+
return module;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
this.setDebug = function (data) {
|
|
63
|
+
_debug = data;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// MAIN
|
|
67
|
+
}
|
|
68
|
+
};
|
package/src/Container.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Object Container (composition root).
|
|
3
|
+
*/
|
|
4
|
+
import Composer from './Composer.js';
|
|
5
|
+
import Defs from './Defs.js';
|
|
6
|
+
import Parser from './Parser.js';
|
|
7
|
+
import PreProcessor from './PreProcessor.js';
|
|
8
|
+
import NewReplace from './PreProcessor/Replace.js';
|
|
9
|
+
import Resolver from './Resolver.js';
|
|
10
|
+
|
|
11
|
+
// VARS
|
|
12
|
+
|
|
13
|
+
// FUNCS
|
|
14
|
+
/**
|
|
15
|
+
* ID to store singletons in the internal registry.
|
|
16
|
+
* @param {TeqFw_Di_Api_ObjectKey} key
|
|
17
|
+
* @return {string}
|
|
18
|
+
*/
|
|
19
|
+
function getSingletonId(key) {
|
|
20
|
+
return `${key.moduleName}#${key.exportName}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// MAIN
|
|
24
|
+
export default class TeqFw_Di_Container {
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
// VARS
|
|
28
|
+
let _composer = new Composer();
|
|
29
|
+
let _debug = false;
|
|
30
|
+
let _parser = new Parser();
|
|
31
|
+
let _preProcessor = new PreProcessor();
|
|
32
|
+
_preProcessor.addHandler(NewReplace()); // create new instance of the replacement handler
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Registry for loaded es6 modules.
|
|
36
|
+
* @type {Object<string, Module>}
|
|
37
|
+
*/
|
|
38
|
+
const _regModules = {};
|
|
39
|
+
/**
|
|
40
|
+
* Registry to store singletons.
|
|
41
|
+
* @type {Object<string, *>}
|
|
42
|
+
*/
|
|
43
|
+
const _regSingles = {};
|
|
44
|
+
let _resolver = new Resolver();
|
|
45
|
+
|
|
46
|
+
// FUNCS
|
|
47
|
+
function error() {
|
|
48
|
+
console.error(...arguments);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function log() {
|
|
52
|
+
if (_debug) console.log(...arguments);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// INSTANCE METHODS
|
|
57
|
+
|
|
58
|
+
this.get = async function (objectKey, stack = []) {
|
|
59
|
+
log(`Object '${objectKey}' is requested.`);
|
|
60
|
+
// return container itself if requested
|
|
61
|
+
if (
|
|
62
|
+
(objectKey === Defs.KEY_CONTAINER) ||
|
|
63
|
+
(objectKey === Defs.KEY_CONTAINER_NS)
|
|
64
|
+
) {
|
|
65
|
+
log(`Container itself is returned.`);
|
|
66
|
+
return _regSingles[Defs.KEY_CONTAINER];
|
|
67
|
+
}
|
|
68
|
+
// parse the `objectKey` and get the structured DTO
|
|
69
|
+
const parsed = _parser.parse(objectKey);
|
|
70
|
+
// modify original key according to some rules (replacements, etc.)
|
|
71
|
+
const key = _preProcessor.process(parsed);
|
|
72
|
+
// return existing singleton
|
|
73
|
+
if (key.life === Defs.LIFE_SINGLETON) {
|
|
74
|
+
const singleId = getSingletonId(key);
|
|
75
|
+
if (_regSingles[singleId]) {
|
|
76
|
+
log(`Existing singleton '${singleId}' is returned.`);
|
|
77
|
+
return _regSingles[singleId];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// load es6 module if not loaded before
|
|
81
|
+
if (!_regModules[key.moduleName]) {
|
|
82
|
+
log(`ES6 module '${key.moduleName}' is not loaded yet`);
|
|
83
|
+
// convert module name to the path to es6-module file with a sources
|
|
84
|
+
const path = _resolver.resolve(key.moduleName);
|
|
85
|
+
try {
|
|
86
|
+
_regModules[key.moduleName] = await import(path);
|
|
87
|
+
log(`ES6 module '${key.moduleName}' is loaded from '${path}'.`);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error(
|
|
90
|
+
e?.message,
|
|
91
|
+
`Object key: "${objectKey}".`,
|
|
92
|
+
`Path: "${path}".`,
|
|
93
|
+
`Stack: ${JSON.stringify(stack)}`
|
|
94
|
+
);
|
|
95
|
+
throw e;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
// create object using the composer
|
|
100
|
+
let res = await _composer.create(key, _regModules[key.moduleName], stack, this);
|
|
101
|
+
log(`Object '${objectKey}' is created.`);
|
|
102
|
+
|
|
103
|
+
// TODO: refactor this code to use wrappers w/o hardcode
|
|
104
|
+
if (key.wrappers.includes(Defs.WRAP_PROXY)) {
|
|
105
|
+
const me = this;
|
|
106
|
+
res = new Proxy({dep: undefined, objectKey}, {
|
|
107
|
+
get: async function (base, name) {
|
|
108
|
+
if (name === 'create') base.dep = await me.get(base.objectKey);
|
|
109
|
+
return base.dep;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (key.life === Defs.LIFE_SINGLETON) {
|
|
115
|
+
const singleId = getSingletonId(key);
|
|
116
|
+
_regSingles[singleId] = res;
|
|
117
|
+
log(`Object '${objectKey}' is saved as singleton.`);
|
|
118
|
+
}
|
|
119
|
+
return res;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
this.getParser = () => _parser;
|
|
123
|
+
|
|
124
|
+
this.getPreProcessor = () => _preProcessor
|
|
125
|
+
;
|
|
126
|
+
this.getResolver = () => _resolver;
|
|
127
|
+
|
|
128
|
+
this.setDebug = function (data) {
|
|
129
|
+
_debug = data;
|
|
130
|
+
_composer.setDebug(data);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
this.setParser = (data) => _parser = data;
|
|
134
|
+
|
|
135
|
+
this.setPreProcessor = (data) => _preProcessor = data;
|
|
136
|
+
|
|
137
|
+
this.setResolver = (data) => _resolver = data;
|
|
138
|
+
|
|
139
|
+
// MAIN
|
|
140
|
+
_regSingles[Defs.KEY_CONTAINER] = this;
|
|
141
|
+
}
|
|
142
|
+
};
|
package/src/Defs.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardcoded constants for the package.
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
COMPOSE_AS_IS: 'A',
|
|
6
|
+
COMPOSE_FACTORY: 'F',
|
|
7
|
+
EXT: 'js',
|
|
8
|
+
KEY_CONTAINER: 'container',
|
|
9
|
+
KEY_CONTAINER_NS: 'TeqFw_Di_Container$',
|
|
10
|
+
LIFE_INSTANCE: 'I',
|
|
11
|
+
LIFE_SINGLETON: 'S',
|
|
12
|
+
WRAP_PROXY: 'proxy',
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Return 'true' if function is a class definition.
|
|
16
|
+
* See: https://stackoverflow.com/a/29094018/4073821
|
|
17
|
+
*
|
|
18
|
+
* @param {function} fn
|
|
19
|
+
* @return {boolean}
|
|
20
|
+
*/
|
|
21
|
+
isClass(fn) {
|
|
22
|
+
const proto = Object.getOwnPropertyDescriptor(fn, 'prototype');
|
|
23
|
+
return proto && !proto.writable;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is default parser that converts dependency ID to the `depId` DTO.
|
|
3
|
+
* @namespace ObjectKey.Parser
|
|
4
|
+
*/
|
|
5
|
+
// IMPORTS
|
|
6
|
+
import {ObjectKey} from '../Api/ObjectKey.mjs';
|
|
7
|
+
|
|
8
|
+
// VARS
|
|
9
|
+
/** @type {RegExp} expression for depId (Ns_Module.export$$#adapter) */
|
|
10
|
+
const TMPL = /^(([A-Z])[A-Za-z0-9_]*)((.)([A-Za-z0-9_]*)?((\$|\$\$)?((#)([A-Za-z0-9_]*)?)?)?)?$/;
|
|
11
|
+
|
|
12
|
+
// FUNCS
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} depId
|
|
15
|
+
* @return {Api.ObjectKey}
|
|
16
|
+
*/
|
|
17
|
+
export default function (depId) {
|
|
18
|
+
let res;
|
|
19
|
+
const parts = TMPL.exec(depId);
|
|
20
|
+
if (parts) {
|
|
21
|
+
const nameMod = parts[1];
|
|
22
|
+
const sepMod = parts[4]; // after-module separator
|
|
23
|
+
const sepExp = parts[6]; // after-export separator
|
|
24
|
+
const sepLife = parts[9]; // after-life separator
|
|
25
|
+
|
|
26
|
+
res = new ObjectKey();
|
|
27
|
+
// always presents
|
|
28
|
+
res.nameModule = nameMod;
|
|
29
|
+
if (sepLife === '#') {
|
|
30
|
+
// the longest form of the ID (Mod.exp$#adp)
|
|
31
|
+
res.adapter = parts[10] ?? 'default';
|
|
32
|
+
res.isExport = true;
|
|
33
|
+
res.isFactory = parts[7] !== undefined;
|
|
34
|
+
if (parts[7] === '$') res.isSingleton = true;
|
|
35
|
+
else if (parts[7] === '$$') res.isSingleton = false;
|
|
36
|
+
res.nameExport = parts[5];
|
|
37
|
+
} else if (sepMod === '#') {
|
|
38
|
+
// Mod#adp
|
|
39
|
+
res.adapter = parts[5] ?? 'default';
|
|
40
|
+
res.isExport = true;
|
|
41
|
+
res.isFactory = false;
|
|
42
|
+
res.nameExport = 'default';
|
|
43
|
+
} else if ((sepExp === '$') || (sepExp === '$$')) {
|
|
44
|
+
if (sepMod === '.') {
|
|
45
|
+
// Mod.exp$$
|
|
46
|
+
res.isExport = true;
|
|
47
|
+
res.isFactory = true;
|
|
48
|
+
if (parts[7] === '$') res.isSingleton = true;
|
|
49
|
+
else if (parts[7] === '$$') res.isSingleton = false;
|
|
50
|
+
res.nameExport = parts[5];
|
|
51
|
+
} else if (sepMod === '$') {
|
|
52
|
+
// Mod$$
|
|
53
|
+
res.isExport = true;
|
|
54
|
+
res.isFactory = true;
|
|
55
|
+
if (parts[3] === '$') res.isSingleton = true;
|
|
56
|
+
else if (parts[3] === '$$') res.isSingleton = false;
|
|
57
|
+
res.nameExport = 'default';
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
// Mod.exp
|
|
61
|
+
res.isFactory = false;
|
|
62
|
+
res.isExport = (sepMod === '.');
|
|
63
|
+
// define the name of the export
|
|
64
|
+
if (res.isExport) res.nameExport = parts[5] ?? 'default';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return res;
|
|
68
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default parser for object keys in format:
|
|
3
|
+
* - Vnd_Pkg_Prj_Mod$FA
|
|
4
|
+
*/
|
|
5
|
+
import Dto from '../Api/ObjectKey.js';
|
|
6
|
+
import Defs from '../Defs.js';
|
|
7
|
+
|
|
8
|
+
// VARS
|
|
9
|
+
/** @type {RegExp} expression for default object key (Ns_Module[.|#]export$[F|A][S|I]) */
|
|
10
|
+
const REGEXP = /^((([A-Z])[A-Za-z0-9_]*)((#|\.)?([A-Za-z0-9]*)((\$)([F|A])?([S|I])?)?)?)$/;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// MAIN
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} objectKey
|
|
16
|
+
* @return {TeqFw_Di_Api_ObjectKey}
|
|
17
|
+
*/
|
|
18
|
+
export default function TeqFw_Di_Parser_Def(objectKey) {
|
|
19
|
+
const res = new Dto();
|
|
20
|
+
res.value = objectKey;
|
|
21
|
+
const parts = REGEXP.exec(objectKey);
|
|
22
|
+
if (parts) {
|
|
23
|
+
res.moduleName = parts[2];
|
|
24
|
+
if (parts[5] === '.') {
|
|
25
|
+
// App_Service.export...
|
|
26
|
+
if (parts[8] === '$') {
|
|
27
|
+
// App_Service.export$...
|
|
28
|
+
res.composition = Defs.COMPOSE_FACTORY;
|
|
29
|
+
res.exportName = parts[6];
|
|
30
|
+
res.life = (parts[10] === Defs.LIFE_INSTANCE)
|
|
31
|
+
? Defs.LIFE_INSTANCE : Defs.LIFE_SINGLETON;
|
|
32
|
+
} else {
|
|
33
|
+
res.composition = ((parts[8] === undefined) || (parts[8] === Defs.COMPOSE_AS_IS))
|
|
34
|
+
? Defs.COMPOSE_AS_IS : Defs.COMPOSE_FACTORY;
|
|
35
|
+
res.exportName = parts[6];
|
|
36
|
+
res.life = ((parts[8] === undefined) || (parts[10] === Defs.LIFE_SINGLETON))
|
|
37
|
+
? Defs.LIFE_SINGLETON : Defs.LIFE_INSTANCE;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
} else if (parts[8] === '$') {
|
|
42
|
+
// App_Logger$FS
|
|
43
|
+
res.composition = ((parts[9] === undefined) || (parts[9] === Defs.COMPOSE_FACTORY))
|
|
44
|
+
? Defs.COMPOSE_FACTORY : Defs.COMPOSE_AS_IS;
|
|
45
|
+
res.exportName = 'default';
|
|
46
|
+
if (parts[10]) {
|
|
47
|
+
res.life = (parts[10] === Defs.LIFE_SINGLETON) ? Defs.LIFE_SINGLETON : Defs.LIFE_INSTANCE;
|
|
48
|
+
} else {
|
|
49
|
+
res.life = (res.composition === Defs.COMPOSE_FACTORY) ? Defs.LIFE_SINGLETON : Defs.LIFE_INSTANCE;
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// App_Service
|
|
53
|
+
res.composition = Defs.COMPOSE_AS_IS;
|
|
54
|
+
res.exportName = 'default';
|
|
55
|
+
res.life = Defs.LIFE_SINGLETON;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// we should always use singletons for as-is exports
|
|
60
|
+
if ((res.composition === Defs.COMPOSE_AS_IS) && (res.life === Defs.LIFE_INSTANCE))
|
|
61
|
+
throw new Error(`Export is not a function and should be used as a singleton only: '${res.value}'.`);
|
|
62
|
+
return res;
|
|
63
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for object keys in old format:
|
|
3
|
+
* - Vnd_Pkg_Prj_Mod[.|#]export$$
|
|
4
|
+
*/
|
|
5
|
+
import Dto from '../Api/ObjectKey.js';
|
|
6
|
+
import Defs from '../Defs.js';
|
|
7
|
+
|
|
8
|
+
// VARS
|
|
9
|
+
/** @type {string} default export keyword */
|
|
10
|
+
const DEF_EXP = 'default';
|
|
11
|
+
/** @type {string} logical namespace export mark (Ns_Mod.export) */
|
|
12
|
+
const EXP = '.';
|
|
13
|
+
/** @type {string} filesystem export mark (@vendor/package!module#export$$) and old logical export mark */
|
|
14
|
+
const EXP_OLD = '#';
|
|
15
|
+
/** @type {string} new instance mark (Ns_Mod.export$$) */
|
|
16
|
+
const INST = '$$';
|
|
17
|
+
/** @type {RegExp} expression for logical namespace IDs (Ns_Module[.|#]export$$@@) */
|
|
18
|
+
const REGEXP = /^((([A-Z])[A-Za-z0-9_]*)((#|.)?([A-Za-z0-9_]*)(\${1,2}|@{1,2})?)?)$/;
|
|
19
|
+
/** @type {RegExp} expression for objects that manually added to DI container (singleton, namedFactory$$) */
|
|
20
|
+
const MANUAL = /^((([a-z])[A-Za-z0-9_]*)(\$\$)?)$/s;
|
|
21
|
+
/** @type {string} new instance proxy mark (Ns_Mod.export@@) */
|
|
22
|
+
const P_INST = '@@';
|
|
23
|
+
/** @type {string} singleton proxy mark (Ns_Mod.export@) */
|
|
24
|
+
const P_SNGLT = '@';
|
|
25
|
+
/** @type {string} singleton mark (Ns_Mod.export$) */
|
|
26
|
+
const SNGLT = '$';
|
|
27
|
+
|
|
28
|
+
// MAIN
|
|
29
|
+
export default function TeqFw_Di_Parser_Old(objectKey) {
|
|
30
|
+
const res = new Dto();
|
|
31
|
+
res.value = objectKey;
|
|
32
|
+
const parts = REGEXP.exec(objectKey);
|
|
33
|
+
if (parts) {
|
|
34
|
+
res.moduleName = parts[2];
|
|
35
|
+
// Ns_Module.name$$[@@] - named instance [proxy]
|
|
36
|
+
if (
|
|
37
|
+
((parts[5] === EXP) || (parts[5] === EXP_OLD))
|
|
38
|
+
&& ((parts[7] === INST) || (parts[7] === P_INST))
|
|
39
|
+
) {
|
|
40
|
+
if (parts[7] === P_INST)
|
|
41
|
+
res.wrappers.push(Defs.WRAP_PROXY);
|
|
42
|
+
res.composition = Defs.COMPOSE_FACTORY;
|
|
43
|
+
res.life = Defs.LIFE_INSTANCE;
|
|
44
|
+
res.exportName = parts[6];
|
|
45
|
+
}
|
|
46
|
+
// Ns_Module.name$[@] - named singleton [proxy]
|
|
47
|
+
else if (
|
|
48
|
+
((parts[5] === EXP) || (parts[5] === EXP_OLD))
|
|
49
|
+
&& ((parts[7] === SNGLT) || (parts[7] === P_SNGLT))
|
|
50
|
+
) {
|
|
51
|
+
if (parts[7] === P_SNGLT)
|
|
52
|
+
res.wrappers.push(Defs.WRAP_PROXY);
|
|
53
|
+
res.composition = Defs.COMPOSE_FACTORY;
|
|
54
|
+
res.life = Defs.LIFE_SINGLETON;
|
|
55
|
+
res.exportName = parts[6];
|
|
56
|
+
}
|
|
57
|
+
// Ns_Module.name - named export
|
|
58
|
+
else if (
|
|
59
|
+
((parts[5] === EXP) || (parts[5] === EXP_OLD))
|
|
60
|
+
&& ((parts[6] !== undefined) && (parts[6] !== ''))
|
|
61
|
+
) {
|
|
62
|
+
res.composition = Defs.COMPOSE_AS_IS;
|
|
63
|
+
res.exportName = parts[6];
|
|
64
|
+
res.life = Defs.LIFE_SINGLETON;
|
|
65
|
+
}
|
|
66
|
+
// Ns_Module$$[@@]- default instance [proxy]
|
|
67
|
+
else if ((parts[4] === INST) || (parts[4] === P_INST)) {
|
|
68
|
+
if (parts[4] === P_INST)
|
|
69
|
+
res.wrappers.push(Defs.WRAP_PROXY);
|
|
70
|
+
res.composition = Defs.COMPOSE_FACTORY;
|
|
71
|
+
res.life = Defs.LIFE_INSTANCE;
|
|
72
|
+
res.exportName = DEF_EXP;
|
|
73
|
+
}
|
|
74
|
+
// Ns_Module$[@] - default singleton [proxy]
|
|
75
|
+
else if ((parts[4] === SNGLT) || (parts[4] === P_SNGLT)) {
|
|
76
|
+
if (parts[4] === P_SNGLT)
|
|
77
|
+
res.wrappers.push(Defs.WRAP_PROXY);
|
|
78
|
+
res.composition = Defs.COMPOSE_FACTORY;
|
|
79
|
+
res.life = Defs.LIFE_SINGLETON;
|
|
80
|
+
res.exportName = DEF_EXP;
|
|
81
|
+
}
|
|
82
|
+
// Ns_Module#[.] - default export
|
|
83
|
+
else if (
|
|
84
|
+
((parts[5] === EXP) || (parts[5] === EXP_OLD))
|
|
85
|
+
&& (parts[7] === undefined)
|
|
86
|
+
) {
|
|
87
|
+
res.composition = Defs.COMPOSE_AS_IS;
|
|
88
|
+
res.life = Defs.LIFE_SINGLETON;
|
|
89
|
+
res.exportName = DEF_EXP;
|
|
90
|
+
} else {
|
|
91
|
+
// just a es6-module (deprecated)
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
const manual = MANUAL.exec(objectKey);
|
|
96
|
+
if (manual) {
|
|
97
|
+
if (manual[4] === '$$') {
|
|
98
|
+
res.composition = Defs.COMPOSE_FACTORY;
|
|
99
|
+
res.life = Defs.LIFE_INSTANCE;
|
|
100
|
+
} else {
|
|
101
|
+
res.life = Defs.LIFE_SINGLETON;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// TODO: add exception
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return res;
|
|
108
|
+
}
|
package/src/Parser.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The root parser for `objectKeys` contains all other parsers.
|
|
3
|
+
* It calls the other parser one by one to parse the object key as a structure.
|
|
4
|
+
* Every npm package can have its own format for an `objectKey`.
|
|
5
|
+
*/
|
|
6
|
+
import defaultParser from './Parser/Def.js';
|
|
7
|
+
|
|
8
|
+
// VARS
|
|
9
|
+
const KEY_PARSER = 'parser';
|
|
10
|
+
const KEY_VALIDATOR = 'validator';
|
|
11
|
+
|
|
12
|
+
// MAIN
|
|
13
|
+
export default class TeqFw_Di_Parser {
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
// VARS
|
|
17
|
+
/**
|
|
18
|
+
* Default parsing function.
|
|
19
|
+
* @type {(function(string): TeqFw_Di_Api_ObjectKey)}
|
|
20
|
+
*/
|
|
21
|
+
let _defaultParser = defaultParser;
|
|
22
|
+
/**
|
|
23
|
+
* The array of the pairs {validator, parser} to parse objectKeys.
|
|
24
|
+
* @type {Object<validator:function, parser:function>[]}
|
|
25
|
+
*/
|
|
26
|
+
const _parsers = [];
|
|
27
|
+
|
|
28
|
+
// INSTANCE METHODS
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param {function(string):boolean} validator
|
|
33
|
+
* @param {function(string):TeqFw_Di_Api_ObjectKey} parser
|
|
34
|
+
*/
|
|
35
|
+
this.addParser = function (validator, parser) {
|
|
36
|
+
_parsers.push({[KEY_VALIDATOR]: validator, [KEY_PARSER]: parser});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} objectKey
|
|
41
|
+
* @return {TeqFw_Di_Api_ObjectKey}
|
|
42
|
+
*/
|
|
43
|
+
this.parse = function (objectKey) {
|
|
44
|
+
let res;
|
|
45
|
+
for (const one of _parsers) {
|
|
46
|
+
if (one[KEY_VALIDATOR](objectKey)) {
|
|
47
|
+
res = one[KEY_PARSER](objectKey);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!res)
|
|
52
|
+
res = _defaultParser(objectKey);
|
|
53
|
+
return res;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {function(string):TeqFw_Di_Api_ObjectKey} parser
|
|
58
|
+
*/
|
|
59
|
+
this.setDefaultParser = function (parser) {
|
|
60
|
+
_defaultParser = parser;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// MAIN
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-processor handler to replace one object key with another.
|
|
3
|
+
* @namespace TeqFw_Di_PreProcessor_Replace
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Factory function to create pre-processor handler.
|
|
8
|
+
* @return {function(*): *}
|
|
9
|
+
*/
|
|
10
|
+
export default function () {
|
|
11
|
+
// VARS
|
|
12
|
+
/**
|
|
13
|
+
* Storage for ES modules replacements (interface => implementation).
|
|
14
|
+
* Sample: {['Vnd_Plug_Interface']:'Vnd_Plug_Impl', ...}
|
|
15
|
+
* @type {Object<string, string>}
|
|
16
|
+
*/
|
|
17
|
+
const replacements = {};
|
|
18
|
+
|
|
19
|
+
// FUNCS
|
|
20
|
+
/**
|
|
21
|
+
* @param {TeqFw_Di_Api_ObjectKey} objectKey
|
|
22
|
+
* @param {TeqFw_Di_Api_ObjectKey} originalKey
|
|
23
|
+
* @return {TeqFw_Di_Api_ObjectKey}
|
|
24
|
+
*/
|
|
25
|
+
function TeqFw_Di_PreProcessor_Replace(objectKey, originalKey) {
|
|
26
|
+
let module = objectKey.moduleName;
|
|
27
|
+
while (replacements[module]) module = replacements[module];
|
|
28
|
+
if (module !== objectKey.moduleName) {
|
|
29
|
+
const res = Object.assign({}, objectKey);
|
|
30
|
+
res.moduleName = module;
|
|
31
|
+
return res;
|
|
32
|
+
} else
|
|
33
|
+
return objectKey;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Add replacement for ES6 modules.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} orig ('Vnd_Plug_Interface')
|
|
40
|
+
* @param {string} alter ('Vnd_Plug_Impl')
|
|
41
|
+
*/
|
|
42
|
+
TeqFw_Di_PreProcessor_Replace.add = function (orig, alter) {
|
|
43
|
+
replacements[orig] = alter;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// MAIN
|
|
47
|
+
return TeqFw_Di_PreProcessor_Replace;
|
|
48
|
+
}
|