@teqfw/di 1.3.0 → 2.0.0
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/CHANGELOG.md +8 -0
- package/README.md +173 -259
- package/dist/esm.js +1 -1
- package/dist/umd.js +1 -1
- package/package.json +16 -10
- package/src/AGENTS.md +177 -0
- package/src/Config/NamespaceRegistry.mjs +210 -0
- package/src/Container/Instantiate/ExportSelector.mjs +39 -0
- package/src/Container/Instantiate/Instantiator.mjs +143 -0
- package/src/Container/Lifecycle/Registry.mjs +81 -0
- package/src/Container/Resolve/GraphResolver.mjs +119 -0
- package/src/Container/Resolver.mjs +175 -0
- package/src/Container/Wrapper/Executor.mjs +71 -0
- package/src/Container.mjs +380 -0
- package/src/Def/Parser.mjs +146 -0
- package/src/Dto/DepId.mjs +131 -0
- package/src/Dto/Resolver/Config/Namespace.mjs +48 -0
- package/src/Dto/Resolver/Config.mjs +58 -0
- package/src/Enum/Composition.mjs +11 -0
- package/src/Enum/Life.mjs +11 -0
- package/src/Enum/Platform.mjs +12 -0
- package/src/Internal/Logger.mjs +54 -0
- package/types.d.ts +53 -26
- package/src/Api/Container/Config.js +0 -73
- package/src/Api/Container/Parser/Chunk.js +0 -27
- package/src/Api/Container/Parser.js +0 -34
- package/src/Api/Container/PostProcessor/Chunk.js +0 -17
- package/src/Api/Container/PostProcessor.js +0 -29
- package/src/Api/Container/PreProcessor/Chunk.js +0 -19
- package/src/Api/Container/PreProcessor.js +0 -27
- package/src/Api/Container/Resolver.js +0 -18
- package/src/Api/Container.js +0 -19
- package/src/Container/A/Composer/A/SpecParser.js +0 -86
- package/src/Container/A/Composer.js +0 -69
- package/src/Container/A/Parser/Chunk/Def.js +0 -69
- package/src/Container/A/Parser/Chunk/V02X.js +0 -66
- package/src/Container/Config.js +0 -93
- package/src/Container/Parser.js +0 -48
- package/src/Container/PostProcessor.js +0 -32
- package/src/Container/PreProcessor.js +0 -34
- package/src/Container/Resolver.js +0 -80
- package/src/Container.js +0 -187
- package/src/Defs.js +0 -22
- package/src/DepId.js +0 -52
- package/src/Pre/Replace.js +0 -80
- package/teqfw.json +0 -8
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The composer creates requested objects. It uses the container to create dependencies.
|
|
3
|
-
*
|
|
4
|
-
* @namespace TeqFw_Di_Container_A_Composer
|
|
5
|
-
*/
|
|
6
|
-
import Defs from '../../Defs.js';
|
|
7
|
-
import specParser from './Composer/A/SpecParser.js';
|
|
8
|
-
|
|
9
|
-
export default class TeqFw_Di_Container_A_Composer {
|
|
10
|
-
|
|
11
|
-
constructor() {
|
|
12
|
-
// VARS
|
|
13
|
-
let _debug = false;
|
|
14
|
-
|
|
15
|
-
// FUNCS
|
|
16
|
-
function log(msg) {
|
|
17
|
-
if (_debug) console.log(msg);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// INSTANCE METHODS
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Returns or creates and returns the requested object.
|
|
24
|
-
*
|
|
25
|
-
* @param {TeqFw_Di_DepId} depId
|
|
26
|
-
* @param {object} module - imported es6 module
|
|
27
|
-
* @param {string[]} stack - array of the parent objects IDs to prevent dependency loop
|
|
28
|
-
* @param {TeqFw_Di_Container} container - to create dependencies for requested object
|
|
29
|
-
* @returns {Promise<*>}
|
|
30
|
-
*/
|
|
31
|
-
this.create = async function (depId, module, stack, container) {
|
|
32
|
-
if (stack.includes(depId.origin))
|
|
33
|
-
throw new Error(`Circular dependency for '${depId.origin}'. Parents are: ${JSON.stringify(stack)}`);
|
|
34
|
-
if (depId.exportName) {
|
|
35
|
-
// use export from the es6-module
|
|
36
|
-
const stackNew = [...stack, depId.origin];
|
|
37
|
-
const {[depId.exportName]: exp} = module;
|
|
38
|
-
if (depId.composition === Defs.CF) {
|
|
39
|
-
if (typeof exp === 'function') {
|
|
40
|
-
// create deps for factory function
|
|
41
|
-
const deps = specParser(exp);
|
|
42
|
-
if (deps.length) log(`Deps for object '${depId.origin}' 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
|
-
// just return the export (w/o factory function)
|
|
57
|
-
return exp;
|
|
58
|
-
} else {
|
|
59
|
-
return module;
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
this.setDebug = function (data) {
|
|
64
|
-
_debug = data;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// MAIN
|
|
68
|
-
}
|
|
69
|
-
};
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default parser for object keys in format:
|
|
3
|
-
* - Ns_Module.export$$(post)
|
|
4
|
-
* - node:package.export$$(post)
|
|
5
|
-
* - node:@scope/package.export$$(post)
|
|
6
|
-
*
|
|
7
|
-
* @namespace TeqFw_Di_Container_A_Parser_Chunk_Def
|
|
8
|
-
*/
|
|
9
|
-
import Dto from '../../../../DepId.js';
|
|
10
|
-
import Defs from '../../../../Defs.js';
|
|
11
|
-
|
|
12
|
-
// VARS
|
|
13
|
-
/** @type {RegExp} expression for a default object key */
|
|
14
|
-
const REGEXP = /^(node:)?(@?[A-Za-z0-9_-]+\/?[A-Za-z0-9_-]*)(([.#])?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\(([A-Za-z0-9_,]*)\))?$/;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @implements TeqFw_Di_Api_Container_Parser_Chunk
|
|
18
|
-
*/
|
|
19
|
-
export default class TeqFw_Di_Container_A_Parser_Chunk_Def {
|
|
20
|
-
|
|
21
|
-
canParse() {
|
|
22
|
-
// the default parser always tries to parse the depId
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
parse(objectKey) {
|
|
27
|
-
const res = new Dto();
|
|
28
|
-
res.origin = objectKey;
|
|
29
|
-
const parts = REGEXP.exec(objectKey);
|
|
30
|
-
if (parts) {
|
|
31
|
-
res.isNodeModule = Boolean(parts[1]); // Detect 'node:' prefix
|
|
32
|
-
res.moduleName = parts[2].replace(/^node:/, ''); // Remove 'node:' prefix
|
|
33
|
-
|
|
34
|
-
if ((parts[4] === '.') || (parts[4] === '#')) {
|
|
35
|
-
// Ns_Module.export or node:package.export
|
|
36
|
-
if ((parts[6] === '$') || (parts[6] === '$$')) {
|
|
37
|
-
res.composition = Defs.CF;
|
|
38
|
-
res.exportName = parts[5];
|
|
39
|
-
res.life = (parts[6] === '$') ? Defs.LS : Defs.LI;
|
|
40
|
-
} else {
|
|
41
|
-
res.composition = Defs.CA;
|
|
42
|
-
res.life = Defs.LS;
|
|
43
|
-
res.exportName = (parts[5] !== '') ? parts[5] : 'default';
|
|
44
|
-
}
|
|
45
|
-
} else if ((parts[6] === '$') || parts[6] === '$$') {
|
|
46
|
-
// Ns_Module$$ or node:package$$
|
|
47
|
-
res.composition = Defs.CF;
|
|
48
|
-
res.exportName = 'default';
|
|
49
|
-
res.life = (parts[6] === '$') ? Defs.LS : Defs.LI;
|
|
50
|
-
} else {
|
|
51
|
-
// Ns_Module or node:package (ES6 module)
|
|
52
|
-
res.composition = undefined;
|
|
53
|
-
res.exportName = undefined;
|
|
54
|
-
res.life = undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Wrappers handling
|
|
58
|
-
if (parts[10]) {
|
|
59
|
-
res.wrappers = parts[10].split(',');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Ensure singletons for non-factory exports
|
|
64
|
-
if ((res.composition === Defs.CA) && (res.life === Defs.LI))
|
|
65
|
-
throw new Error(`Export is not a function and should be used as a singleton only: '${res.origin}'.`);
|
|
66
|
-
|
|
67
|
-
return res;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default parser for object keys in format:
|
|
3
|
-
* - Ns_Module[.|#]export$[F|A][S|I]
|
|
4
|
-
* - node:package[.|#]export$[F|A][S|I]
|
|
5
|
-
*
|
|
6
|
-
* @namespace TeqFw_Di_Container_A_Parser_Chunk_V02X
|
|
7
|
-
*/
|
|
8
|
-
import Dto from '../../../../DepId.js';
|
|
9
|
-
import Defs from '../../../../Defs.js';
|
|
10
|
-
|
|
11
|
-
// VARS
|
|
12
|
-
/** @type {RegExp} expression for a default object key */
|
|
13
|
-
const REGEXP = /^(node:)?(([A-Z])[A-Za-z0-9_]*|[a-z][a-z0-9-]*)(([#.])?([A-Za-z0-9_]*)((\$)([F|A])?([S|I])?)?)?$/;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @implements TeqFw_Di_Api_Container_Parser_Chunk
|
|
17
|
-
*/
|
|
18
|
-
export default class TeqFw_Di_Container_A_Parser_Chunk_V02X {
|
|
19
|
-
|
|
20
|
-
canParse() {
|
|
21
|
-
// the default parser always tries to parse the depId
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
parse(objectKey) {
|
|
26
|
-
const res = new Dto();
|
|
27
|
-
res.origin = objectKey;
|
|
28
|
-
const parts = REGEXP.exec(objectKey);
|
|
29
|
-
if (parts) {
|
|
30
|
-
res.isNodeModule = Boolean(parts[1]); // Check if it starts with 'node:'
|
|
31
|
-
res.moduleName = parts[2].replace(/^node:/, ''); // Remove 'node:' if present
|
|
32
|
-
|
|
33
|
-
if (parts[5] === '.') {
|
|
34
|
-
// App_Service.export or node:package.export
|
|
35
|
-
if (parts[8] === '$') {
|
|
36
|
-
// App_Service.export$ or node:package.export$
|
|
37
|
-
res.composition = Defs.CF;
|
|
38
|
-
res.exportName = parts[6];
|
|
39
|
-
res.life = (parts[10] === Defs.LI) ? Defs.LI : Defs.LS;
|
|
40
|
-
} else {
|
|
41
|
-
res.composition = (!parts[8] || parts[8] === Defs.CA) ? Defs.CA : Defs.CF;
|
|
42
|
-
res.exportName = parts[6];
|
|
43
|
-
res.life = (!parts[8] || parts[10] === Defs.LS) ? Defs.LS : Defs.LI;
|
|
44
|
-
}
|
|
45
|
-
} else if (parts[8] === '$') {
|
|
46
|
-
// App_Logger$FS or node:package$
|
|
47
|
-
res.composition = (!parts[9] || parts[9] === Defs.CF) ? Defs.CF : Defs.CA;
|
|
48
|
-
res.exportName = 'default';
|
|
49
|
-
res.life = parts[10] ? (parts[10] === Defs.LS ? Defs.LS : Defs.LI) : (res.composition === Defs.CF ? Defs.LS : Defs.LI);
|
|
50
|
-
} else {
|
|
51
|
-
// App_Service or node:package (ES6 module)
|
|
52
|
-
res.composition = undefined;
|
|
53
|
-
res.exportName = undefined;
|
|
54
|
-
res.life = undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Enforce singleton for non-factory exports
|
|
60
|
-
if (res.composition === Defs.CA && res.life === Defs.LI) {
|
|
61
|
-
throw new Error(`Export is not a function and should be used as a singleton only: '${res.origin}'.`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return res;
|
|
65
|
-
}
|
|
66
|
-
}
|
package/src/Container/Config.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import Container from '../Container.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Runtime configuration facade for the DI container.
|
|
5
|
-
*
|
|
6
|
-
* @implements {TeqFw_Di_Api_Container_Config}
|
|
7
|
-
*/
|
|
8
|
-
export default class TeqFw_Di_Container_Config {
|
|
9
|
-
constructor() {
|
|
10
|
-
// VARS
|
|
11
|
-
const _container = new Container();
|
|
12
|
-
let _finalized = false;
|
|
13
|
-
|
|
14
|
-
// FUNCS
|
|
15
|
-
function assertNotFinalized() {
|
|
16
|
-
if (_finalized) throw new Error('Container configuration is finalized.');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// INSTANCE METHODS
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Returns the parser configurator.
|
|
23
|
-
*
|
|
24
|
-
* @returns {TeqFw_Di_Api_Container_Parser}
|
|
25
|
-
*/
|
|
26
|
-
this.parser = function () {
|
|
27
|
-
assertNotFinalized();
|
|
28
|
-
return _container.getParser();
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Returns the pre-processor configurator.
|
|
33
|
-
*
|
|
34
|
-
* @returns {TeqFw_Di_Api_Container_PreProcessor}
|
|
35
|
-
*/
|
|
36
|
-
this.preProcessor = function () {
|
|
37
|
-
assertNotFinalized();
|
|
38
|
-
return _container.getPreProcessor();
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Returns the post-processor configurator.
|
|
43
|
-
*
|
|
44
|
-
* @returns {TeqFw_Di_Api_Container_PostProcessor}
|
|
45
|
-
*/
|
|
46
|
-
this.postProcessor = function () {
|
|
47
|
-
assertNotFinalized();
|
|
48
|
-
return _container.getPostProcessor();
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Returns the resolver configurator.
|
|
53
|
-
*
|
|
54
|
-
* @returns {TeqFw_Di_Api_Container_Resolver}
|
|
55
|
-
*/
|
|
56
|
-
this.resolver = function () {
|
|
57
|
-
assertNotFinalized();
|
|
58
|
-
return _container.getResolver();
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Enables test mode.
|
|
63
|
-
*
|
|
64
|
-
* @returns {void}
|
|
65
|
-
*/
|
|
66
|
-
this.enableTestMode = function () {
|
|
67
|
-
assertNotFinalized();
|
|
68
|
-
_container.enableTestMode();
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Registers a singleton or a Node.js module replacement in test mode.
|
|
73
|
-
*
|
|
74
|
-
* @param {string} depId
|
|
75
|
-
* @param {object} obj
|
|
76
|
-
* @returns {void}
|
|
77
|
-
*/
|
|
78
|
-
this.register = function (depId, obj) {
|
|
79
|
-
assertNotFinalized();
|
|
80
|
-
_container.register(depId, obj);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Finalizes configuration and returns a runtime container instance.
|
|
85
|
-
*
|
|
86
|
-
* @returns {TeqFw_Di_Api_Container}
|
|
87
|
-
*/
|
|
88
|
-
this.finalize = function () {
|
|
89
|
-
_finalized = true;
|
|
90
|
-
return _container;
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
}
|
package/src/Container/Parser.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The parser for the runtime dependency ID contains multiple chunks. Each npm package can have its own format for
|
|
3
|
-
* a `depId`. The parser calls the chunks one by one to parse the string ID as a structure and returns the first result.
|
|
4
|
-
* If none of the chunks processed the `depId`, the parser calls the default chunk.
|
|
5
|
-
*/
|
|
6
|
-
import DefChunk from './A/Parser/Chunk/Def.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @implements TeqFw_Di_Api_Container_Parser
|
|
10
|
-
*/
|
|
11
|
-
export default class TeqFw_Di_Container_Parser {
|
|
12
|
-
constructor() {
|
|
13
|
-
// VARS
|
|
14
|
-
/**
|
|
15
|
-
* The default chunk to parse the depId if no other chunks have parsed this depId.
|
|
16
|
-
*
|
|
17
|
-
* @type {TeqFw_Di_Api_Container_Parser_Chunk}
|
|
18
|
-
*/
|
|
19
|
-
let _defaultChunk = new DefChunk();
|
|
20
|
-
/**
|
|
21
|
-
* The array of the chunks to parse dependency IDs.
|
|
22
|
-
* @type {TeqFw_Di_Api_Container_Parser_Chunk[]}
|
|
23
|
-
*/
|
|
24
|
-
const _chunks = [];
|
|
25
|
-
|
|
26
|
-
// INSTANCE METHODS
|
|
27
|
-
|
|
28
|
-
this.addChunk = function (chunk) {
|
|
29
|
-
_chunks.push(chunk);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
this.parse = function (depId) {
|
|
33
|
-
let res;
|
|
34
|
-
for (const one of _chunks)
|
|
35
|
-
if (one.canParse(depId)) {
|
|
36
|
-
res = one.parse(depId);
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
if (!res)
|
|
40
|
-
res = _defaultChunk?.parse(depId);
|
|
41
|
-
return res;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
this.setDefaultChunk = function (chunk) {
|
|
45
|
-
_defaultChunk = chunk;
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The post-processor handles the result object after composition and before returning.
|
|
3
|
-
*
|
|
4
|
-
* @implements TeqFw_Di_Api_Container_PostProcessor
|
|
5
|
-
*/
|
|
6
|
-
export default class TeqFw_Di_Container_PostProcessor {
|
|
7
|
-
|
|
8
|
-
constructor() {
|
|
9
|
-
// VARS
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* The array of the chunks to modify dependency IDs.
|
|
13
|
-
* @type {TeqFw_Di_Api_Container_PostProcessor_Chunk[]}
|
|
14
|
-
*/
|
|
15
|
-
const _chunks = [];
|
|
16
|
-
|
|
17
|
-
// INSTANCE METHODS
|
|
18
|
-
|
|
19
|
-
this.addChunk = function (chunk) {
|
|
20
|
-
_chunks.push(chunk);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
this.modify = async function (obj, depId, stack) {
|
|
24
|
-
let res = obj;
|
|
25
|
-
for (const one of _chunks) {
|
|
26
|
-
res = one.modify(res, depId, stack);
|
|
27
|
-
if (res instanceof Promise) res = await res;
|
|
28
|
-
}
|
|
29
|
-
return res;
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The preprocessor handles object keys after the parsing but before creating any objects.
|
|
3
|
-
* A replacement rules can be implemented here.
|
|
4
|
-
* Every handler is a function with 2 arguments:
|
|
5
|
-
* - objectKey: current key after processing with other handlers;
|
|
6
|
-
* - originalKey: the key before any processing;
|
|
7
|
-
*
|
|
8
|
-
* @implements TeqFw_Di_Api_Container_PreProcessor
|
|
9
|
-
*/
|
|
10
|
-
export default class TeqFw_Di_Container_PreProcessor {
|
|
11
|
-
|
|
12
|
-
constructor() {
|
|
13
|
-
// VARS
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* The array of the chunks to modify dependency IDs.
|
|
17
|
-
* @type {TeqFw_Di_Api_Container_PreProcessor_Chunk[]}
|
|
18
|
-
*/
|
|
19
|
-
const _chunks = [];
|
|
20
|
-
|
|
21
|
-
// INSTANCE METHODS
|
|
22
|
-
|
|
23
|
-
this.addChunk = function (chunk) {
|
|
24
|
-
_chunks.push(chunk);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
this.modify = function (depId, stack) {
|
|
28
|
-
let res = depId;
|
|
29
|
-
for (const one of _chunks)
|
|
30
|
-
res = one.modify(res, depId, stack);
|
|
31
|
-
return res;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Resolver should convert ES6 module name into the path to the sources (file path or URL).
|
|
3
|
-
*
|
|
4
|
-
* This is a base resolver that considers that:
|
|
5
|
-
* - module name is Zend1-compatible ('Vendor_Package_Module')
|
|
6
|
-
* - every namespace is bound to some real path ('Vendor_Package_' => '.../node_modules/@vendor/package/src/...)
|
|
7
|
-
* - every package has sources with the same extensions (*.js, *.mjs, *.es6, ...)
|
|
8
|
-
* - namespaces can be nested (App_Web_ => ./@app/web/..., App_Web_Api_ => ./@app/web_api/...)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// VARS
|
|
12
|
-
const KEY_EXT = 'ext';
|
|
13
|
-
const KEY_NS = 'ns';
|
|
14
|
-
const KEY_PATH = 'root';
|
|
15
|
-
/**
|
|
16
|
-
* Namespace parts separator.
|
|
17
|
-
*
|
|
18
|
-
* @type {string}
|
|
19
|
-
*/
|
|
20
|
-
const NSS = '_';
|
|
21
|
-
|
|
22
|
-
// MAIN
|
|
23
|
-
/**
|
|
24
|
-
* @implements {TeqFw_Di_Api_Container_Resolver}
|
|
25
|
-
*/
|
|
26
|
-
export default class TeqFw_Di_Container_Resolver {
|
|
27
|
-
|
|
28
|
-
constructor() {
|
|
29
|
-
// VARS
|
|
30
|
-
const _regNs = {};
|
|
31
|
-
let _isWindows = false; // flag of the runtime env - win or *nix/web
|
|
32
|
-
let _namespaces = [];
|
|
33
|
-
let _ps = '/'; // web & unix path separator
|
|
34
|
-
|
|
35
|
-
// INSTANCE METHODS
|
|
36
|
-
|
|
37
|
-
this.addNamespaceRoot = function (ns, path, ext) {
|
|
38
|
-
const lead = (_isWindows) ? path.replace(/^\\/, '') : path; // remove leading backslash for Win
|
|
39
|
-
const norm = lead.replace(/\\/g, '/'); // replace all windows path separators
|
|
40
|
-
const root = (_isWindows) ? `file://${norm}` : norm;
|
|
41
|
-
_regNs[ns] = {
|
|
42
|
-
[KEY_EXT]: ext ?? 'js',
|
|
43
|
-
[KEY_NS]: ns,
|
|
44
|
-
[KEY_PATH]: root,
|
|
45
|
-
};
|
|
46
|
-
_namespaces = Object.keys(_regNs).sort((a, b) => b.localeCompare(a));
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Convert the module name to the path of the source files .
|
|
51
|
-
* @param {string} moduleName 'Vendor_Package_Module'
|
|
52
|
-
* @returns {string} '/home/user/app/node_modules/@vendor/package/src/Module.js'
|
|
53
|
-
*/
|
|
54
|
-
this.resolve = function (moduleName) {
|
|
55
|
-
let root, ext, ns;
|
|
56
|
-
for (ns of _namespaces) {
|
|
57
|
-
if (moduleName.startsWith(ns)) {
|
|
58
|
-
root = _regNs[ns][KEY_PATH];
|
|
59
|
-
ext = _regNs[ns][KEY_EXT];
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (root && ext) {
|
|
64
|
-
let tail = moduleName.replace(ns, '');
|
|
65
|
-
if (tail.indexOf(NSS) === 0) tail = tail.replace(NSS, '');
|
|
66
|
-
const file = tail.replaceAll(NSS, _ps);
|
|
67
|
-
return `${root}${_ps}${file}.${ext}`;
|
|
68
|
-
} else return moduleName;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 'true' - to use '\' as path separator to resolve paths.
|
|
73
|
-
* @param {boolean} isWindows
|
|
74
|
-
*/
|
|
75
|
-
this.setWindowsEnv = function (isWindows = true) {
|
|
76
|
-
_isWindows = isWindows;
|
|
77
|
-
_ps = (isWindows) ? '\\' : '/';
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
};
|
package/src/Container.js
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Object Container (composition root).
|
|
3
|
-
* We can use static imports in the Container.
|
|
4
|
-
*
|
|
5
|
-
* @namespace TeqFw_Di_Container
|
|
6
|
-
*/
|
|
7
|
-
import Composer from './Container/A/Composer.js';
|
|
8
|
-
import Defs from './Defs.js';
|
|
9
|
-
import Parser from './Container/Parser.js';
|
|
10
|
-
import PreProcessor from './Container/PreProcessor.js';
|
|
11
|
-
import PostProcessor from './Container/PostProcessor.js';
|
|
12
|
-
import Resolver from './Container/Resolver.js';
|
|
13
|
-
|
|
14
|
-
// FUNCS
|
|
15
|
-
/**
|
|
16
|
-
* ID to store singletons in the internal registry.
|
|
17
|
-
* @param {TeqFw_Di_DepId} key
|
|
18
|
-
* @returns {string}
|
|
19
|
-
*/
|
|
20
|
-
function getSingletonId(key) {
|
|
21
|
-
return `${key.moduleName}#${key.exportName}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Determines if an object, function, or primitive can be safely frozen.
|
|
26
|
-
* @param {*} value - The value to check.
|
|
27
|
-
* @returns {boolean} - Returns true if the value can be safely frozen.
|
|
28
|
-
*/
|
|
29
|
-
function canBeFrozen(value) {
|
|
30
|
-
// Primitives (except objects and functions) cannot be frozen
|
|
31
|
-
if (value === null || typeof value !== 'object' && typeof value !== 'function') return false;
|
|
32
|
-
// // ES modules cannot be frozen
|
|
33
|
-
if (Object.prototype.toString.call(value) === '[object Module]') return false;
|
|
34
|
-
// check is Object is already frozen
|
|
35
|
-
return !Object.isFrozen(value);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// MAIN
|
|
39
|
-
export default class TeqFw_Di_Container {
|
|
40
|
-
|
|
41
|
-
constructor() {
|
|
42
|
-
// VARS
|
|
43
|
-
let _composer = new Composer();
|
|
44
|
-
let _debug = false;
|
|
45
|
-
let _parser = new Parser();
|
|
46
|
-
let _postProcessor = new PostProcessor();
|
|
47
|
-
let _preProcessor = new PreProcessor();
|
|
48
|
-
let _testMode = false;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Registry for paths for loaded es6 modules.
|
|
52
|
-
*
|
|
53
|
-
* @type {Object<string, string>}
|
|
54
|
-
*/
|
|
55
|
-
const _regPaths = {};
|
|
56
|
-
/**
|
|
57
|
-
* Registry to store singletons.
|
|
58
|
-
* @type {Object<string, object>}
|
|
59
|
-
*/
|
|
60
|
-
const _regSingles = {};
|
|
61
|
-
/**
|
|
62
|
-
* Registry to store mocks for Node.js libs in the Test mode.
|
|
63
|
-
* @type {Object<string, object>}
|
|
64
|
-
*/
|
|
65
|
-
const _regTestNodeLibs = {};
|
|
66
|
-
|
|
67
|
-
let _resolver = new Resolver();
|
|
68
|
-
|
|
69
|
-
// FUNCS
|
|
70
|
-
|
|
71
|
-
function log() {
|
|
72
|
-
if (_debug) console.log(...arguments);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// INSTANCE METHODS
|
|
76
|
-
|
|
77
|
-
this.get = async function (depId, stack = []) {
|
|
78
|
-
log(`Object '${depId}' is requested.`);
|
|
79
|
-
// parse the `objectKey` and get the structured DTO
|
|
80
|
-
const parsed = _parser.parse(depId);
|
|
81
|
-
// modify the original key according to some rules (replacements, etc.)
|
|
82
|
-
const key = _preProcessor.modify(parsed, stack);
|
|
83
|
-
// return existing singleton
|
|
84
|
-
if (key.life === Defs.LS) {
|
|
85
|
-
const singleId = getSingletonId(key);
|
|
86
|
-
if (_regSingles[singleId]) {
|
|
87
|
-
log(`Existing singleton '${singleId}' is returned.`);
|
|
88
|
-
return _regSingles[singleId];
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
// return existing node lib in the Test mode
|
|
92
|
-
if (key.isNodeModule && _testMode) {
|
|
93
|
-
const nodeId = key.origin;
|
|
94
|
-
if (_regTestNodeLibs[nodeId]) {
|
|
95
|
-
log(`Existing nodejs lib '${nodeId}' is returned.`);
|
|
96
|
-
return _regTestNodeLibs[nodeId];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// resolve a path to es6 module if not resolved before
|
|
100
|
-
if (!_regPaths[key.moduleName]) {
|
|
101
|
-
log(`ES6 module '${key.moduleName}' is not resolved yet`);
|
|
102
|
-
// convert the module name to the path to an es6-module file with a source
|
|
103
|
-
_regPaths[key.moduleName] = _resolver.resolve(key.moduleName);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// load es6 module
|
|
107
|
-
let module;
|
|
108
|
-
const path = _regPaths[key.moduleName];
|
|
109
|
-
try {
|
|
110
|
-
module = await import(path);
|
|
111
|
-
log(`ES6 module '${key.moduleName}' is loaded from '${path}'.`);
|
|
112
|
-
} catch (e) {
|
|
113
|
-
console.error(e?.message, `Object key: "${depId}".`, `Path: "${path}".`, `Stack: ${JSON.stringify(stack)}`);
|
|
114
|
-
throw e;
|
|
115
|
-
}
|
|
116
|
-
// create an object using the composer, then modify it in post-processor
|
|
117
|
-
let res = await _composer.create(key, module, stack, this);
|
|
118
|
-
// freeze the result to prevent modifications
|
|
119
|
-
if (canBeFrozen(res)) Object.freeze(res);
|
|
120
|
-
res = await _postProcessor.modify(res, key, stack);
|
|
121
|
-
log(`Object '${depId}' is created.`);
|
|
122
|
-
|
|
123
|
-
// save singletons
|
|
124
|
-
if (key.life === Defs.LS) {
|
|
125
|
-
const singleId = getSingletonId(key);
|
|
126
|
-
_regSingles[singleId] = res;
|
|
127
|
-
log(`Object '${depId}' is saved as singleton.`);
|
|
128
|
-
}
|
|
129
|
-
return res;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Enables test mode, allowing manual singleton registration.
|
|
134
|
-
*/
|
|
135
|
-
this.enableTestMode = function () {
|
|
136
|
-
_testMode = true;
|
|
137
|
-
log('Test mode enabled');
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
this.getParser = () => _parser;
|
|
141
|
-
|
|
142
|
-
this.getPreProcessor = () => _preProcessor;
|
|
143
|
-
|
|
144
|
-
this.getPostProcessor = () => _postProcessor;
|
|
145
|
-
|
|
146
|
-
this.getResolver = () => _resolver;
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Registers a new singleton object in the Container.
|
|
150
|
-
*
|
|
151
|
-
* @param {string} depId - Dependency identifier. Must be a singleton identifier.
|
|
152
|
-
* @param {object} obj - The object to register.
|
|
153
|
-
*/
|
|
154
|
-
this.register = function (depId, obj) {
|
|
155
|
-
if (!_testMode) throw new Error('Use enableTestMode() to allow it');
|
|
156
|
-
|
|
157
|
-
if (!depId || !obj) throw new Error('Both params are required');
|
|
158
|
-
|
|
159
|
-
const key = _parser.parse(depId);
|
|
160
|
-
if ((key.life !== Defs.LS) && !key.isNodeModule) throw new Error(`Only node modules & singletons can be registered: '${depId}'`);
|
|
161
|
-
if (key.life === Defs.LS) {
|
|
162
|
-
const singleId = getSingletonId(key);
|
|
163
|
-
if (_regSingles[singleId]) throw new Error(`'${depId}' is already registered`);
|
|
164
|
-
_regSingles[singleId] = obj;
|
|
165
|
-
} else if (key.isNodeModule) {
|
|
166
|
-
const nodeId = key.origin;
|
|
167
|
-
if (_regTestNodeLibs[nodeId]) throw new Error(`'${depId}' is already registered`);
|
|
168
|
-
_regTestNodeLibs[nodeId] = obj;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
log(`'${depId}' is registered`);
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
this.setDebug = function (data) {
|
|
175
|
-
_debug = data;
|
|
176
|
-
_composer.setDebug(data);
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
this.setParser = (data) => _parser = data;
|
|
180
|
-
|
|
181
|
-
this.setPreProcessor = (data) => _preProcessor = data;
|
|
182
|
-
|
|
183
|
-
this.setPostProcessor = (data) => _postProcessor = data;
|
|
184
|
-
|
|
185
|
-
this.setResolver = (data) => _resolver = data;
|
|
186
|
-
}
|
|
187
|
-
};
|