@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# extensions.md
|
|
2
2
|
|
|
3
|
-
Version:
|
|
3
|
+
Version: 20260730
|
|
4
4
|
|
|
5
5
|
## Purpose
|
|
6
6
|
|
|
@@ -12,9 +12,19 @@ The container supports controlled extension of dependency resolution through thr
|
|
|
12
12
|
|
|
13
13
|
These mechanisms are related but not interchangeable.
|
|
14
14
|
|
|
15
|
+
## Contents
|
|
16
|
+
|
|
17
|
+
- [Preprocess Hooks](#preprocess-hooks)
|
|
18
|
+
- [Postprocess Hooks](#postprocess-hooks)
|
|
19
|
+
- [Wrapper Exports](#wrapper-exports)
|
|
20
|
+
- [Execution Order](#execution-order)
|
|
21
|
+
- [Constraints](#constraints)
|
|
22
|
+
|
|
15
23
|
## Preprocess Hooks
|
|
16
24
|
|
|
17
|
-
Preprocess hooks transform
|
|
25
|
+
Preprocess hooks transform parsed dependency identities before resolution.
|
|
26
|
+
|
|
27
|
+
They apply to the root request and every dependency declaration discovered transitively in the requested graph, exactly once per occurrence.
|
|
18
28
|
|
|
19
29
|
Signature:
|
|
20
30
|
|
|
@@ -31,10 +41,11 @@ Properties:
|
|
|
31
41
|
|
|
32
42
|
Typical uses:
|
|
33
43
|
|
|
34
|
-
-
|
|
44
|
+
- Dependency Specifier alias rewriting;
|
|
35
45
|
- policy-driven identifier normalization;
|
|
36
46
|
- project-specific prefix adaptation.
|
|
37
47
|
|
|
48
|
+
|
|
38
49
|
## Postprocess Hooks
|
|
39
50
|
|
|
40
51
|
Postprocess hooks transform resolved values after instantiation and before wrapper exports.
|
|
@@ -42,7 +53,7 @@ Postprocess hooks transform resolved values after instantiation and before wrapp
|
|
|
42
53
|
Signature:
|
|
43
54
|
|
|
44
55
|
```js
|
|
45
|
-
(value) => value
|
|
56
|
+
(value, context) => value
|
|
46
57
|
```
|
|
47
58
|
|
|
48
59
|
Properties:
|
|
@@ -50,7 +61,7 @@ Properties:
|
|
|
50
61
|
- registered with `addPostprocess()`;
|
|
51
62
|
- run for every resolved value;
|
|
52
63
|
- run in declared order;
|
|
53
|
-
- do not alter
|
|
64
|
+
- do not alter dependency specifier parsing or module resolution.
|
|
54
65
|
|
|
55
66
|
Typical uses:
|
|
56
67
|
|
|
@@ -60,7 +71,7 @@ Typical uses:
|
|
|
60
71
|
|
|
61
72
|
## Wrapper Exports
|
|
62
73
|
|
|
63
|
-
Wrapper exports are selected directly from
|
|
74
|
+
Wrapper exports are selected directly from dependency specifier suffixes and are resolved from the same module namespace as the dependency being composed.
|
|
64
75
|
|
|
65
76
|
Signature:
|
|
66
77
|
|
|
@@ -71,7 +82,7 @@ Signature:
|
|
|
71
82
|
Properties:
|
|
72
83
|
|
|
73
84
|
- selected by wrapper suffixes such as `_wrapLog`;
|
|
74
|
-
- applied only when present in the
|
|
85
|
+
- applied only when present in the Dependency Specifier;
|
|
75
86
|
- executed after postprocess hooks and before freeze;
|
|
76
87
|
- not registered globally in the container.
|
|
77
88
|
|
|
@@ -66,28 +66,23 @@ export interface PackageApiContract {
|
|
|
66
66
|
*/
|
|
67
67
|
export const PACKAGE_API: PackageApiContract = {
|
|
68
68
|
packageName: '@teqfw/di',
|
|
69
|
-
packageRole: 'Deterministic runtime DI container for native ES modules with explicit
|
|
69
|
+
packageRole: 'Deterministic runtime DI container for native ES modules with explicit Dependency Specifier contracts.',
|
|
70
70
|
canonicalEntrypoints: [
|
|
71
71
|
'@teqfw/di',
|
|
72
|
-
'@teqfw/di/
|
|
72
|
+
'@teqfw/di/node/registry/namespace',
|
|
73
|
+
'@teqfw/di/node/registry/package',
|
|
73
74
|
],
|
|
74
75
|
publicRuntime: [
|
|
75
76
|
{
|
|
76
77
|
alias: 'TeqFw_Di_Container',
|
|
77
78
|
kind: 'class',
|
|
78
|
-
role: 'Primary runtime composition root. Resolves
|
|
79
|
+
role: 'Primary runtime composition root. Resolves Dependency Specifiers into frozen linked values.',
|
|
79
80
|
imports: [
|
|
80
81
|
{
|
|
81
82
|
specifier: '@teqfw/di',
|
|
82
83
|
exportName: 'default',
|
|
83
84
|
canonical: true,
|
|
84
85
|
},
|
|
85
|
-
{
|
|
86
|
-
specifier: '@teqfw/di/src/Container.mjs',
|
|
87
|
-
exportName: 'default',
|
|
88
|
-
canonical: false,
|
|
89
|
-
note: 'Explicit source subpath export. Prefer the package root import.',
|
|
90
|
-
},
|
|
91
86
|
],
|
|
92
87
|
methods: [
|
|
93
88
|
{
|
|
@@ -100,7 +95,7 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
100
95
|
name: 'addNamespaceRoot',
|
|
101
96
|
signature: 'addNamespaceRoot(prefix: string, target: string, defaultExt: string): void',
|
|
102
97
|
stage: 'builder',
|
|
103
|
-
summary: 'Registers one namespace root that maps a
|
|
98
|
+
summary: 'Registers one namespace root that maps a Module Token prefix to a module-specifier base.',
|
|
104
99
|
constraints: [
|
|
105
100
|
'Allowed only before the first get().',
|
|
106
101
|
'The target may be filesystem-backed or URL-backed depending on runtime environment.',
|
|
@@ -111,10 +106,10 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
111
106
|
name: 'addPreprocess',
|
|
112
107
|
signature: 'addPreprocess(fn: (depId: TeqFw_Di_DepId$DTO) => TeqFw_Di_DepId$DTO): void',
|
|
113
108
|
stage: 'builder',
|
|
114
|
-
summary: 'Adds an ordered
|
|
109
|
+
summary: 'Adds an ordered parsed dependency identity preprocessing hook.',
|
|
115
110
|
constraints: [
|
|
116
111
|
'Allowed only before the first get().',
|
|
117
|
-
'Each hook must return another DepId DTO.',
|
|
112
|
+
'Each hook is invoked synchronously in registration order and must return another DepId DTO.',
|
|
118
113
|
],
|
|
119
114
|
},
|
|
120
115
|
{
|
|
@@ -124,7 +119,7 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
124
119
|
summary: 'Adds an ordered value transform applied to every resolved value after instantiation.',
|
|
125
120
|
constraints: [
|
|
126
121
|
'Allowed only before the first get().',
|
|
127
|
-
'Runs after instantiation and before wrapper exports.',
|
|
122
|
+
'Runs synchronously in registration order after instantiation and before wrapper exports.',
|
|
128
123
|
],
|
|
129
124
|
},
|
|
130
125
|
{
|
|
@@ -147,9 +142,9 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
147
142
|
},
|
|
148
143
|
{
|
|
149
144
|
name: 'register',
|
|
150
|
-
signature: 'register(
|
|
145
|
+
signature: 'register(specifier: string, mock: unknown): void',
|
|
151
146
|
stage: 'builder',
|
|
152
|
-
summary: 'Registers a mock by canonical DepId identity after parsing the provided
|
|
147
|
+
summary: 'Registers a mock by canonical DepId identity after parsing the provided Dependency Specifier.',
|
|
153
148
|
constraints: [
|
|
154
149
|
'Allowed only before the first get().',
|
|
155
150
|
'Requires enableTestMode() first.',
|
|
@@ -157,7 +152,7 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
157
152
|
},
|
|
158
153
|
{
|
|
159
154
|
name: 'get',
|
|
160
|
-
signature: 'get(
|
|
155
|
+
signature: 'get(specifier: string): Promise<any>',
|
|
161
156
|
stage: 'runtime',
|
|
162
157
|
summary: 'Parses, preprocesses, resolves, instantiates, postprocesses, wraps, applies lifecycle, freezes, and returns a linked value.',
|
|
163
158
|
constraints: [
|
|
@@ -169,12 +164,22 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
169
164
|
],
|
|
170
165
|
},
|
|
171
166
|
{
|
|
172
|
-
alias: '
|
|
167
|
+
alias: 'TeqFw_Di_Node_Registry_Package',
|
|
168
|
+
kind: 'class',
|
|
169
|
+
role: 'Node.js-only composition-stage static runtime package graph builder.',
|
|
170
|
+
imports: [{specifier: '@teqfw/di/node/registry/package', exportName: 'default', canonical: true}],
|
|
171
|
+
methods: [
|
|
172
|
+
{name: 'constructor', signature: 'new PackageRegistry({ fs, path, appRoot })', stage: 'composition', summary: 'Creates a static package graph builder.'},
|
|
173
|
+
{name: 'build', signature: 'build(): Promise<ReadonlyArray<PackageRecord>>', stage: 'composition', summary: 'Builds immutable dependency-first postorder package records; independent dependency names use ascending lexical order as the stable tie-breaker.', constraints: ['Reads only transitive dependencies and static package metadata.', 'Does not load modules or interpret application providers.']},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
alias: 'TeqFw_Di_Node_Registry_Namespace',
|
|
173
178
|
kind: 'class',
|
|
174
|
-
role: '
|
|
179
|
+
role: 'Node.js-only composition-stage helper that discovers namespace roots from the root package and installed npm dependencies.',
|
|
175
180
|
imports: [
|
|
176
181
|
{
|
|
177
|
-
specifier: '@teqfw/di/
|
|
182
|
+
specifier: '@teqfw/di/node/registry/namespace',
|
|
178
183
|
exportName: 'default',
|
|
179
184
|
canonical: true,
|
|
180
185
|
},
|
|
@@ -186,7 +191,7 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
186
191
|
stage: 'composition',
|
|
187
192
|
summary: 'Creates a registry builder over filesystem and path adapters.',
|
|
188
193
|
constraints: [
|
|
189
|
-
'Intended for composition root code, not for DI-managed runtime modules.',
|
|
194
|
+
'Intended only for Node.js composition root code, not for DI-managed or browser runtime modules.',
|
|
190
195
|
'Consumes static package.json metadata only.',
|
|
191
196
|
],
|
|
192
197
|
},
|
|
@@ -196,13 +201,16 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
196
201
|
stage: 'composition',
|
|
197
202
|
summary: 'Builds an immutable namespace registry sorted by descending prefix length.',
|
|
198
203
|
constraints: [
|
|
199
|
-
'
|
|
204
|
+
'Selects package.json#teqfw.fw.di.namespaces when present; otherwise temporarily falls back to the legacy package.json#teqfw.namespaces array.',
|
|
205
|
+
'Both schemas require arrays; canonical metadata wins exclusively and the arrays are never merged.',
|
|
206
|
+
'Fails fast on invalid selected metadata or duplicate prefixes with publisher attribution.',
|
|
200
207
|
],
|
|
201
208
|
},
|
|
202
209
|
],
|
|
203
210
|
},
|
|
204
211
|
],
|
|
205
212
|
structuralContracts: [
|
|
213
|
+
{name: 'Runtime Package Record', kind: 'protocol', aliases: ['TeqFw_Di_Node_Registry_Package_Record'], summary: 'Immutable static package metadata record.', fields: {name: 'Declared package name.', rootAbs: 'Resolved package root.', rootReal: 'Canonical package root.', packageJson: 'Recursively frozen package metadata.'}},
|
|
206
214
|
{
|
|
207
215
|
name: 'DepId DTO',
|
|
208
216
|
kind: 'dto',
|
|
@@ -215,7 +223,7 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
215
223
|
composition: '"A" | "F" in the current implementation.',
|
|
216
224
|
life: '"S" | "T" | null in the current implementation.',
|
|
217
225
|
wrappers: 'Ordered wrapper export names.',
|
|
218
|
-
origin: 'Original
|
|
226
|
+
origin: 'Original Dependency Specifier string for diagnostics.',
|
|
219
227
|
},
|
|
220
228
|
notes: [
|
|
221
229
|
'Identity excludes origin.',
|
|
@@ -245,8 +253,8 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
245
253
|
kind: 'module-contract',
|
|
246
254
|
summary: 'Shape expected from application modules resolved by the container.',
|
|
247
255
|
fields: {
|
|
248
|
-
__deps__: 'Optional dependency descriptor. Canonical form is hierarchical Record<exportName, Record<dependencyKey,
|
|
249
|
-
moduleNamespace: 'Whole ES module namespace object returned for as-is
|
|
256
|
+
__deps__: 'Optional dependency descriptor. Canonical form is hierarchical Record<exportName, Record<dependencyKey, Dependency Specifier string>>. Supported shorthand form is a flat Record<dependencyKey, Dependency Specifier string> for default-export-only modules. Omission means there are no declared dependencies.',
|
|
257
|
+
moduleNamespace: 'Whole ES module namespace object returned for as-is Dependency Specifier without selected export.',
|
|
250
258
|
defaultExport: 'Used when the parsed DepId selects exportName="default" for factory composition.',
|
|
251
259
|
namedExports: 'May be selected via __ExportName for factory composition and may also provide wrapper exports.',
|
|
252
260
|
wrapperExport: 'Named export whose identifier appears in depId.wrappers; it must be synchronous and unary.',
|
|
@@ -260,17 +268,30 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
260
268
|
],
|
|
261
269
|
typeMapClassification: [
|
|
262
270
|
{
|
|
263
|
-
alias: '
|
|
264
|
-
source: './src/
|
|
271
|
+
alias: 'TeqFw_Di_Node_Registry_Package',
|
|
272
|
+
source: './src/Node/Registry/Package.mjs',
|
|
273
|
+
exposure: 'public-runtime',
|
|
274
|
+
reason: 'Exported through package.json exports as the supported Node.js composition subpath.',
|
|
275
|
+
canonicalUse: 'Node.js composition-stage package graph imported from @teqfw/di/node/registry/package.',
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
alias: 'TeqFw_Di_Node_Registry_Package_Record',
|
|
279
|
+
source: './src/Node/Registry/Package.mjs#record',
|
|
280
|
+
exposure: 'public-structural',
|
|
281
|
+
reason: 'Public immutable record contract returned by PackageRegistry.build().',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
alias: 'TeqFw_Di_Node_Registry_Namespace',
|
|
285
|
+
source: './src/Node/Registry/Namespace.mjs',
|
|
265
286
|
exposure: 'public-runtime',
|
|
266
|
-
reason: 'Exported through package.json exports as
|
|
267
|
-
canonicalUse: '
|
|
287
|
+
reason: 'Exported through package.json exports as the supported Node.js composition subpath.',
|
|
288
|
+
canonicalUse: 'Node.js composition-stage helper imported from @teqfw/di/node/registry/namespace.',
|
|
268
289
|
},
|
|
269
290
|
{
|
|
270
291
|
alias: 'TeqFw_Di_Container',
|
|
271
292
|
source: './src/Container.mjs',
|
|
272
293
|
exposure: 'public-runtime',
|
|
273
|
-
reason: 'Default export of the package root
|
|
294
|
+
reason: 'Default export of the package root.',
|
|
274
295
|
canonicalUse: 'Primary container API imported from @teqfw/di.',
|
|
275
296
|
},
|
|
276
297
|
{
|
|
@@ -280,25 +301,25 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
280
301
|
reason: 'Internal immutable-core helper. Not exported via package.json.',
|
|
281
302
|
},
|
|
282
303
|
{
|
|
283
|
-
alias: '
|
|
304
|
+
alias: 'TeqFw_Di_Container_Instantiate',
|
|
284
305
|
source: './src/Container/Instantiate.mjs',
|
|
285
306
|
exposure: 'internal',
|
|
286
307
|
reason: 'Internal immutable-core helper. Not exported via package.json.',
|
|
287
308
|
},
|
|
288
309
|
{
|
|
289
|
-
alias: '
|
|
310
|
+
alias: 'TeqFw_Di_Container_Lifecycle',
|
|
290
311
|
source: './src/Container/Lifecycle.mjs',
|
|
291
312
|
exposure: 'internal',
|
|
292
313
|
reason: 'Internal lifecycle cache component. Not exported via package.json.',
|
|
293
314
|
},
|
|
294
315
|
{
|
|
295
|
-
alias: '
|
|
316
|
+
alias: 'TeqFw_Di_Container_GraphResolver',
|
|
296
317
|
source: './src/Container/GraphResolver.mjs',
|
|
297
318
|
exposure: 'internal',
|
|
298
319
|
reason: 'Internal graph builder used by Container.get(). Not exported via package.json.',
|
|
299
320
|
},
|
|
300
321
|
{
|
|
301
|
-
alias: '
|
|
322
|
+
alias: 'TeqFw_Di_Container_Executor',
|
|
302
323
|
source: './src/Container/Executor.mjs',
|
|
303
324
|
exposure: 'internal',
|
|
304
325
|
reason: 'Internal wrapper-stage executor. Not exported via package.json.',
|
|
@@ -399,11 +420,13 @@ export const PACKAGE_API: PackageApiContract = {
|
|
|
399
420
|
},
|
|
400
421
|
],
|
|
401
422
|
operationalNotes: [
|
|
402
|
-
'
|
|
423
|
+
'Canonical Node.js registry entrypoints are @teqfw/di/node/registry/namespace and @teqfw/di/node/registry/package. They must not be imported by browser runtime modules.',
|
|
424
|
+
'The legacy compatibility entry point @teqfw/di/src/Config/NamespaceRegistry.mjs is deprecated. Every other src/** path is internal and unsupported; new code must use @teqfw/di/node/registry/namespace.',
|
|
425
|
+
'NamespaceRegistry uses the canonical package.json#teqfw.fw.di.namespaces array and temporarily falls back to package.json#teqfw.namespaces only when canonical metadata is absent.',
|
|
403
426
|
'Resolved values are frozen before being returned.',
|
|
404
427
|
'The hierarchical export-scoped descriptor is canonical. The flat shorthand descriptor is supported only for default-export-only modules.',
|
|
405
428
|
'Named wrapper exports are executed after addPostprocess() hooks and before freeze.',
|
|
406
|
-
'
|
|
429
|
+
'$$ selects transient lifecycle; $$$ explicitly selects direct lifecycle.',
|
|
407
430
|
'types.d.ts is broader than the runtime import surface. Presence of an alias there does not by itself make the underlying module a supported runtime entrypoint.',
|
|
408
431
|
],
|
|
409
432
|
} as const;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# usage.md
|
|
2
|
+
|
|
3
|
+
Version: 20260730
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This document shows canonical usage patterns for the container. Examples are intentionally short and prioritize supported, recommended forms over convenience shorthand.
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- [Canonical Module Descriptor](#canonical-module-descriptor)
|
|
12
|
+
- [Canonical Container Setup](#canonical-container-setup)
|
|
13
|
+
- [Lifecycle Composition](#lifecycle-composition)
|
|
14
|
+
- [Test Mode And Mocks](#test-mode-and-mocks)
|
|
15
|
+
- [Browser Entry Point](#browser-entry-point)
|
|
16
|
+
- [Package-Backed Composition](#package-backed-composition)
|
|
17
|
+
- [Package Namespace Metadata](#package-namespace-metadata)
|
|
18
|
+
|
|
19
|
+
## Canonical Module Descriptor
|
|
20
|
+
|
|
21
|
+
The preferred module exposes one Principal Application Value through `default export`. The `__deps__` export is declarative metadata listing the values required to link it. The canonical declaration form is hierarchical and keyed by export name.
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
// @ts-check
|
|
25
|
+
|
|
26
|
+
export const __deps__ = {
|
|
27
|
+
default: {
|
|
28
|
+
cast: "App_Helper_Cast$",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default class App_Root {
|
|
33
|
+
/**
|
|
34
|
+
* @param {{cast: (value: unknown) => string}} deps
|
|
35
|
+
*/
|
|
36
|
+
constructor({ cast }) {
|
|
37
|
+
return {
|
|
38
|
+
configure(params = {}) {
|
|
39
|
+
return {
|
|
40
|
+
name: cast(params.name ?? "app"),
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Dependency module:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
// @ts-check
|
|
52
|
+
|
|
53
|
+
export default function App_Helper_Cast() {
|
|
54
|
+
return function cast(value) {
|
|
55
|
+
return String(value);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Rules:
|
|
61
|
+
|
|
62
|
+
- the hierarchical export-scoped form is canonical;
|
|
63
|
+
- each export entry maps constructor dependency names to dependency specifiers;
|
|
64
|
+
- if `__deps__` is omitted, the module has no declared dependencies;
|
|
65
|
+
- dependencies are resolved recursively before instantiation.
|
|
66
|
+
|
|
67
|
+
## Canonical Container Setup
|
|
68
|
+
|
|
69
|
+
The container is configured in the composition root before the first resolution.
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import path from "node:path";
|
|
73
|
+
import { fileURLToPath } from "node:url";
|
|
74
|
+
import Container from "@teqfw/di";
|
|
75
|
+
|
|
76
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
77
|
+
const __dirname = path.dirname(__filename);
|
|
78
|
+
|
|
79
|
+
const container = new Container();
|
|
80
|
+
container.addNamespaceRoot("App_", path.resolve(__dirname, "./src/App"), ".mjs");
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Namespace roots may also use URL-backed module-specifier bases:
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
container.addNamespaceRoot("App_", "https://cdn.example.com/app", ".mjs");
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Resolve Root Dependency
|
|
90
|
+
|
|
91
|
+
Applications typically resolve one root dependency and let the container build the full object graph.
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
const root = await container.get("App_Root$");
|
|
95
|
+
console.log(root.configure({name: 123}).name);
|
|
96
|
+
console.log(Object.isFrozen(root));
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Named Export
|
|
100
|
+
|
|
101
|
+
Named exports are a JavaScript ecosystem compatibility surface. They use the `__ExportName` segment in the Dependency Specifier and the same hierarchical `__deps__` structure.
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
export const __deps__ = {
|
|
105
|
+
default: {
|
|
106
|
+
cast: "App_Helper_Cast$",
|
|
107
|
+
},
|
|
108
|
+
Factory: {
|
|
109
|
+
cast: "App_Helper_Cast$",
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default class RuntimeWrapper {
|
|
114
|
+
constructor() {
|
|
115
|
+
return {mode: "runtime"};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class Factory {
|
|
120
|
+
constructor({ cast }) {
|
|
121
|
+
this.configure = function (params = {}) {
|
|
122
|
+
return {
|
|
123
|
+
mode: "factory",
|
|
124
|
+
name: cast(params.name ?? "app"),
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Resolution examples:
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
const runtime = await container.get("App_Module$");
|
|
135
|
+
const factory = await container.get("App_Module__Factory$");
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Without a lifecycle marker, the export is resolved as-is — the class or function itself, not an instance:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
const FactoryClass = await container.get("App_Module__Factory");
|
|
142
|
+
const factory = new FactoryClass({cast: resolvedCast});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Lifecycle Composition
|
|
146
|
+
|
|
147
|
+
Common lifecycle-based compositions:
|
|
148
|
+
|
|
149
|
+
```txt
|
|
150
|
+
App_Service$
|
|
151
|
+
App_Task$$
|
|
152
|
+
App_Task$$$
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- `$` creates and reuses a singleton instance;
|
|
156
|
+
- `$$` creates a new instance for each request;
|
|
157
|
+
- `$$$` uses direct factory composition: it produces a value for the request without lifecycle caching.
|
|
158
|
+
|
|
159
|
+
## Wrappers
|
|
160
|
+
|
|
161
|
+
Wrapper exports are selected by dependency specifier suffixes and are applied after postprocess hooks.
|
|
162
|
+
|
|
163
|
+
```txt
|
|
164
|
+
App_Service$$_wrapLog_wrapTrace
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
This pattern is useful when runtime behavior should be decorated without changing the service module contract.
|
|
168
|
+
|
|
169
|
+
## Platform Modules
|
|
170
|
+
|
|
171
|
+
Dependency Specifier may refer to platform modules directly.
|
|
172
|
+
|
|
173
|
+
```txt
|
|
174
|
+
node:fs
|
|
175
|
+
npm:@humanfs/core
|
|
176
|
+
node:worker_threads
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
These forms resolve the selected platform module export as-is unless a lifecycle marker is explicitly added.
|
|
180
|
+
|
|
181
|
+
## Non-Canonical Shorthand
|
|
182
|
+
|
|
183
|
+
A flat `__deps__` object is a supported shorthand for default-export-only modules, but it is not the canonical model.
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
export const __deps__ = {
|
|
187
|
+
cast: "App_Helper_Cast$",
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export default class App_Short {
|
|
191
|
+
constructor({ cast }) {
|
|
192
|
+
this.cast = cast;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Prefer the hierarchical form for new integrations and for any module that exposes named exports.
|
|
198
|
+
|
|
199
|
+
## Empty Descriptor
|
|
200
|
+
|
|
201
|
+
Modules with no declared dependencies omit `__deps__` entirely.
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
export default class App_Empty {
|
|
205
|
+
constructor() {
|
|
206
|
+
this.ready = function () {
|
|
207
|
+
return true;
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Package-Backed Composition
|
|
214
|
+
|
|
215
|
+
In Node.js, a composition root may import `PackageRegistry` from `@teqfw/di/node/registry/package`, import `NamespaceRegistry` from `@teqfw/di/node/registry/namespace`, use the latter for namespace roots, and independently inspect each immutable package record for application-owned metadata. Package discovery reads only static manifests and transitive dependencies; it does not resolve container values or interpret providers. These imports are forbidden in browser runtime modules. All `src/**` package subpaths are unsupported except the deprecated `@teqfw/di/src/Config/NamespaceRegistry.mjs` compatibility import; new code must use `@teqfw/di/node/registry/namespace` instead.
|
|
216
|
+
|
|
217
|
+
Build namespace roots and register each one before the first `get()`:
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
import fs from "node:fs/promises";
|
|
221
|
+
import path from "node:path";
|
|
222
|
+
import Container from "@teqfw/di";
|
|
223
|
+
import NamespaceRegistry from "@teqfw/di/node/registry/namespace";
|
|
224
|
+
|
|
225
|
+
const appRoot = "/absolute/path/to/application";
|
|
226
|
+
const container = new Container();
|
|
227
|
+
const namespaces = await new NamespaceRegistry({fs, path, appRoot}).build();
|
|
228
|
+
|
|
229
|
+
for (const {prefix, dirAbs, ext} of namespaces) {
|
|
230
|
+
container.addNamespaceRoot(prefix, dirAbs, ext);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const root = await container.get("App_Root$");
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`appRoot` is the absolute application root containing its `package.json`. Do not assume that the current working directory is that root unless the host application guarantees it.
|
|
237
|
+
|
|
238
|
+
## Test Mode And Mocks
|
|
239
|
+
|
|
240
|
+
Enable test mode and register mocks before the first `get()`. A registered mock is selected by canonical Dependency Specifier identity, bypasses resolution and instantiation, and is frozen before it is returned.
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
const container = new Container();
|
|
244
|
+
container.enableTestMode();
|
|
245
|
+
container.register("App_Service$", mockService);
|
|
246
|
+
|
|
247
|
+
const service = await container.get("App_Service$");
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Do not use test mode as an application-time substitution mechanism: configuration is locked by the first `get()`.
|
|
251
|
+
|
|
252
|
+
## Browser Entry Point
|
|
253
|
+
|
|
254
|
+
The container and URL-backed namespace roots can be used in browser ESM code. Node.js registry utilities are excluded from browser runtime code.
|
|
255
|
+
|
|
256
|
+
```html
|
|
257
|
+
<script type="module">
|
|
258
|
+
import Container from "https://cdn.jsdelivr.net/npm/@teqfw/di@2/+esm";
|
|
259
|
+
|
|
260
|
+
const container = new Container();
|
|
261
|
+
container.addNamespaceRoot("App_", "https://cdn.example.com/app", ".mjs");
|
|
262
|
+
</script>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Package Namespace Metadata
|
|
266
|
+
|
|
267
|
+
A package declares namespace mappings in `package.json` using `teqfw.fw.di.namespaces`; the declaration is always an array, including one mapping.
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"teqfw": {
|
|
272
|
+
"fw": {
|
|
273
|
+
"di": {
|
|
274
|
+
"namespaces": [
|
|
275
|
+
{"prefix": "App_", "path": "./src", "ext": ".mjs"}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Each `path` is a non-empty relative path from its publishing package, must resolve to an existing directory inside that package, and must use an ESM-compatible extension. Canonical metadata takes precedence over the legacy `teqfw.namespaces` array; they are never merged. Build registries and add their roots during composition, before the first `container.get()`.
|
package/src/AGENTS.md
CHANGED
|
@@ -14,16 +14,12 @@ This file defines only implementation-level obligations. It does not redefine ar
|
|
|
14
14
|
|
|
15
15
|
The following documents are mandatory:
|
|
16
16
|
|
|
17
|
-
- `ctx/docs/code/structure.md`
|
|
18
|
-
- `ctx/docs/code/container.md`
|
|
19
|
-
- `ctx/docs/code/resolver.md`
|
|
20
|
-
- `ctx/docs/code/parser.md`
|
|
21
|
-
- `ctx/docs/code/depid.md`
|
|
22
|
-
- `ctx/docs/code/testing.md`
|
|
23
|
-
- `ctx/docs/code/jsdoc-spec.md`
|
|
24
|
-
- `ctx/docs/code/convention/es6-modules.md`
|
|
25
|
-
- `ctx/docs/code/convention/teqfw/dto.md`
|
|
26
|
-
- `ctx/docs/code/convention/teqfw/enum.md`
|
|
17
|
+
- `ctx/docs/code/layout/structure.md`
|
|
18
|
+
- `ctx/docs/code/components/container.md`
|
|
19
|
+
- `ctx/docs/code/components/resolver.md`
|
|
20
|
+
- `ctx/docs/code/components/parser.md`
|
|
21
|
+
- `ctx/docs/code/components/depid.md`
|
|
22
|
+
- `ctx/docs/code/layout/testing.md`
|
|
27
23
|
|
|
28
24
|
Agent MUST read and follow them before generating or modifying code.
|
|
29
25
|
|
|
@@ -33,7 +29,7 @@ Deviation from these documents constitutes non-compliance.
|
|
|
33
29
|
|
|
34
30
|
Directory layout, namespace mapping, file naming, dependency direction, and static import rules MUST strictly follow:
|
|
35
31
|
|
|
36
|
-
`ctx/docs/code/structure.md`
|
|
32
|
+
`ctx/docs/code/layout/structure.md`
|
|
37
33
|
|
|
38
34
|
In particular:
|
|
39
35
|
|
|
@@ -42,7 +38,7 @@ In particular:
|
|
|
42
38
|
- Underscores in file names are prohibited.
|
|
43
39
|
- Namespace hierarchy MUST be reflected in directory hierarchy.
|
|
44
40
|
- Static dependency direction MUST follow the allowed layer graph.
|
|
45
|
-
- `Container.mjs` is the
|
|
41
|
+
- `Container.mjs` is the primary runtime entry point; the package also exposes canonical Node.js registry subpaths and one deprecated migration-only registry compatibility subpath.
|
|
46
42
|
|
|
47
43
|
Structural violations are execution errors.
|
|
48
44
|
|
|
@@ -50,7 +46,7 @@ Structural violations are execution errors.
|
|
|
50
46
|
|
|
51
47
|
JSDoc is mandatory and governed by:
|
|
52
48
|
|
|
53
|
-
`ctx/docs/code/
|
|
49
|
+
`ctx/docs/code/layout/structure.md` (Section 8)
|
|
54
50
|
|
|
55
51
|
All implementation files MUST:
|
|
56
52
|
|
|
@@ -72,8 +68,8 @@ Absence of required JSDoc constitutes non-compliance.
|
|
|
72
68
|
|
|
73
69
|
All DTO and Enum implementations MUST strictly follow:
|
|
74
70
|
|
|
75
|
-
- `ctx/docs/code/
|
|
76
|
-
- `ctx/docs/code/
|
|
71
|
+
- `ctx/docs/code/components/depid.md`
|
|
72
|
+
- `ctx/docs/code/layout/structure.md`
|
|
77
73
|
|
|
78
74
|
In particular:
|
|
79
75
|
|
|
@@ -116,7 +112,7 @@ Redundant validation logic constitutes architectural violation.
|
|
|
116
112
|
|
|
117
113
|
Unit and integration tests MUST follow:
|
|
118
114
|
|
|
119
|
-
`ctx/docs/code/testing.md`
|
|
115
|
+
`ctx/docs/code/layout/testing.md`
|
|
120
116
|
|
|
121
117
|
For every testable source module, exactly one corresponding unit test MUST exist under `test/unit/`, mirroring directory structure.
|
|
122
118
|
|
|
@@ -127,6 +123,14 @@ Tests MUST use:
|
|
|
127
123
|
|
|
128
124
|
Isolation and determinism requirements are mandatory.
|
|
129
125
|
|
|
126
|
+
## Validation Tooling
|
|
127
|
+
|
|
128
|
+
This package follows classical JavaScript module conventions and is a foundational TeqFW package.
|
|
129
|
+
|
|
130
|
+
The `teqfw-esm-validator` skill and CLI MUST NOT be used for this project because their application-module profiles are not applicable to this package.
|
|
131
|
+
|
|
132
|
+
Source changes MUST be verified with the project-provided tests and applicable classical JavaScript checks, including `node --check`, local lint commands, and local type checks when available.
|
|
133
|
+
|
|
130
134
|
## Type Declaration Discipline
|
|
131
135
|
|
|
132
136
|
The file `types.d.ts` defines exported structural type aliases corresponding to implementation modules.
|