@teqfw/di 2.5.0 → 2.6.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 +14 -0
- package/README.md +449 -263
- package/ai/AGENTS.md +42 -55
- package/ai/concepts.md +11 -54
- package/ai/container.md +57 -45
- package/ai/dependency-id.md +78 -58
- package/ai/extensions.md +68 -38
- package/ai/package-api.ts +13 -11
- package/ai/usage.md +80 -205
- package/dist/esm.js +1 -1
- package/dist/umd.js +1 -1
- package/package.json +1 -1
- package/src/Config/NamespaceRegistry.mjs +19 -15
- package/src/Container/Lifecycle/Registry.mjs +2 -12
- package/src/Container/Resolve/GraphResolver.mjs +8 -28
- package/src/Container/Runtime.mjs +244 -0
- package/src/Container.mjs +14 -387
- package/src/Internal/DependencyKey.mjs +29 -0
- package/src/Internal/DepsDecl.mjs +42 -0
- package/src/Internal/PromiseSafe.mjs +39 -0
- package/src/{Def/Parser.mjs → Parser.mjs} +82 -35
- package/src/Container/Instantiate/ExportSelector.mjs +0 -44
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @namespace
|
|
4
|
+
* @namespace TeqFw_Di_Parser
|
|
5
5
|
* @description CDC parser that builds dependency identity DTOs.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import TeqFw_Di_Enum_Composition from '
|
|
9
|
-
import TeqFw_Di_Enum_Life from '
|
|
10
|
-
import TeqFw_Di_Enum_Platform from '
|
|
11
|
-
import {Factory as TeqFw_Di_Dto_DepId_Factory} from '
|
|
8
|
+
import TeqFw_Di_Enum_Composition from './Enum/Composition.mjs';
|
|
9
|
+
import TeqFw_Di_Enum_Life from './Enum/Life.mjs';
|
|
10
|
+
import TeqFw_Di_Enum_Platform from './Enum/Platform.mjs';
|
|
11
|
+
import {Factory as TeqFw_Di_Dto_DepId_Factory} from './Dto/DepId.mjs';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Parser for CDC identifiers into frozen dependency identity DTO.
|
|
15
15
|
*/
|
|
16
|
-
export default class
|
|
16
|
+
export default class TeqFw_Di_Parser {
|
|
17
17
|
/**
|
|
18
18
|
* Creates parser instance.
|
|
19
19
|
*/
|
|
@@ -24,35 +24,36 @@ export default class TeqFw_Di_Def_Parser {
|
|
|
24
24
|
let logger = null;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Detects platform prefix and strips it from the source string.
|
|
28
28
|
*
|
|
29
|
-
* @param {string}
|
|
30
|
-
* @returns {
|
|
29
|
+
* @param {string} source CDC source without validation.
|
|
30
|
+
* @returns {{platform: typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform], source: string}}
|
|
31
31
|
*/
|
|
32
|
-
|
|
33
|
-
if (logger) logger.log(`Parser.parse: input='${cdc}'.`);
|
|
34
|
-
if (typeof cdc !== 'string') throw new Error('CDC must be a string.');
|
|
35
|
-
if (cdc.length === 0) throw new Error('CDC must be non-empty.');
|
|
36
|
-
if (!/^[\x00-\x7F]+$/.test(cdc)) throw new Error('CDC must be ASCII.');
|
|
37
|
-
|
|
38
|
-
/** @type {string} */
|
|
39
|
-
const origin = cdc;
|
|
40
|
-
let source = cdc;
|
|
32
|
+
const detectPlatform = function (source) {
|
|
41
33
|
/** @type {typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform]} */
|
|
42
34
|
let platform = TeqFw_Di_Enum_Platform.TEQ;
|
|
43
|
-
|
|
44
35
|
if (source.startsWith('node:')) {
|
|
45
36
|
platform = TeqFw_Di_Enum_Platform.NODE;
|
|
46
|
-
|
|
47
|
-
}
|
|
37
|
+
return {platform, source: source.slice(5)};
|
|
38
|
+
}
|
|
39
|
+
if (source.startsWith('npm:')) {
|
|
48
40
|
platform = TeqFw_Di_Enum_Platform.NPM;
|
|
49
|
-
|
|
50
|
-
}
|
|
41
|
+
return {platform, source: source.slice(4)};
|
|
42
|
+
}
|
|
43
|
+
if (source.startsWith('teq:')) {
|
|
51
44
|
throw new Error('Explicit teq: prefix is forbidden.');
|
|
52
45
|
}
|
|
46
|
+
return {platform, source};
|
|
47
|
+
};
|
|
53
48
|
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Parses lifecycle and wrapper suffix from the source string.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} source CDC source without platform prefix.
|
|
53
|
+
* @param {typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform]} platform
|
|
54
|
+
* @returns {{core: string, life: typeof TeqFw_Di_Enum_Life[keyof typeof TeqFw_Di_Enum_Life] | null, lifecycleDeclared: boolean, wrappers: string[]}}
|
|
55
|
+
*/
|
|
56
|
+
const parseLifecycle = function (source, platform) {
|
|
56
57
|
/** @type {typeof TeqFw_Di_Enum_Life[keyof typeof TeqFw_Di_Enum_Life] | null} */
|
|
57
58
|
let life = null;
|
|
58
59
|
let lifecycleDeclared = false;
|
|
@@ -74,13 +75,24 @@ export default class TeqFw_Di_Def_Parser {
|
|
|
74
75
|
if (suffix.length > 0) {
|
|
75
76
|
wrappers = suffix.slice(1).split('_');
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
-
if (source.includes('$')) throw new Error('Invalid lifecycle encoding.');
|
|
79
|
-
if ((platform !== TeqFw_Di_Enum_Platform.NODE) && /(?:^|[^_])_[a-z][0-9A-Za-z]*$/.test(source)) {
|
|
80
|
-
throw new Error('Wrapper without lifecycle is forbidden.');
|
|
81
|
-
}
|
|
78
|
+
return {core, life, lifecycleDeclared, wrappers};
|
|
82
79
|
}
|
|
83
80
|
|
|
81
|
+
if (source.includes('$')) throw new Error('Invalid lifecycle encoding.');
|
|
82
|
+
if ((platform !== TeqFw_Di_Enum_Platform.NODE) && /(?:^|[^_])_[a-z][0-9A-Za-z]*$/.test(source)) {
|
|
83
|
+
throw new Error('Wrapper without lifecycle is forbidden.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {core, life, lifecycleDeclared, wrappers};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Splits module and export names from canonical core string.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} core CDC core without lifecycle suffix.
|
|
93
|
+
* @returns {{moduleName: string, exportName: string|null}}
|
|
94
|
+
*/
|
|
95
|
+
const parseModuleExport = function (core) {
|
|
84
96
|
const firstDelim = core.indexOf('__');
|
|
85
97
|
const lastDelim = core.lastIndexOf('__');
|
|
86
98
|
if ((firstDelim !== -1) && (firstDelim !== lastDelim)) throw new Error('Export delimiter must appear at most once.');
|
|
@@ -98,6 +110,17 @@ export default class TeqFw_Di_Def_Parser {
|
|
|
98
110
|
if (exportName.includes('$')) throw new Error('Export must not contain $.');
|
|
99
111
|
}
|
|
100
112
|
|
|
113
|
+
return {moduleName, exportName};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Validates canonical module name for platform-specific rules.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} moduleName
|
|
120
|
+
* @param {typeof TeqFw_Di_Enum_Platform[keyof typeof TeqFw_Di_Enum_Platform]} platform
|
|
121
|
+
* @returns {void}
|
|
122
|
+
*/
|
|
123
|
+
const assertModuleName = function (moduleName, platform) {
|
|
101
124
|
if (!moduleName) throw new Error('moduleName must be non-empty.');
|
|
102
125
|
if (moduleName.startsWith('_') || moduleName.startsWith('$')) throw new Error('moduleName must not start with _ or $.');
|
|
103
126
|
if (moduleName.includes('__')) throw new Error('moduleName must not contain __.');
|
|
@@ -113,23 +136,47 @@ export default class TeqFw_Di_Def_Parser {
|
|
|
113
136
|
} else if (!/^[@A-Za-z_][$0-9A-Za-z_./-]*$/.test(moduleName)) {
|
|
114
137
|
throw new Error('npm moduleName must satisfy the package specifier form.');
|
|
115
138
|
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parses one CDC identifier and returns normalized frozen dependency DTO.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} cdc CDC identifier string.
|
|
145
|
+
* @returns {TeqFw_Di_DepId__DTO}
|
|
146
|
+
*/
|
|
147
|
+
this.parse = function (cdc) {
|
|
148
|
+
if (logger) logger.log(`Parser.parse: input='${cdc}'.`);
|
|
149
|
+
if (typeof cdc !== 'string') throw new Error('CDC must be a string.');
|
|
150
|
+
if (cdc.length === 0) throw new Error('CDC must be non-empty.');
|
|
151
|
+
if (!/^[\x00-\x7F]+$/.test(cdc)) throw new Error('CDC must be ASCII.');
|
|
152
|
+
|
|
153
|
+
/** @type {string} */
|
|
154
|
+
const origin = cdc;
|
|
155
|
+
const detected = detectPlatform(cdc);
|
|
156
|
+
const platform = detected.platform;
|
|
157
|
+
const source = detected.source;
|
|
158
|
+
if (source.length === 0) throw new Error('moduleName must be non-empty.');
|
|
159
|
+
const lifecycle = parseLifecycle(source, platform);
|
|
160
|
+
const split = parseModuleExport(lifecycle.core);
|
|
161
|
+
assertModuleName(split.moduleName, platform);
|
|
116
162
|
|
|
117
163
|
/** @type {typeof TeqFw_Di_Enum_Composition[keyof typeof TeqFw_Di_Enum_Composition]} */
|
|
118
164
|
let composition = TeqFw_Di_Enum_Composition.AS_IS;
|
|
165
|
+
let exportName = split.exportName;
|
|
119
166
|
if (exportName !== null) {
|
|
120
167
|
composition = TeqFw_Di_Enum_Composition.FACTORY;
|
|
121
|
-
} else if (lifecycleDeclared) {
|
|
122
|
-
composition = TeqFw_Di_Enum_Composition.FACTORY;
|
|
168
|
+
} else if (lifecycle.lifecycleDeclared) {
|
|
123
169
|
exportName = 'default';
|
|
170
|
+
composition = TeqFw_Di_Enum_Composition.FACTORY;
|
|
124
171
|
}
|
|
125
172
|
|
|
126
173
|
const depId = depIdFactory.create({
|
|
127
|
-
moduleName,
|
|
174
|
+
moduleName: split.moduleName,
|
|
128
175
|
platform,
|
|
129
176
|
exportName,
|
|
130
177
|
composition,
|
|
131
|
-
life,
|
|
132
|
-
wrappers,
|
|
178
|
+
life: lifecycle.life,
|
|
179
|
+
wrappers: lifecycle.wrappers,
|
|
133
180
|
origin,
|
|
134
181
|
});
|
|
135
182
|
if (logger) logger.log(`Parser.parse: produced='${depId.platform}::${depId.moduleName}'.`);
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @namespace TeqFw_Di_Container_Instantiate_ExportSelector
|
|
5
|
-
* @description Selects target export from loaded module namespace.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Instantiate-stage export selector.
|
|
10
|
-
*
|
|
11
|
-
* Selects exactly one export from an already loaded ES module namespace object
|
|
12
|
-
* using only `depId.exportName`.
|
|
13
|
-
*/
|
|
14
|
-
export default class TeqFw_Di_Container_Instantiate_ExportSelector {
|
|
15
|
-
/**
|
|
16
|
-
* Creates export selector instance.
|
|
17
|
-
*/
|
|
18
|
-
constructor() {
|
|
19
|
-
/**
|
|
20
|
-
* Selects a raw export value from module namespace.
|
|
21
|
-
*
|
|
22
|
-
* @param {object} namespace Loaded ES module namespace object.
|
|
23
|
-
* @param {TeqFw_Di_DepId__DTO} depId Dependency identity DTO.
|
|
24
|
-
* @returns {unknown} Raw selected export value.
|
|
25
|
-
*/
|
|
26
|
-
this.select = function (namespace, depId) {
|
|
27
|
-
if (!namespace || (typeof namespace !== 'object')) {
|
|
28
|
-
throw new Error('Namespace must be an object.');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** @type {string|null} */
|
|
32
|
-
const exportName = depId.exportName;
|
|
33
|
-
if (exportName === null) {
|
|
34
|
-
throw new Error('Export name must not be null for export selection.');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!(exportName in namespace)) {
|
|
38
|
-
throw new Error(`Export '${exportName}' is not found in module namespace.`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return namespace[exportName];
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
}
|