@teqfw/di 2.6.2 → 2.8.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 +24 -0
- package/README.md +63 -561
- package/ai/AGENTS.md +10 -54
- package/dist/esm.js +1 -1
- package/dist/umd.js +1 -1
- package/package.json +23 -16
- package/skills/teqfw-di/SKILL.md +36 -0
- package/skills/teqfw-di/agents/openai.yaml +4 -0
- package/skills/teqfw-di/references/compatibility.md +35 -0
- package/skills/teqfw-di/references/concepts.md +43 -0
- package/{ai → skills/teqfw-di/references}/container.md +12 -6
- package/{ai → skills/teqfw-di/references}/dependency-id.md +28 -11
- package/skills/teqfw-di/references/distribution.md +24 -0
- package/{ai → skills/teqfw-di/references}/extensions.md +18 -7
- package/{ai → skills/teqfw-di/references}/package-api.ts +58 -35
- package/skills/teqfw-di/references/usage.md +283 -0
- package/src/AGENTS.md +20 -16
- package/src/Config/NamespaceRegistry.mjs +3 -213
- package/src/Container/Executor.mjs +2 -2
- package/src/Container/GraphResolver.mjs +24 -13
- package/src/Container/Instantiate.mjs +2 -2
- package/src/Container/Lifecycle.mjs +2 -2
- package/src/Container/Pipeline.mjs +20 -22
- package/src/Container.mjs +67 -35
- package/src/Dto/DepId.mjs +1 -1
- package/src/Internal/DepsDecl.mjs +2 -2
- package/src/Node/Registry/Namespace.mjs +208 -0
- package/src/Node/Registry/Package.mjs +215 -0
- package/src/Parser.mjs +18 -18
- package/types.d.ts +9 -4
- package/ai/concepts.md +0 -25
- package/ai/usage.md +0 -194
|
@@ -2,218 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @namespace TeqFw_Di_Config_NamespaceRegistry
|
|
5
|
-
* @description
|
|
5
|
+
* @description Deprecated compatibility re-export for the Node.js namespace registry.
|
|
6
|
+
* @deprecated Use @teqfw/di/node/registry/namespace in new integrations.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
* @typedef {object} TeqFw_Di_Config_NamespaceRegistry_Dependencies
|
|
10
|
-
* @property {{readFile(path: string, encoding: string): Promise<string>, readdir(path: string): Promise<string[]>, realpath(path: string): Promise<string>, stat(path: string): Promise<{isDirectory(): boolean}>}} fs
|
|
11
|
-
* @property {{join(...paths: string[]): string, dirname(path: string): string, relative(from: string, to: string): string, resolve(...paths: string[]): string, isAbsolute(path: string): boolean}} path
|
|
12
|
-
* @property {string} appRoot
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {object} TeqFw_Di_Config_NamespaceRegistry_Entry
|
|
17
|
-
* @property {string} prefix
|
|
18
|
-
* @property {string} dirAbs
|
|
19
|
-
* @property {string} ext
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Builds deterministic immutable namespace registry from root package and installed dependencies.
|
|
24
|
-
*/
|
|
25
|
-
export default class TeqFw_Di_Config_NamespaceRegistry {
|
|
26
|
-
/**
|
|
27
|
-
* @param {TeqFw_Di_Config_NamespaceRegistry_Dependencies} deps
|
|
28
|
-
*/
|
|
29
|
-
constructor({fs, path, appRoot}) {
|
|
30
|
-
const appRootAbs = path.resolve(appRoot);
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @param {string} fileAbs
|
|
34
|
-
* @returns {Promise<Record<string, unknown>>}
|
|
35
|
-
*/
|
|
36
|
-
const readJson = async function (fileAbs) {
|
|
37
|
-
const content = await fs.readFile(fileAbs, 'utf8');
|
|
38
|
-
return /** @type {Record<string, unknown>} */ (JSON.parse(content));
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @param {string} absPath
|
|
43
|
-
* @returns {Promise<boolean>}
|
|
44
|
-
*/
|
|
45
|
-
const isDirectory = async function (absPath) {
|
|
46
|
-
try {
|
|
47
|
-
const stat = await fs.stat(absPath);
|
|
48
|
-
return stat.isDirectory();
|
|
49
|
-
} catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @param {string} rootAbs
|
|
56
|
-
* @param {string} candidateAbs
|
|
57
|
-
* @returns {boolean}
|
|
58
|
-
*/
|
|
59
|
-
const isInside = function (rootAbs, candidateAbs) {
|
|
60
|
-
const rel = path.relative(rootAbs, candidateAbs);
|
|
61
|
-
return (rel === '') || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* @param {string} packageRootAbs
|
|
66
|
-
* @param {string} dirAbs
|
|
67
|
-
* @returns {Promise<void>}
|
|
68
|
-
*/
|
|
69
|
-
const assertInsidePackageRoot = async function (packageRootAbs, dirAbs) {
|
|
70
|
-
const packageRootReal = await fs.realpath(packageRootAbs);
|
|
71
|
-
const dirReal = await fs.realpath(dirAbs);
|
|
72
|
-
if (!isInside(packageRootReal, dirReal)) {
|
|
73
|
-
throw new Error(`Namespace path resolves outside package root: '${dirAbs}'.`);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @param {unknown} ext
|
|
79
|
-
* @returns {string}
|
|
80
|
-
*/
|
|
81
|
-
const normalizeExt = function (ext) {
|
|
82
|
-
if (ext === undefined) return '.mjs';
|
|
83
|
-
if ((typeof ext !== 'string') || (ext.length === 0)) {
|
|
84
|
-
throw new Error('Namespace extension must be a non-empty string.');
|
|
85
|
-
}
|
|
86
|
-
const normalized = ext.startsWith('.') ? ext : `.${ext}`;
|
|
87
|
-
if ((normalized !== '.mjs') && (normalized !== '.js')) {
|
|
88
|
-
throw new Error(`Namespace extension is not ESM-compatible: '${normalized}'.`);
|
|
89
|
-
}
|
|
90
|
-
return normalized;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @param {unknown} raw
|
|
95
|
-
* @param {string} packageRootAbs
|
|
96
|
-
* @returns {Promise<TeqFw_Di_Config_NamespaceRegistry_Entry>}
|
|
97
|
-
*/
|
|
98
|
-
const normalizeEntry = async function (raw, packageRootAbs) {
|
|
99
|
-
if ((raw === null) || (typeof raw !== 'object')) {
|
|
100
|
-
throw new Error('Namespace entry must be an object.');
|
|
101
|
-
}
|
|
102
|
-
const item = /** @type {Record<string, unknown>} */ (raw);
|
|
103
|
-
|
|
104
|
-
const prefix = item.prefix;
|
|
105
|
-
if ((typeof prefix !== 'string') || (prefix.length === 0) || !prefix.endsWith('_')) {
|
|
106
|
-
throw new Error(`Namespace prefix is invalid: '${String(prefix)}'.`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const rawPath = item.path;
|
|
110
|
-
if ((typeof rawPath !== 'string') || (rawPath.length === 0) || path.isAbsolute(rawPath)) {
|
|
111
|
-
throw new Error(`Namespace path must be a non-empty relative path: '${String(rawPath)}'.`);
|
|
112
|
-
}
|
|
113
|
-
const dirAbs = path.resolve(packageRootAbs, rawPath);
|
|
114
|
-
if (!(await isDirectory(dirAbs))) {
|
|
115
|
-
throw new Error(`Namespace path does not point to existing directory: '${dirAbs}'.`);
|
|
116
|
-
}
|
|
117
|
-
await assertInsidePackageRoot(packageRootAbs, dirAbs);
|
|
118
|
-
|
|
119
|
-
const ext = normalizeExt(item.ext);
|
|
120
|
-
|
|
121
|
-
return {prefix, dirAbs, ext};
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* @param {string} packageRootAbs
|
|
126
|
-
* @returns {Promise<{namespaces: unknown[], dependencies: string[]}>}
|
|
127
|
-
*/
|
|
128
|
-
const readPackageMetadata = async function (packageRootAbs) {
|
|
129
|
-
const packageJsonAbs = path.join(packageRootAbs, 'package.json');
|
|
130
|
-
const packageJson = await readJson(packageJsonAbs);
|
|
131
|
-
const teqfw = (packageJson.teqfw && typeof packageJson.teqfw === 'object')
|
|
132
|
-
? /** @type {Record<string, unknown>} */ (packageJson.teqfw)
|
|
133
|
-
: {};
|
|
134
|
-
const namespaces = Array.isArray(teqfw.namespaces) ? teqfw.namespaces : [];
|
|
135
|
-
const dependencies = (packageJson.dependencies && typeof packageJson.dependencies === 'object')
|
|
136
|
-
? Object.keys(/** @type {Record<string, unknown>} */ (packageJson.dependencies)).sort()
|
|
137
|
-
: [];
|
|
138
|
-
return {namespaces, dependencies};
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Checks whether package.json exists inside a dependency directory.
|
|
143
|
-
*
|
|
144
|
-
* @param {string} candidateAbs
|
|
145
|
-
* @returns {Promise<boolean>}
|
|
146
|
-
*/
|
|
147
|
-
const hasPackageJson = async function (candidateAbs) {
|
|
148
|
-
try {
|
|
149
|
-
await fs.stat(path.join(candidateAbs, 'package.json'));
|
|
150
|
-
return true;
|
|
151
|
-
} catch {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* @param {string} packageName
|
|
158
|
-
* @param {string} fromPackageRootAbs
|
|
159
|
-
* @returns {Promise<string>}
|
|
160
|
-
*/
|
|
161
|
-
const resolveDependencyPackageRoot = async function (packageName, fromPackageRootAbs) {
|
|
162
|
-
let cursor = fromPackageRootAbs;
|
|
163
|
-
while (isInside(appRootAbs, cursor)) {
|
|
164
|
-
const candidate = path.join(cursor, 'node_modules', packageName);
|
|
165
|
-
if (await isDirectory(candidate) && (await hasPackageJson(candidate))) {
|
|
166
|
-
return path.resolve(candidate);
|
|
167
|
-
}
|
|
168
|
-
if (cursor === appRootAbs) break;
|
|
169
|
-
const parent = path.dirname(cursor);
|
|
170
|
-
if (parent === cursor) break;
|
|
171
|
-
cursor = parent;
|
|
172
|
-
}
|
|
173
|
-
throw new Error(`Installed dependency is not found: '${packageName}' from '${fromPackageRootAbs}'.`);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* @returns {Promise<Readonly<TeqFw_Di_Config_NamespaceRegistry_Entry[]>>}
|
|
178
|
-
*/
|
|
179
|
-
this.build = async function () {
|
|
180
|
-
/** @type {{name: string, rootAbs: string}[]} */
|
|
181
|
-
const queue = [{rootAbs: appRootAbs}];
|
|
182
|
-
/** @type {Set<string>} */
|
|
183
|
-
const visitedRoots = new Set();
|
|
184
|
-
/** @type {Set<string>} */
|
|
185
|
-
const uniquePrefixes = new Set();
|
|
186
|
-
/** @type {TeqFw_Di_Config_NamespaceRegistry_Entry[]} */
|
|
187
|
-
const entries = [];
|
|
188
|
-
|
|
189
|
-
while (queue.length > 0) {
|
|
190
|
-
const current = queue.shift();
|
|
191
|
-
const packageRootAbs = current.rootAbs;
|
|
192
|
-
const packageRootReal = await fs.realpath(packageRootAbs);
|
|
193
|
-
if (visitedRoots.has(packageRootReal)) continue;
|
|
194
|
-
visitedRoots.add(packageRootReal);
|
|
195
|
-
|
|
196
|
-
const meta = await readPackageMetadata(packageRootAbs);
|
|
197
|
-
for (const raw of meta.namespaces) {
|
|
198
|
-
const normalized = await normalizeEntry(raw, packageRootAbs);
|
|
199
|
-
if (uniquePrefixes.has(normalized.prefix)) {
|
|
200
|
-
throw new Error(`Duplicate namespace prefix is not allowed: '${normalized.prefix}'.`);
|
|
201
|
-
}
|
|
202
|
-
uniquePrefixes.add(normalized.prefix);
|
|
203
|
-
entries.push(normalized);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
for (const depName of meta.dependencies) {
|
|
207
|
-
const depRootAbs = await resolveDependencyPackageRoot(depName, packageRootAbs);
|
|
208
|
-
queue.push({rootAbs: depRootAbs});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
entries.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
213
|
-
for (const entry of entries) {
|
|
214
|
-
Object.freeze(entry);
|
|
215
|
-
}
|
|
216
|
-
return Object.freeze(entries);
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
}
|
|
9
|
+
export {default} from "../Node/Registry/Namespace.mjs";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @namespace
|
|
4
|
+
* @namespace TeqFw_Di_Container_Executor
|
|
5
5
|
* @description Applies wrapper pipeline to resolved values.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Executes wrapper pipeline declared in `depId.wrappers` using functions
|
|
12
12
|
* exported by the resolved module namespace.
|
|
13
13
|
*/
|
|
14
|
-
export default class
|
|
14
|
+
export default class TeqFw_Di_Container_Executor {
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Creates wrapper executor instance.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @namespace
|
|
4
|
+
* @namespace TeqFw_Di_Container_GraphResolver
|
|
5
5
|
* @description Dependency graph resolver for container preloading.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -13,25 +13,27 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* @typedef {object}
|
|
17
|
-
* @property {
|
|
16
|
+
* @typedef {object} TeqFw_Di_Container_GraphResolver_Dependencies
|
|
17
|
+
* @property {(specifier: string) => TeqFw_Di_DepId__DTO} canonicalize
|
|
18
|
+
* @property {(depId: TeqFw_Di_DepId__DTO) => {found: boolean, value: unknown}} findMock
|
|
18
19
|
* @property {TeqFw_Di_Resolver} resolver
|
|
19
20
|
* @property {{log(message: string): void}|null} [logger]
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
|
-
* @typedef {{depId: TeqFw_Di_DepId__DTO, namespace: object}}
|
|
24
|
+
* @typedef {{depId: TeqFw_Di_DepId__DTO, namespace: object|null, dependencies: Map<string, string>, mock: {found: boolean, value: unknown}}} TeqFw_Di_Container_GraphResolver_Node
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
27
|
import {buildDependencyKey} from '../Internal/DependencyKey.mjs';
|
|
27
28
|
import {readDepsDecl} from '../Internal/DepsDecl.mjs';
|
|
28
29
|
|
|
29
|
-
export default class
|
|
30
|
+
export default class TeqFw_Di_Container_GraphResolver {
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
|
-
* @param {
|
|
33
|
+
* @param {TeqFw_Di_Container_GraphResolver_Dependencies} deps
|
|
33
34
|
*/
|
|
34
|
-
constructor({parser, resolver, logger = null}) {
|
|
35
|
+
constructor({canonicalize, parser, findMock = () => ({found: false, value: undefined}), resolver, logger = null}) {
|
|
36
|
+
canonicalize ??= parser.parse.bind(parser);
|
|
35
37
|
/** @type {{log(message: string): void}|null} */
|
|
36
38
|
const log = logger;
|
|
37
39
|
|
|
@@ -43,7 +45,7 @@ export default class TeqFw_Di_Container_Resolve_GraphResolver {
|
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
47
|
* @param {TeqFw_Di_DepId__DTO} depId
|
|
46
|
-
* @param {Map<string,
|
|
48
|
+
* @param {Map<string, TeqFw_Di_Container_GraphResolver_Node>} out
|
|
47
49
|
* @param {Set<string>} stack
|
|
48
50
|
* @param {string[]} chain
|
|
49
51
|
* @returns {Promise<void>}
|
|
@@ -61,19 +63,28 @@ export default class TeqFw_Di_Container_Resolve_GraphResolver {
|
|
|
61
63
|
const key = makeNodeKey(depId);
|
|
62
64
|
if (out.has(key)) return;
|
|
63
65
|
|
|
66
|
+
const mock = findMock(depId);
|
|
67
|
+
if (mock.found) {
|
|
68
|
+
out.set(key, {depId, namespace: null, dependencies: new Map(), mock});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
stack.add(identity);
|
|
65
73
|
chain.push(identity);
|
|
66
74
|
try {
|
|
67
75
|
/** @type {object} */
|
|
68
76
|
const namespace = await resolver.resolve(depId);
|
|
69
77
|
if (log) log.log(`GraphResolver.walk: resolved '${key}'.`);
|
|
70
|
-
|
|
78
|
+
/** @type {Map<string, string>} */
|
|
79
|
+
const dependencies = new Map();
|
|
80
|
+
out.set(key, {depId, namespace, dependencies, mock});
|
|
71
81
|
|
|
72
82
|
/** @type {Record<string, unknown>} */
|
|
73
83
|
const depsMap = readDepsDecl(namespace, depId);
|
|
74
|
-
for (const
|
|
84
|
+
for (const [name, nextSpecifier] of Object.entries(depsMap)) {
|
|
75
85
|
/** @type {TeqFw_Di_DepId__DTO} */
|
|
76
|
-
const nextDepId =
|
|
86
|
+
const nextDepId = canonicalize(/** @type {string} */ (nextSpecifier));
|
|
87
|
+
dependencies.set(name, makeNodeKey(nextDepId));
|
|
77
88
|
if (log) log.log(`GraphResolver.walk: edge '${key}' -> '${nextDepId.platform}::${nextDepId.moduleName}'.`);
|
|
78
89
|
await walk(nextDepId, out, stack, chain);
|
|
79
90
|
}
|
|
@@ -87,10 +98,10 @@ export default class TeqFw_Di_Container_Resolve_GraphResolver {
|
|
|
87
98
|
* Resolves full dependency graph for a root depId.
|
|
88
99
|
*
|
|
89
100
|
* @param {TeqFw_Di_DepId__DTO} depId
|
|
90
|
-
* @returns {Promise<Map<string,
|
|
101
|
+
* @returns {Promise<Map<string, TeqFw_Di_Container_GraphResolver_Node>>}
|
|
91
102
|
*/
|
|
92
103
|
this.resolve = async function (depId) {
|
|
93
|
-
/** @type {Map<string,
|
|
104
|
+
/** @type {Map<string, TeqFw_Di_Container_GraphResolver_Node>} */
|
|
94
105
|
const out = new Map();
|
|
95
106
|
/** @type {Set<string>} */
|
|
96
107
|
const stack = new Set();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @namespace
|
|
4
|
+
* @namespace TeqFw_Di_Container_Instantiate
|
|
5
5
|
* @description Instantiates selected exports using composition rules.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -25,7 +25,7 @@ import TeqFw_Di_Enum_Composition from '../Enum/Composition.mjs';
|
|
|
25
25
|
/**
|
|
26
26
|
* @typedef {CallableFactory | ConstructableFactory} Factory
|
|
27
27
|
*/
|
|
28
|
-
export default class
|
|
28
|
+
export default class TeqFw_Di_Container_Instantiate {
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Creates instantiator instance.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @namespace
|
|
4
|
+
* @namespace TeqFw_Di_Container_Lifecycle
|
|
5
5
|
* @description Lifecycle policy cache for produced values.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -17,7 +17,7 @@ import {buildDependencyKey} from '../Internal/DependencyKey.mjs';
|
|
|
17
17
|
* - transient values are never cached;
|
|
18
18
|
* - as-is composition is returned as produced without lifecycle caching.
|
|
19
19
|
*/
|
|
20
|
-
export default class
|
|
20
|
+
export default class TeqFw_Di_Container_Lifecycle {
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Creates lifecycle registry instance.
|
|
@@ -6,35 +6,32 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {buildDependencyKey} from '../Internal/DependencyKey.mjs';
|
|
9
|
-
import {readDepsDecl} from '../Internal/DepsDecl.mjs';
|
|
10
9
|
import {makePromiseSafe} from '../Internal/PromiseSafe.mjs';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* @typedef {object} TeqFw_Di_Container_Pipeline_Context
|
|
14
|
-
* @property {TeqFw_Di_Parser} parser
|
|
15
13
|
* @property {TeqFw_Di_Resolver|undefined} resolver
|
|
16
|
-
* @property {
|
|
17
|
-
* @property {
|
|
18
|
-
* @property {
|
|
19
|
-
* @property {
|
|
14
|
+
* @property {TeqFw_Di_Container_GraphResolver|undefined} graphResolver
|
|
15
|
+
* @property {TeqFw_Di_Container_Lifecycle|undefined} lifecycle
|
|
16
|
+
* @property {TeqFw_Di_Container_Instantiate} instantiator
|
|
17
|
+
* @property {TeqFw_Di_Container_Executor} wrapperExecutor
|
|
20
18
|
* @property {{log(message: string): void, error(message: string, error?: unknown): void}} logger
|
|
21
19
|
* @property {boolean} testMode
|
|
22
20
|
* @property {Map<string, unknown>} mockRegistry
|
|
23
21
|
* @property {(value: unknown) => unknown} freeze
|
|
24
|
-
* @property {(
|
|
25
|
-
* @property {(value: unknown) => unknown} applyPostprocess
|
|
22
|
+
* @property {(specifier: string) => TeqFw_Di_DepId__DTO} canonicalize
|
|
23
|
+
* @property {(value: unknown, depId: TeqFw_Di_DepId__DTO) => unknown} applyPostprocess
|
|
26
24
|
*/
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
|
-
* Executes full container
|
|
27
|
+
* Executes the full container pipeline for a Dependency Specifier.
|
|
30
28
|
*
|
|
31
29
|
* @param {TeqFw_Di_Container_Pipeline_Context} ctx
|
|
32
|
-
* @param {string}
|
|
30
|
+
* @param {string} specifier
|
|
33
31
|
* @returns {Promise<any>}
|
|
34
32
|
*/
|
|
35
|
-
export async function executeContainerPipeline(ctx,
|
|
33
|
+
export async function executeContainerPipeline(ctx, specifier) {
|
|
36
34
|
const {
|
|
37
|
-
parser,
|
|
38
35
|
resolver,
|
|
39
36
|
graphResolver,
|
|
40
37
|
lifecycle,
|
|
@@ -44,7 +41,7 @@ export async function executeContainerPipeline(ctx, cdc) {
|
|
|
44
41
|
testMode,
|
|
45
42
|
mockRegistry,
|
|
46
43
|
freeze,
|
|
47
|
-
|
|
44
|
+
canonicalize,
|
|
48
45
|
applyPostprocess,
|
|
49
46
|
} = ctx;
|
|
50
47
|
|
|
@@ -58,14 +55,14 @@ export async function executeContainerPipeline(ctx, cdc) {
|
|
|
58
55
|
/** @type {string} */
|
|
59
56
|
let stage = 'start';
|
|
60
57
|
try {
|
|
61
|
-
logger.log(`Container.get:
|
|
58
|
+
logger.log(`Container.get: specifier='${specifier}'.`);
|
|
62
59
|
stage = 'parse';
|
|
63
60
|
logger.log('Container.pipeline: parse:entry.');
|
|
64
|
-
const
|
|
61
|
+
const root = canonicalize(specifier);
|
|
62
|
+
const parsed = root;
|
|
65
63
|
logger.log(`Container.pipeline: parse:exit '${parsed.platform}::${parsed.moduleName}'.`);
|
|
66
64
|
stage = 'preprocess';
|
|
67
65
|
logger.log('Container.pipeline: preprocess:entry.');
|
|
68
|
-
const root = applyPreprocess(parsed);
|
|
69
66
|
logger.log(`Container.pipeline: preprocess:exit '${root.platform}::${root.moduleName}'.`);
|
|
70
67
|
if (testMode === true) {
|
|
71
68
|
stage = 'mock';
|
|
@@ -95,6 +92,10 @@ export async function executeContainerPipeline(ctx, cdc) {
|
|
|
95
92
|
if (built.has(key)) return built.get(key);
|
|
96
93
|
if (!graph.has(key)) throw new Error(`Resolved graph node is missing for '${key}'.`);
|
|
97
94
|
const node = graph.get(key);
|
|
95
|
+
if (node.mock?.found) {
|
|
96
|
+
stage = 'freeze';
|
|
97
|
+
return freeze(node.mock.value);
|
|
98
|
+
}
|
|
98
99
|
stage = 'lifecycle';
|
|
99
100
|
logger.log(`Container.pipeline: lifecycle:entry '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
100
101
|
const value = lifecycle.apply(node.depId, function () {
|
|
@@ -102,17 +103,14 @@ export async function executeContainerPipeline(ctx, cdc) {
|
|
|
102
103
|
logger.log(`Container.pipeline: instantiate:entry '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
103
104
|
/** @type {Record<string, unknown>} */
|
|
104
105
|
const deps = {};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
for (const [name, cdcValue] of Object.entries(depsDecl)) {
|
|
108
|
-
const childDepId = parser.parse(/** @type {string} */ (cdcValue));
|
|
109
|
-
deps[name] = build(getKey(childDepId));
|
|
106
|
+
for (const [name, childKey] of (node.dependencies ?? [])) {
|
|
107
|
+
deps[name] = build(childKey);
|
|
110
108
|
}
|
|
111
109
|
const instantiated = instantiator.instantiate(node.depId, node.namespace, deps);
|
|
112
110
|
logger.log(`Container.pipeline: instantiate:exit '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
113
111
|
stage = 'postprocess';
|
|
114
112
|
logger.log(`Container.pipeline: postprocess:entry '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
115
|
-
const postprocessed = applyPostprocess(instantiated);
|
|
113
|
+
const postprocessed = applyPostprocess(instantiated, node.depId);
|
|
116
114
|
logger.log(`Container.pipeline: postprocess:exit '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
117
115
|
const wrapped = wrapperExecutor.execute(node.depId, postprocessed, node.namespace);
|
|
118
116
|
stage = 'freeze';
|