@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
package/src/Container.mjs
CHANGED
|
@@ -5,425 +5,52 @@
|
|
|
5
5
|
* @description DI container orchestration entry point.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import {Factory as TeqFw_Di_Dto_Resolver_Config_Factory} from './Dto/Resolver/Config.mjs';
|
|
10
|
-
import TeqFw_Di_Resolver from './Container/Resolver.mjs';
|
|
11
|
-
import TeqFw_Di_Container_Resolve_GraphResolver from './Container/Resolve/GraphResolver.mjs';
|
|
12
|
-
import TeqFw_Di_Container_Instantiate_Instantiator from './Container/Instantiate/Instantiator.mjs';
|
|
13
|
-
import TeqFw_Di_Container_Lifecycle_Registry from './Container/Lifecycle/Registry.mjs';
|
|
14
|
-
import TeqFw_Di_Container_Wrapper_Executor from './Container/Wrapper/Executor.mjs';
|
|
15
|
-
import TeqFw_Di_Internal_Logger, {TeqFw_Di_Internal_Logger_Noop} from './Internal/Logger.mjs';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @typedef {'notConfigured'|'operational'|'failed'} TeqFw_Di_Container_State
|
|
19
|
-
*/
|
|
8
|
+
import TeqFw_Di_Container_Runtime from './Container/Runtime.mjs';
|
|
20
9
|
|
|
21
10
|
/**
|
|
22
11
|
* Container orchestration boundary.
|
|
23
12
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* lifecycle -> freeze -> return.
|
|
13
|
+
* Delegates all runtime behavior to an internal runtime coordinator while
|
|
14
|
+
* preserving the public API and contract surface.
|
|
27
15
|
*
|
|
28
16
|
* @LLM-DOC
|
|
29
17
|
* Spec: ./ctx/docs/code/components/container.md
|
|
30
18
|
*/
|
|
31
19
|
export default class TeqFw_Di_Container {
|
|
32
|
-
/**
|
|
33
|
-
* Creates container instance in `notConfigured` state.
|
|
34
|
-
*/
|
|
35
20
|
constructor() {
|
|
36
|
-
/** @type {
|
|
37
|
-
|
|
38
|
-
/** @type {((depId: TeqFw_Di_DepId__DTO) => TeqFw_Di_DepId__DTO)[]} */
|
|
39
|
-
const preprocess = [];
|
|
40
|
-
/** @type {((value: unknown) => unknown)[]} */
|
|
41
|
-
const postprocess = [];
|
|
42
|
-
/** @type {TeqFw_Di_Dto_Resolver_Config_Namespace__DTO[]} */
|
|
43
|
-
const namespaceRoots = [];
|
|
44
|
-
/** @type {Map<string, unknown>} */
|
|
45
|
-
const mockRegistry = new Map();
|
|
46
|
-
/** @type {WeakMap<object, object>} */
|
|
47
|
-
const promiseSafeCache = new WeakMap();
|
|
48
|
-
let testMode = false;
|
|
49
|
-
let loggingEnabled = false;
|
|
50
|
-
|
|
51
|
-
/** @type {TeqFw_Di_Def_Parser} */
|
|
52
|
-
let parser = new TeqFw_Di_Def_Parser();
|
|
53
|
-
/** @type {TeqFw_Di_Dto_Resolver_Config__Factory} */
|
|
54
|
-
const configFactory = new TeqFw_Di_Dto_Resolver_Config_Factory();
|
|
55
|
-
/** @type {TeqFw_Di_Resolver|undefined} */
|
|
56
|
-
let resolver;
|
|
57
|
-
/** @type {TeqFw_Di_Container_Resolve_GraphResolver|undefined} */
|
|
58
|
-
let graphResolver;
|
|
59
|
-
/** @type {TeqFw_Di_Container_Lifecycle_Registry|undefined} */
|
|
60
|
-
let lifecycle;
|
|
61
|
-
/** @type {{log(message: string): void, error(message: string, error?: unknown): void}} */
|
|
62
|
-
let logger = TeqFw_Di_Internal_Logger_Noop;
|
|
63
|
-
/** @type {TeqFw_Di_Container_Instantiate_Instantiator} */
|
|
64
|
-
const instantiator = new TeqFw_Di_Container_Instantiate_Instantiator();
|
|
65
|
-
/** @type {TeqFw_Di_Container_Wrapper_Executor} */
|
|
66
|
-
const wrapperExecutor = new TeqFw_Di_Container_Wrapper_Executor();
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @param {TeqFw_Di_DepId__DTO} depId
|
|
70
|
-
* @returns {string}
|
|
71
|
-
*/
|
|
72
|
-
const getKey = function (depId) {
|
|
73
|
-
const exportName = depId.exportName === null ? '' : depId.exportName;
|
|
74
|
-
const life = depId.life === null ? '' : depId.life;
|
|
75
|
-
const wrappers = Array.isArray(depId.wrappers) ? depId.wrappers.join('|') : '';
|
|
76
|
-
return [
|
|
77
|
-
depId.platform,
|
|
78
|
-
depId.moduleName,
|
|
79
|
-
exportName,
|
|
80
|
-
depId.composition,
|
|
81
|
-
life,
|
|
82
|
-
wrappers,
|
|
83
|
-
].join('::');
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Canonical structural identity excluding `origin`.
|
|
88
|
-
*
|
|
89
|
-
* @param {TeqFw_Di_DepId__DTO} depId
|
|
90
|
-
* @returns {string}
|
|
91
|
-
*/
|
|
92
|
-
const getMockKey = function (depId) {
|
|
93
|
-
return getKey(depId);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Freezes values that support freezing.
|
|
98
|
-
*
|
|
99
|
-
* @param {unknown} value
|
|
100
|
-
* @returns {unknown}
|
|
101
|
-
*/
|
|
102
|
-
const freeze = function (value) {
|
|
103
|
-
if ((value === null) || (value === undefined)) return value;
|
|
104
|
-
const type = typeof value;
|
|
105
|
-
if ((type !== 'object') && (type !== 'function')) return value;
|
|
106
|
-
if (Object.prototype.toString.call(value) === '[object Module]') return value;
|
|
107
|
-
if (Object.isFrozen(value)) return value;
|
|
108
|
-
try {
|
|
109
|
-
Object.freeze(value);
|
|
110
|
-
} catch (error) {
|
|
111
|
-
logger.log(`Container.freeze: skipped (${String(error)}).`);
|
|
112
|
-
}
|
|
113
|
-
return value;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Ensures async `get()` can resolve objects whose proxy `get('then')` throws.
|
|
118
|
-
*
|
|
119
|
-
* @param {unknown} value
|
|
120
|
-
* @returns {unknown}
|
|
121
|
-
*/
|
|
122
|
-
const asPromiseSafe = function (value) {
|
|
123
|
-
if ((value === null) || (value === undefined)) return value;
|
|
124
|
-
const type = typeof value;
|
|
125
|
-
if ((type !== 'object') && (type !== 'function')) return value;
|
|
126
|
-
const obj = /** @type {object} */ (value);
|
|
127
|
-
if (promiseSafeCache.has(obj)) return promiseSafeCache.get(obj);
|
|
128
|
-
try {
|
|
129
|
-
void Reflect.get(obj, 'then');
|
|
130
|
-
return value;
|
|
131
|
-
} catch {
|
|
132
|
-
const wrapped = new Proxy(obj, {
|
|
133
|
-
get(target, property, receiver) {
|
|
134
|
-
if (property === 'then') return undefined;
|
|
135
|
-
return Reflect.get(target, property, receiver);
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
promiseSafeCache.set(obj, wrapped);
|
|
139
|
-
return wrapped;
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Applies ordered preprocess pipeline.
|
|
145
|
-
*
|
|
146
|
-
* @param {TeqFw_Di_DepId__DTO} depId
|
|
147
|
-
* @returns {TeqFw_Di_DepId__DTO}
|
|
148
|
-
*/
|
|
149
|
-
const applyPreprocess = function (depId) {
|
|
150
|
-
/** @type {TeqFw_Di_DepId__DTO} */
|
|
151
|
-
let current = depId;
|
|
152
|
-
for (const fn of preprocess) {
|
|
153
|
-
current = fn(current);
|
|
154
|
-
}
|
|
155
|
-
return current;
|
|
156
|
-
};
|
|
21
|
+
/** @type {TeqFw_Di_Container_Runtime} */
|
|
22
|
+
const runtime = new TeqFw_Di_Container_Runtime();
|
|
157
23
|
|
|
158
|
-
/**
|
|
159
|
-
* Applies ordered postprocess pipeline.
|
|
160
|
-
*
|
|
161
|
-
* @param {unknown} value
|
|
162
|
-
* @returns {unknown}
|
|
163
|
-
*/
|
|
164
|
-
const applyPostprocess = function (value) {
|
|
165
|
-
/** @type {unknown} */
|
|
166
|
-
let current = value;
|
|
167
|
-
for (const fn of postprocess) {
|
|
168
|
-
current = fn(current);
|
|
169
|
-
}
|
|
170
|
-
return current;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* @param {object} namespace
|
|
175
|
-
* @param {TeqFw_Di_DepId__DTO} depId
|
|
176
|
-
* @returns {Record<string, unknown>}
|
|
177
|
-
*/
|
|
178
|
-
const readDepsDecl = function (namespace, depId) {
|
|
179
|
-
/** @type {unknown} */
|
|
180
|
-
const deps = Reflect.get(namespace, '__deps__');
|
|
181
|
-
if (deps === undefined) return {};
|
|
182
|
-
if ((deps !== null) && (typeof deps === 'object') && !Array.isArray(deps)) {
|
|
183
|
-
const exportName = depId.exportName === null ? 'default' : depId.exportName;
|
|
184
|
-
const exportScoped = Reflect.get(/** @type {object} */ (deps), exportName);
|
|
185
|
-
if ((exportScoped !== undefined) && (exportScoped !== null) && (typeof exportScoped === 'object') && !Array.isArray(exportScoped)) {
|
|
186
|
-
return /** @type {Record<string, unknown>} */ (exportScoped);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return /** @type {Record<string, unknown>} */ (deps);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* @returns {void}
|
|
194
|
-
*/
|
|
195
|
-
const assertBuilderStage = function () {
|
|
196
|
-
if (state !== 'notConfigured') throw new Error('Container configuration is locked.');
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Emits builder-stage diagnostics only when logging is enabled.
|
|
201
|
-
*
|
|
202
|
-
* @param {string} message
|
|
203
|
-
* @returns {void}
|
|
204
|
-
*/
|
|
205
|
-
const logBuilder = function (message) {
|
|
206
|
-
if (!loggingEnabled) return;
|
|
207
|
-
logger.log(`Container.builder: ${message}`);
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Lazily creates infrastructure and locks builder configuration.
|
|
212
|
-
*
|
|
213
|
-
* @returns {void}
|
|
214
|
-
*/
|
|
215
|
-
const initializeInfrastructure = function () {
|
|
216
|
-
if (state !== 'notConfigured') return;
|
|
217
|
-
logger.log('Container.transition: notConfigured -> operational.');
|
|
218
|
-
state = 'operational';
|
|
219
|
-
const resolverConfig = configFactory.create({
|
|
220
|
-
namespaces: namespaceRoots,
|
|
221
|
-
});
|
|
222
|
-
if (typeof parser.setLogger === 'function') parser.setLogger(logger);
|
|
223
|
-
resolver = new TeqFw_Di_Resolver({config: resolverConfig, logger});
|
|
224
|
-
graphResolver = new TeqFw_Di_Container_Resolve_GraphResolver({parser, resolver, logger});
|
|
225
|
-
lifecycle = new TeqFw_Di_Container_Lifecycle_Registry(logger);
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Registers preprocess extension before first resolution.
|
|
230
|
-
*
|
|
231
|
-
* @param {(depId: TeqFw_Di_DepId__DTO) => TeqFw_Di_DepId__DTO} fn
|
|
232
|
-
* @returns {void}
|
|
233
|
-
*/
|
|
234
24
|
this.addPreprocess = function (fn) {
|
|
235
|
-
|
|
236
|
-
logBuilder('addPreprocess().');
|
|
237
|
-
preprocess.push(fn);
|
|
25
|
+
return runtime.addPreprocess(fn);
|
|
238
26
|
};
|
|
239
27
|
|
|
240
|
-
/**
|
|
241
|
-
* Registers postprocess extension before first resolution.
|
|
242
|
-
*
|
|
243
|
-
* @param {(value: unknown) => unknown} fn
|
|
244
|
-
* @returns {void}
|
|
245
|
-
*/
|
|
246
28
|
this.addPostprocess = function (fn) {
|
|
247
|
-
|
|
248
|
-
logBuilder('addPostprocess().');
|
|
249
|
-
postprocess.push(fn);
|
|
29
|
+
return runtime.addPostprocess(fn);
|
|
250
30
|
};
|
|
251
31
|
|
|
252
|
-
/**
|
|
253
|
-
* Replaces default parser before first resolution.
|
|
254
|
-
*
|
|
255
|
-
* @param {TeqFw_Di_Def_Parser} next
|
|
256
|
-
* @returns {void}
|
|
257
|
-
*/
|
|
258
32
|
this.setParser = function (next) {
|
|
259
|
-
|
|
260
|
-
parser = next;
|
|
261
|
-
if (typeof parser.setLogger === 'function') parser.setLogger(loggingEnabled ? logger : null);
|
|
262
|
-
logBuilder('setParser().');
|
|
33
|
+
return runtime.setParser(next);
|
|
263
34
|
};
|
|
264
35
|
|
|
265
|
-
/**
|
|
266
|
-
* Registers one resolver namespace root before first resolution.
|
|
267
|
-
*
|
|
268
|
-
* @param {string} prefix
|
|
269
|
-
* @param {string} target
|
|
270
|
-
* @param {string} defaultExt
|
|
271
|
-
* @returns {void}
|
|
272
|
-
*/
|
|
273
36
|
this.addNamespaceRoot = function (prefix, target, defaultExt) {
|
|
274
|
-
|
|
275
|
-
logBuilder(`addNamespaceRoot('${prefix}').`);
|
|
276
|
-
namespaceRoots.push({prefix, target, defaultExt});
|
|
37
|
+
return runtime.addNamespaceRoot(prefix, target, defaultExt);
|
|
277
38
|
};
|
|
278
39
|
|
|
279
|
-
/**
|
|
280
|
-
* Enables mock registration capability for test scenarios.
|
|
281
|
-
*
|
|
282
|
-
* @returns {void}
|
|
283
|
-
*/
|
|
284
40
|
this.enableTestMode = function () {
|
|
285
|
-
|
|
286
|
-
logBuilder('enableTestMode().');
|
|
287
|
-
testMode = true;
|
|
41
|
+
return runtime.enableTestMode();
|
|
288
42
|
};
|
|
289
43
|
|
|
290
|
-
/**
|
|
291
|
-
* Enables diagnostic logging before first resolution.
|
|
292
|
-
*
|
|
293
|
-
* @returns {void}
|
|
294
|
-
*/
|
|
295
44
|
this.enableLogging = function () {
|
|
296
|
-
|
|
297
|
-
if (loggingEnabled) return;
|
|
298
|
-
loggingEnabled = true;
|
|
299
|
-
logger = new TeqFw_Di_Internal_Logger();
|
|
300
|
-
if (typeof parser.setLogger === 'function') parser.setLogger(logger);
|
|
301
|
-
logger.log('Container.builder: enableLogging().');
|
|
45
|
+
return runtime.enableLogging();
|
|
302
46
|
};
|
|
303
47
|
|
|
304
|
-
/**
|
|
305
|
-
* Registers test mock by CDC structural identity.
|
|
306
|
-
*
|
|
307
|
-
* @param {string} cdc
|
|
308
|
-
* @param {unknown} mock
|
|
309
|
-
* @returns {void}
|
|
310
|
-
*/
|
|
311
48
|
this.register = function (cdc, mock) {
|
|
312
|
-
|
|
313
|
-
logBuilder(`register('${cdc}').`);
|
|
314
|
-
if (testMode !== true) throw new Error('Container test mode is disabled.');
|
|
315
|
-
const depId = parser.parse(cdc);
|
|
316
|
-
mockRegistry.set(getMockKey(depId), mock);
|
|
49
|
+
return runtime.register(cdc, mock);
|
|
317
50
|
};
|
|
318
51
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
*
|
|
322
|
-
* @param {string} cdc
|
|
323
|
-
* @returns {Promise<any>}
|
|
324
|
-
*/
|
|
325
|
-
this.get = async function (cdc) {
|
|
326
|
-
if (state === 'failed') {
|
|
327
|
-
logger.error(`Container.get: rejected in failed state cdc='${cdc}'.`);
|
|
328
|
-
throw new Error('Container is in failed state.');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/** @type {string} */
|
|
332
|
-
let stage = 'start';
|
|
333
|
-
try {
|
|
334
|
-
logger.log(`Container.get: cdc='${cdc}'.`);
|
|
335
|
-
initializeInfrastructure();
|
|
336
|
-
logger.log(`Container.state: '${state}'.`);
|
|
337
|
-
/** @type {TeqFw_Di_DepId__DTO} */
|
|
338
|
-
stage = 'parse';
|
|
339
|
-
logger.log('Container.pipeline: parse:entry.');
|
|
340
|
-
const parsed = parser.parse(cdc);
|
|
341
|
-
logger.log(`Container.pipeline: parse:exit '${parsed.platform}::${parsed.moduleName}'.`);
|
|
342
|
-
/** @type {TeqFw_Di_DepId__DTO} */
|
|
343
|
-
stage = 'preprocess';
|
|
344
|
-
logger.log('Container.pipeline: preprocess:entry.');
|
|
345
|
-
const root = applyPreprocess(parsed);
|
|
346
|
-
logger.log(`Container.pipeline: preprocess:exit '${root.platform}::${root.moduleName}'.`);
|
|
347
|
-
if (testMode === true) {
|
|
348
|
-
stage = 'mock';
|
|
349
|
-
logger.log('Container.pipeline: mock-lookup:entry.');
|
|
350
|
-
const key = getMockKey(root);
|
|
351
|
-
if (mockRegistry.has(key)) {
|
|
352
|
-
logger.log(`Container.pipeline: mock-lookup:hit '${key}'.`);
|
|
353
|
-
stage = 'freeze';
|
|
354
|
-
logger.log('Container.pipeline: freeze:entry.');
|
|
355
|
-
const frozenMock = freeze(mockRegistry.get(key));
|
|
356
|
-
logger.log('Container.pipeline: freeze:exit.');
|
|
357
|
-
logger.log('Container.pipeline: return:success.');
|
|
358
|
-
return asPromiseSafe(frozenMock);
|
|
359
|
-
}
|
|
360
|
-
logger.log(`Container.pipeline: mock-lookup:miss '${key}'.`);
|
|
361
|
-
} else {
|
|
362
|
-
logger.log('Container.pipeline: mock-lookup:disabled.');
|
|
363
|
-
}
|
|
364
|
-
/** @type {Map<string, {depId: TeqFw_Di_DepId__DTO, namespace: object}>} */
|
|
365
|
-
stage = 'resolve';
|
|
366
|
-
logger.log('Container.pipeline: resolve:entry.');
|
|
367
|
-
const graph = await graphResolver.resolve(root);
|
|
368
|
-
logger.log(`Container.pipeline: resolve:exit nodes=${graph.size}.`);
|
|
369
|
-
/** @type {Map<string, unknown>} */
|
|
370
|
-
const built = new Map();
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* @param {string} key
|
|
374
|
-
* @returns {unknown}
|
|
375
|
-
*/
|
|
376
|
-
const build = function (key) {
|
|
377
|
-
if (built.has(key)) return built.get(key);
|
|
378
|
-
if (!graph.has(key)) throw new Error(`Resolved graph node is missing for '${key}'.`);
|
|
379
|
-
|
|
380
|
-
const node = graph.get(key);
|
|
381
|
-
stage = 'lifecycle';
|
|
382
|
-
logger.log(`Container.pipeline: lifecycle:entry '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
383
|
-
const value = lifecycle.apply(node.depId, function () {
|
|
384
|
-
stage = 'instantiate';
|
|
385
|
-
logger.log(`Container.pipeline: instantiate:entry '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
386
|
-
/** @type {Record<string, unknown>} */
|
|
387
|
-
const deps = {};
|
|
388
|
-
/** @type {Record<string, unknown>} */
|
|
389
|
-
const depsDecl = readDepsDecl(node.namespace, node.depId);
|
|
390
|
-
for (const [name, cdc] of Object.entries(depsDecl)) {
|
|
391
|
-
/** @type {string} */
|
|
392
|
-
const childCdc = /** @type {string} */ (cdc);
|
|
393
|
-
/** @type {TeqFw_Di_DepId__DTO} */
|
|
394
|
-
const childDepId = parser.parse(childCdc);
|
|
395
|
-
deps[name] = build(getKey(childDepId));
|
|
396
|
-
}
|
|
397
|
-
/** @type {unknown} */
|
|
398
|
-
const instantiated = instantiator.instantiate(node.depId, node.namespace, deps);
|
|
399
|
-
logger.log(`Container.pipeline: instantiate:exit '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
400
|
-
/** @type {unknown} */
|
|
401
|
-
stage = 'postprocess';
|
|
402
|
-
logger.log(`Container.pipeline: postprocess:entry '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
403
|
-
const postprocessed = applyPostprocess(instantiated);
|
|
404
|
-
logger.log(`Container.pipeline: postprocess:exit '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
405
|
-
/** @type {unknown} */
|
|
406
|
-
const wrapped = wrapperExecutor.execute(node.depId, postprocessed, node.namespace);
|
|
407
|
-
stage = 'freeze';
|
|
408
|
-
logger.log(`Container.pipeline: freeze:entry '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
409
|
-
const frozen = freeze(wrapped);
|
|
410
|
-
logger.log(`Container.pipeline: freeze:exit '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
411
|
-
return frozen;
|
|
412
|
-
});
|
|
413
|
-
logger.log(`Container.pipeline: lifecycle:exit '${node.depId.platform}::${node.depId.moduleName}'.`);
|
|
414
|
-
built.set(key, value);
|
|
415
|
-
return value;
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
const result = build(getKey(root));
|
|
419
|
-
logger.log('Container.pipeline: return:success.');
|
|
420
|
-
return asPromiseSafe(result);
|
|
421
|
-
} catch (error) {
|
|
422
|
-
logger.error(`Container.pipeline: failed at stage='${stage}'.`, error);
|
|
423
|
-
logger.log('Container.transition: operational -> failed.');
|
|
424
|
-
state = 'failed';
|
|
425
|
-
throw error;
|
|
426
|
-
}
|
|
52
|
+
this.get = function (cdc) {
|
|
53
|
+
return runtime.get(cdc);
|
|
427
54
|
};
|
|
428
55
|
}
|
|
429
56
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @namespace TeqFw_Di_Internal_DependencyKey
|
|
5
|
+
* @description Shared helper for structural dependency identity keys.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Builds deterministic structural key for dependency identity.
|
|
10
|
+
*
|
|
11
|
+
* The key excludes `origin` and preserves field order used by cache and graph
|
|
12
|
+
* lookups across runtime components.
|
|
13
|
+
*
|
|
14
|
+
* @param {TeqFw_Di_DepId__DTO} depId Dependency identity DTO.
|
|
15
|
+
* @returns {string} Canonical structural key.
|
|
16
|
+
*/
|
|
17
|
+
export function buildDependencyKey(depId) {
|
|
18
|
+
const exportName = depId.exportName === null ? '' : depId.exportName;
|
|
19
|
+
const life = depId.life === null ? '' : depId.life;
|
|
20
|
+
const wrappers = Array.isArray(depId.wrappers) ? depId.wrappers.join('|') : '';
|
|
21
|
+
return [
|
|
22
|
+
depId.platform,
|
|
23
|
+
depId.moduleName,
|
|
24
|
+
exportName,
|
|
25
|
+
depId.composition,
|
|
26
|
+
life,
|
|
27
|
+
wrappers,
|
|
28
|
+
].join('::');
|
|
29
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @namespace TeqFw_Di_Internal_DepsDecl
|
|
5
|
+
* @description Shared helper for reading `__deps__` declarations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reads dependency declaration map for the selected export of a module namespace.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} namespace Loaded module namespace.
|
|
12
|
+
* @param {TeqFw_Di_DepId__DTO} depId Canonical dependency identity.
|
|
13
|
+
* @returns {Record<string, unknown>} Dependency declaration map.
|
|
14
|
+
*/
|
|
15
|
+
export function readDepsDecl(namespace, depId) {
|
|
16
|
+
/** @type {unknown} */
|
|
17
|
+
const deps = Reflect.get(namespace, '__deps__');
|
|
18
|
+
if (deps === undefined) return {};
|
|
19
|
+
if ((deps === null) || (typeof deps !== 'object') || Array.isArray(deps)) {
|
|
20
|
+
throw new Error('__deps__ must be a plain object.');
|
|
21
|
+
}
|
|
22
|
+
const exportName = depId.exportName === null ? 'default' : depId.exportName;
|
|
23
|
+
const exportScoped = Reflect.get(/** @type {object} */ (deps), exportName);
|
|
24
|
+
if ((exportScoped !== undefined) && (exportScoped !== null) && (typeof exportScoped === 'object') && !Array.isArray(exportScoped)) {
|
|
25
|
+
const values = Object.values(/** @type {Record<string, unknown>} */ (exportScoped));
|
|
26
|
+
if (!values.every((value) => typeof value === 'string')) {
|
|
27
|
+
throw new Error('__deps__ export entries must map dependency names to CDC strings.');
|
|
28
|
+
}
|
|
29
|
+
return /** @type {Record<string, unknown>} */ (exportScoped);
|
|
30
|
+
}
|
|
31
|
+
if (exportName === 'default') {
|
|
32
|
+
const values = Object.values(/** @type {Record<string, unknown>} */ (deps));
|
|
33
|
+
if (values.every((value) => typeof value === 'string')) {
|
|
34
|
+
return /** @type {Record<string, unknown>} */ (deps);
|
|
35
|
+
}
|
|
36
|
+
if (values.every((value) => (value !== null) && (typeof value === 'object') && !Array.isArray(value))) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
throw new Error('__deps__ must be either flat or export-scoped.');
|
|
40
|
+
}
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @namespace TeqFw_Di_Internal_PromiseSafe
|
|
5
|
+
* @description Helper for protecting async return values from unsafe `then` access.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** @type {WeakMap<object, object>} */
|
|
9
|
+
const cache = new WeakMap();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns a value that is safe to hand to an async caller.
|
|
13
|
+
*
|
|
14
|
+
* If a proxy throws on `then` access, the value is wrapped in a proxy that
|
|
15
|
+
* hides `then` while preserving all other property access.
|
|
16
|
+
*
|
|
17
|
+
* @param {unknown} value Input value.
|
|
18
|
+
* @returns {unknown} Promise-safe value.
|
|
19
|
+
*/
|
|
20
|
+
export function makePromiseSafe(value) {
|
|
21
|
+
if ((value === null) || (value === undefined)) return value;
|
|
22
|
+
const type = typeof value;
|
|
23
|
+
if ((type !== 'object') && (type !== 'function')) return value;
|
|
24
|
+
const obj = /** @type {object} */ (value);
|
|
25
|
+
if (cache.has(obj)) return cache.get(obj);
|
|
26
|
+
try {
|
|
27
|
+
void Reflect.get(obj, 'then');
|
|
28
|
+
return value;
|
|
29
|
+
} catch {
|
|
30
|
+
const wrapped = new Proxy(obj, {
|
|
31
|
+
get(target, property, receiver) {
|
|
32
|
+
if (property === 'then') return undefined;
|
|
33
|
+
return Reflect.get(target, property, receiver);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
cache.set(obj, wrapped);
|
|
37
|
+
return wrapped;
|
|
38
|
+
}
|
|
39
|
+
}
|