@teqfw/di 2.7.0 → 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.
@@ -2,218 +2,8 @@
2
2
 
3
3
  /**
4
4
  * @namespace TeqFw_Di_Config_NamespaceRegistry
5
- * @description Deterministic loader for package namespace rules.
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";
@@ -14,13 +14,14 @@
14
14
 
15
15
  /**
16
16
  * @typedef {object} TeqFw_Di_Container_GraphResolver_Dependencies
17
- * @property {TeqFw_Di_Parser} parser
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}} TeqFw_Di_Container_GraphResolver_Node
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';
@@ -31,7 +32,8 @@ export default class TeqFw_Di_Container_GraphResolver {
31
32
  /**
32
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
 
@@ -61,19 +63,28 @@ export default class TeqFw_Di_Container_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
- out.set(key, {depId, namespace});
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 nextCdc of Object.values(depsMap)) {
84
+ for (const [name, nextSpecifier] of Object.entries(depsMap)) {
75
85
  /** @type {TeqFw_Di_DepId__DTO} */
76
- const nextDepId = parser.parse(/** @type {string} */ (nextCdc));
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
  }
@@ -6,12 +6,10 @@
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
14
  * @property {TeqFw_Di_Container_GraphResolver|undefined} graphResolver
17
15
  * @property {TeqFw_Di_Container_Lifecycle|undefined} lifecycle
@@ -21,20 +19,19 @@ import {makePromiseSafe} from '../Internal/PromiseSafe.mjs';
21
19
  * @property {boolean} testMode
22
20
  * @property {Map<string, unknown>} mockRegistry
23
21
  * @property {(value: unknown) => unknown} freeze
24
- * @property {(depId: TeqFw_Di_DepId__DTO) => TeqFw_Di_DepId__DTO} applyPreprocess
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 get pipeline for a CDC.
27
+ * Executes the full container pipeline for a Dependency Specifier.
30
28
  *
31
29
  * @param {TeqFw_Di_Container_Pipeline_Context} ctx
32
- * @param {string} cdc
30
+ * @param {string} specifier
33
31
  * @returns {Promise<any>}
34
32
  */
35
- export async function executeContainerPipeline(ctx, cdc) {
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
- applyPreprocess,
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: cdc='${cdc}'.`);
58
+ logger.log(`Container.get: specifier='${specifier}'.`);
62
59
  stage = 'parse';
63
60
  logger.log('Container.pipeline: parse:entry.');
64
- const parsed = parser.parse(cdc);
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
- /** @type {Record<string, unknown>} */
106
- const depsDecl = readDepsDecl(node.namespace, node.depId);
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';
package/src/Container.mjs CHANGED
@@ -32,7 +32,7 @@ export default class TeqFw_Di_Container {
32
32
  let state = 'notConfigured';
33
33
  /** @type {((depId: TeqFw_Di_DepId__DTO) => TeqFw_Di_DepId__DTO)[]} */
34
34
  const preprocess = [];
35
- /** @type {((value: unknown) => unknown)[]} */
35
+ /** @type {((value: unknown, context: TeqFw_Di_Container_Postprocess_Context) => unknown)[]} */
36
36
  const postprocess = [];
37
37
  /** @type {TeqFw_Di_Dto_Resolver_Config_Namespace__DTO[]} */
38
38
  const namespaceRoots = [];
@@ -60,18 +60,13 @@ export default class TeqFw_Di_Container {
60
60
  const getKey = buildDependencyKey;
61
61
  const getMockKey = buildDependencyKey;
62
62
 
63
- const freeze = function (value) {
63
+ let freeze = function (value) {
64
64
  if ((value === null) || (value === undefined)) return value;
65
65
  const type = typeof value;
66
66
  if ((type !== 'object') && (type !== 'function')) return value;
67
67
  if (Object.prototype.toString.call(value) === '[object Module]') return value;
68
68
  if (Object.isFrozen(value)) return value;
69
- try {
70
- Object.freeze(value);
71
- } catch (error) {
72
- logger.log(`Container.freeze: skipped (${String(error)}).`);
73
- }
74
- return value;
69
+ return Object.freeze(value);
75
70
  };
76
71
 
77
72
  const applyPreprocess = function (depId) {
@@ -81,10 +76,26 @@ export default class TeqFw_Di_Container {
81
76
  return current;
82
77
  };
83
78
 
84
- const applyPostprocess = function (value) {
79
+ const canonicalize = function (specifier) {
80
+ return applyPreprocess(parser.parse(specifier));
81
+ };
82
+
83
+ const findMock = function (depId) {
84
+ const key = getMockKey(depId);
85
+ return {found: testMode === true && mockRegistry.has(key), value: mockRegistry.get(key)};
86
+ };
87
+
88
+ const applyPostprocess = function (value, depId) {
85
89
  /** @type {unknown} */
86
90
  let current = value;
87
- for (const fn of postprocess) current = fn(current);
91
+ /** @type {TeqFw_Di_Container_Postprocess_Context} */
92
+ const context = Object.freeze({depId});
93
+ for (const fn of postprocess) {
94
+ current = fn(current, context);
95
+ if (current instanceof Promise) {
96
+ throw new Error('Postprocess callback must return synchronously (non-Promise).');
97
+ }
98
+ }
88
99
  return current;
89
100
  };
90
101
 
@@ -104,7 +115,7 @@ export default class TeqFw_Di_Container {
104
115
  const resolverConfig = configFactory.create({namespaces: namespaceRoots});
105
116
  if (typeof parser.setLogger === 'function') parser.setLogger(logger);
106
117
  resolver = new TeqFw_Di_Resolver({config: resolverConfig, logger});
107
- graphResolver = new TeqFw_Di_Container_GraphResolver({parser, resolver, logger});
118
+ graphResolver = new TeqFw_Di_Container_GraphResolver({canonicalize, findMock, resolver, logger});
108
119
  lifecycle = new TeqFw_Di_Container_Lifecycle(logger);
109
120
  };
110
121
 
@@ -123,7 +134,7 @@ export default class TeqFw_Di_Container {
123
134
  /**
124
135
  * Adds a postprocessing hook.
125
136
  *
126
- * @param {(value: unknown) => unknown} fn
137
+ * @param {(value: unknown, context: TeqFw_Di_Container_Postprocess_Context) => unknown} fn
127
138
  * @returns {void}
128
139
  */
129
140
  this.addPostprocess = function (fn) {
@@ -132,6 +143,19 @@ export default class TeqFw_Di_Container {
132
143
  postprocess.push(fn);
133
144
  };
134
145
 
146
+
147
+ /**
148
+ * Sets the host hardening policy for every resolved value and mock.
149
+ *
150
+ * @param {(value: unknown) => unknown} fn
151
+ * @returns {void}
152
+ */
153
+ this.setHardener = function (fn) {
154
+ assertBuilderStage();
155
+ logBuilder('setHardener().');
156
+ freeze = fn;
157
+ };
158
+
135
159
  /**
136
160
  * Registers namespace root mapping.
137
161
  *
@@ -172,29 +196,29 @@ export default class TeqFw_Di_Container {
172
196
  };
173
197
 
174
198
  /**
175
- * Registers a mock value for a CDC.
199
+ * Registers a mock value for a Dependency Specifier.
176
200
  *
177
- * @param {string} cdc
201
+ * @param {string} specifier
178
202
  * @param {any} mock
179
203
  * @returns {void}
180
204
  */
181
- this.register = function (cdc, mock) {
205
+ this.register = function (specifier, mock) {
182
206
  assertBuilderStage();
183
- logBuilder(`register('${cdc}').`);
207
+ logBuilder(`register('${specifier}').`);
184
208
  if (testMode !== true) throw new Error('Container test mode is disabled.');
185
- const depId = parser.parse(cdc);
209
+ const depId = canonicalize(specifier);
186
210
  mockRegistry.set(getMockKey(depId), mock);
187
211
  };
188
212
 
189
213
  /**
190
- * Resolves a CDC into a frozen linked instance.
214
+ * Resolves a Dependency Specifier into a frozen Resolved Value.
191
215
  *
192
- * @param {string} cdc
216
+ * @param {string} specifier
193
217
  * @returns {Promise<any>}
194
218
  */
195
- this.get = async function (cdc) {
219
+ this.get = async function (specifier) {
196
220
  if (state === 'failed') {
197
- logger.error(`Container.get: rejected in failed state cdc='${cdc}'.`);
221
+ logger.error(`Container.get: rejected in failed state specifier='${specifier}'.`);
198
222
  throw new Error('Container is in failed state.');
199
223
  }
200
224
 
@@ -202,7 +226,6 @@ export default class TeqFw_Di_Container {
202
226
  initializeInfrastructure();
203
227
  logger.log(`Container.state: '${state}'.`);
204
228
  return await executeContainerPipeline({
205
- parser,
206
229
  resolver,
207
230
  graphResolver,
208
231
  lifecycle,
@@ -212,9 +235,9 @@ export default class TeqFw_Di_Container {
212
235
  testMode,
213
236
  mockRegistry,
214
237
  freeze,
215
- applyPreprocess,
238
+ canonicalize,
216
239
  applyPostprocess,
217
- }, cdc);
240
+ }, specifier);
218
241
  } catch (error) {
219
242
  logger.error(`Container.transition: operational -> failed.`, error);
220
243
  state = 'failed';
@@ -223,3 +246,12 @@ export default class TeqFw_Di_Container {
223
246
  };
224
247
  }
225
248
  }
249
+
250
+ const canonicalize = function (specifier) {
251
+ return applyPreprocess(parser.parse(specifier));
252
+ };
253
+
254
+ const findMock = function (depId) {
255
+ const key = getMockKey(depId);
256
+ return {found: testMode === true && mockRegistry.has(key), value: mockRegistry.get(key)};
257
+ };
package/src/Dto/DepId.mjs CHANGED
@@ -47,7 +47,7 @@ export default class DTO {
47
47
  /** @type {string[]} Wrapper pipeline names. */
48
48
  wrappers;
49
49
 
50
- /** @type {string} Original CDC string. */
50
+ /** @type {string} Original Dependency Specifier string. */
51
51
  origin;
52
52
  }
53
53
 
@@ -9,7 +9,7 @@
9
9
  * Reads dependency declaration map for the selected export of a module namespace.
10
10
  *
11
11
  * @param {object} namespace Loaded module namespace.
12
- * @param {TeqFw_Di_DepId__DTO} depId Canonical dependency identity.
12
+ * @param {TeqFw_Di_DepId__DTO} depId Parsed dependency identity.
13
13
  * @returns {Record<string, unknown>} Dependency declaration map.
14
14
  */
15
15
  export function readDepsDecl(namespace, depId) {
@@ -24,7 +24,7 @@ export function readDepsDecl(namespace, depId) {
24
24
  if ((exportScoped !== undefined) && (exportScoped !== null) && (typeof exportScoped === 'object') && !Array.isArray(exportScoped)) {
25
25
  const values = Object.values(/** @type {Record<string, unknown>} */ (exportScoped));
26
26
  if (!values.every((value) => typeof value === 'string')) {
27
- throw new Error('__deps__ export entries must map dependency names to CDC strings.');
27
+ throw new Error('__deps__ export entries must map dependency names to Dependency Specifier strings.');
28
28
  }
29
29
  return /** @type {Record<string, unknown>} */ (exportScoped);
30
30
  }