@codefast/di 0.10.1 → 0.12.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 +376 -0
- package/README.md +94 -17
- package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +7 -2
- package/dist/{ambient/active-container.js → ambient-container.js} +6 -1
- package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +27 -16
- package/dist/container/binding-chain.js +254 -0
- package/dist/container/container.d.ts +11 -2
- package/dist/container/container.js +293 -73
- package/dist/core/binding-builders.d.ts +114 -0
- package/dist/core/binding-declaration.d.ts +127 -0
- package/dist/core/binding-declaration.js +187 -0
- package/dist/core/binding.d.ts +62 -138
- package/dist/core/binding.js +54 -17
- package/dist/core/module.d.ts +8 -5
- package/dist/core/module.js +17 -3
- package/dist/core/registry.d.ts +17 -28
- package/dist/core/registry.js +92 -133
- package/dist/core/state-epoch.d.ts +18 -1
- package/dist/core/state-epoch.js +17 -0
- package/dist/core/tag.js +1 -1
- package/dist/decorators/inject.js +6 -5
- package/dist/decorators/injectable.js +4 -2
- package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +9 -3
- package/dist/decorators/metadata-record.d.ts +9 -0
- package/dist/decorators/metadata-record.js +20 -0
- package/dist/{errors/errors.d.ts → errors.d.ts} +84 -9
- package/dist/{errors/errors.js → errors.js} +101 -17
- package/dist/index.d.ts +14 -10
- package/dist/index.js +7 -6
- package/dist/injection/{resolve-options.js → dependency-slot.js} +6 -4
- package/dist/injection/descriptor.d.ts +1 -1
- package/dist/injection/descriptor.js +3 -7
- package/dist/introspection/dependency-graph.d.ts +8 -3
- package/dist/introspection/dependency-graph.js +48 -26
- package/dist/introspection/explanation.d.ts +101 -0
- package/dist/introspection/explanation.js +151 -0
- package/dist/introspection/graph-adapters/reactflow.js +6 -4
- package/dist/introspection/inspector.d.ts +9 -2
- package/dist/introspection/inspector.js +30 -28
- package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +9 -2
- package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +59 -30
- package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +3 -5
- package/dist/lifecycle/{scope-manager.js → scopes.js} +36 -29
- package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
- package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
- package/dist/metadata/types.js +1 -0
- package/dist/metadata/verify.d.ts +43 -0
- package/dist/metadata/verify.js +133 -0
- package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +5 -4
- package/dist/metadata/verifying-reader.js +53 -0
- package/dist/resolution/async-fan-out.d.ts +12 -0
- package/dist/resolution/async-fan-out.js +26 -0
- package/dist/resolution/cache/activation-need.d.ts +1 -2
- package/dist/resolution/cache/activation-need.js +16 -19
- package/dist/resolution/cache/class-introspector.d.ts +38 -36
- package/dist/resolution/cache/class-introspector.js +79 -159
- package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -9
- package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +70 -25
- package/dist/resolution/context.d.ts +16 -24
- package/dist/resolution/context.js +47 -56
- package/dist/resolution/path.d.ts +128 -0
- package/dist/resolution/path.js +167 -0
- package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +5 -4
- package/dist/resolution/plan/{plan-codegen.js → codegen.js} +58 -32
- package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
- package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +64 -24
- package/dist/resolution/resolver.d.ts +11 -12
- package/dist/resolution/resolver.js +573 -356
- package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
- package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
- package/dist/resolution/select/constraints.js +1 -1
- package/package.json +87 -67
- package/dist/container/binding-builders.js +0 -305
- package/dist/core/binding-scope.d.ts +0 -12
- package/dist/core/binding-scope.js +0 -12
- package/dist/metadata/verifying-metadata-reader.js +0 -31
- package/dist/resolution/path/resolution-path.d.ts +0 -93
- package/dist/resolution/path/resolution-path.js +0 -116
- /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
- /package/dist/decorators/{lifecycle-decorators.d.ts → lifecycle.d.ts} +0 -0
- /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
- /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
- /package/dist/{errors → introspection}/diagnostics.js +0 -0
- /package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +0 -0
- /package/dist/metadata/{metadata-keys.js → keys.js} +0 -0
- /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,381 @@
|
|
|
1
1
|
# @codefast/di
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1010](https://github.com/codefastlabs/codefast/pull/1010) **Breaking:** `resolveAsync`, `resolveOptionalAsync` and `resolveAllAsync` on a disposed container — or on a child of
|
|
8
|
+
one — now return a promise rejected with `DisposedContainerError` instead of throwing it, and `resolveAllAsync(token)`
|
|
9
|
+
likewise rejects when a `when()` predicate throws or an alias cycle is followed, so every async method reports its
|
|
10
|
+
failure through the promise it returns. Code that awaits these calls, or chains `.catch()`, behaves as before or better;
|
|
11
|
+
only a caller that wrapped the call itself in `try`/`catch` without awaiting it no longer sees the error there, and
|
|
12
|
+
should await it instead.
|
|
13
|
+
|
|
14
|
+
- [#995](https://github.com/codefastlabs/codefast/pull/995) Add `container.explain(token, options)`, which answers why a request selects the binding it does without instantiating
|
|
15
|
+
anything. It reads the container chain the way `resolve` does and decides by the same rules, so its answer is the
|
|
16
|
+
engine's own. It reports each registry it read with every candidate and its verdict (`eligible`, `slot-mismatch`,
|
|
17
|
+
`predicate-refused`, `collection-member`), the rule that decided (`sole-candidate`, `sole-predicate`, `most-criteria`,
|
|
18
|
+
`default-alias`, `ambiguous`), the binding the request ends on after every alias, and an `outcome` naming the error
|
|
19
|
+
`resolve` would throw when it ends on none. `when()` predicates run as they would in `resolve`, and `options.ancestors`
|
|
20
|
+
explains a request made from inside other resolutions, so a predicate that reads the parent sees the one it would.
|
|
21
|
+
|
|
22
|
+
- [#999](https://github.com/codefastlabs/codefast/pull/999) **Breaking:** a binding's slot is declared before its strategy —
|
|
23
|
+
`bind(Logger).whenNamed("file").to(FileLogger).singleton()`. `when()`, `whenNamed()`, `whenTagged()`, `whenDefault()`
|
|
24
|
+
and `many()` now come before `to*()` and are refused after it with `ChainAlreadyRegisteredError`, so a binding registers
|
|
25
|
+
once, in its final slot, and no later step moves it, displaces something else or brings back what it displaced. Scope
|
|
26
|
+
and lifecycle hooks stay after `to*()`. To migrate, move each chain's slot steps in front of its `to*()` call.
|
|
27
|
+
|
|
28
|
+
The registry no longer parks the bindings a re-slot displaced or restores them, so a bind with a name or a tag costs a
|
|
29
|
+
quarter to two fifths less. `BindingBuilder`, `ConstantBindingBuilder` and `AliasBindingBuilder` lose their `Names` type
|
|
30
|
+
parameter, since the slot is settled before they exist.
|
|
31
|
+
|
|
32
|
+
- [#1005](https://github.com/codefastlabs/codefast/pull/1005) **Breaking for deep subpath imports only** — the root entry `@codefast/di` is unchanged. `src/` now follows three rules,
|
|
33
|
+
and the published subpaths follow `src/` with no exception: a directory names a family of two or more modules and a lone
|
|
34
|
+
module sits flat at its layer; a directory carries the topic and a file its role, so no file repeats its directory's
|
|
35
|
+
name; and the exports map is the source tree, so the `strip` that kept the introspection modules at flat specifiers is
|
|
36
|
+
gone.
|
|
37
|
+
|
|
38
|
+
Moves: the `MetadataReader` verification leaves `resolution/cache/class-introspector` for `metadata/verify` — it was the
|
|
39
|
+
one value import pointing up from `metadata/` into `resolution/`; the `RESOLUTION_DIAGNOSTICS` channel leaves `errors/`
|
|
40
|
+
for `introspection/diagnostics`; the fluent chain's contract leaves `core/binding` for `core/binding-builders`, and the
|
|
41
|
+
class implementing it is `container/binding-chain`; `effectiveBindingScope` folds into `core/binding`.
|
|
42
|
+
|
|
43
|
+
Renamed specifiers: `./errors/errors` → `./errors`; `./errors/diagnostics` → `./introspection/diagnostics`;
|
|
44
|
+
`./ambient/active-container` → `./ambient-container`; `./container/binding-builders` → `./container/binding-chain`;
|
|
45
|
+
`./core/binding-scope` → gone (`effectiveBindingScope` is in `./core/binding`); `./injection/resolve-options` →
|
|
46
|
+
`./injection/dependency-slot`;
|
|
47
|
+
`./metadata/{metadata-keys,metadata-types,metadata-reader-token,symbol-metadata-reader,verifying-metadata-reader}` →
|
|
48
|
+
`./metadata/{keys,types,reader-token,symbol-reader,verifying-reader}`; `./lifecycle/{lifecycle-manager,scope-manager}` →
|
|
49
|
+
`./lifecycle/{hooks,scopes}`; `./decorators/{lifecycle-decorators,decorator-metadata}` →
|
|
50
|
+
`./decorators/{lifecycle,metadata-record}`; `./resolution/path/resolution-path` → `./resolution/path`;
|
|
51
|
+
`./resolution/plan/{instantiation-plan,plan-codegen}` → `./resolution/plan/{compiler,codegen}`;
|
|
52
|
+
`./resolution/select/binding-select` → `./resolution/select/candidates`; `./resolution/cache/binding-lookup-cache` →
|
|
53
|
+
`./resolution/cache/lookup`; `./{inspector,explanation,dependency-graph,graph-adapters/*}` →
|
|
54
|
+
`./introspection/{inspector,explanation,dependency-graph,graph-adapters/*}`. New: `./core/binding-builders`,
|
|
55
|
+
`./metadata/verify`.
|
|
56
|
+
|
|
57
|
+
`@codefast/di-testing` follows the one specifier it imports, `metadata/verifying-reader`.
|
|
58
|
+
|
|
59
|
+
### Patch Changes
|
|
60
|
+
|
|
61
|
+
- [#996](https://github.com/codefastlabs/codefast/pull/996) A request with criteria that matches no slot is fast again when the container holds no alias: `resolveOptional()` of a
|
|
62
|
+
missing tagged slot no longer probes for a default-slot alias to forward to in a registry that never held one.
|
|
63
|
+
|
|
64
|
+
- [#1009](https://github.com/codefastlabs/codefast/pull/1009) `resolveAsync` no longer rejects with `AsyncResolutionError` when a later sibling reads a singleton or scoped binding
|
|
65
|
+
synchronously — a `toDynamic` factory's `ctx.resolve`, an `@inject` accessor, a top-level `resolve` — while the async
|
|
66
|
+
lane is still materialising it from an earlier sibling. A level of the async lane now yields only where a dependency or
|
|
67
|
+
hook really awaits, so an instance built without yielding is cached before the next sibling starts, and only a
|
|
68
|
+
materialisation that is genuinely pending is published in flight.
|
|
69
|
+
|
|
70
|
+
- [#994](https://github.com/codefastlabs/codefast/pull/994) A scope refinement costs less at bind time. `singleton()`, `transient()` and `scoped()` now do nothing when the scope
|
|
71
|
+
does not change, and release only what exists when it does, so `bind(T).to(X).singleton()` straight after registration
|
|
72
|
+
no longer calls into the scope manager.
|
|
73
|
+
|
|
74
|
+
- [#997](https://github.com/codefastlabs/codefast/pull/997) Every read on a child container is cheaper. The child's check that no ancestor was disposed is now one epoch compare
|
|
75
|
+
that V8 inlines into each entry point, the walk up the chain runs only after some container was disposed, and a new
|
|
76
|
+
child starts confirmed live, so a per-request child skips the walk on its first read.
|
|
77
|
+
|
|
78
|
+
- [#994](https://github.com/codefastlabs/codefast/pull/994) Resolving a class binding for the first time is cheaper. The metadata cache now keeps one record per class, which a cold
|
|
79
|
+
resolve looks up once instead of reading a separate map for each fact, and a class with no constructor parameters is
|
|
80
|
+
built without allocating an empty argument list. This mostly speeds up containers that bind and resolve many class
|
|
81
|
+
singletons at startup.
|
|
82
|
+
|
|
83
|
+
- [#1001](https://github.com/codefastlabs/codefast/pull/1001) The first resolve of a class binding is cheaper. The class's parameter list, and the check that a subclass is not
|
|
84
|
+
dropping its base's declared dependencies, are settled once per class instead of once per instantiation, and the class
|
|
85
|
+
asked about last is answered without a map lookup, so a hundred singletons of one class bind and materialize about a
|
|
86
|
+
fifth faster.
|
|
87
|
+
|
|
88
|
+
- [#994](https://github.com/codefastlabs/codefast/pull/994) A root-level `resolveAll()` read in a loop over the same token, as an event bus does for every event, is cheaper: the
|
|
89
|
+
lookup cache keeps the last collection in front of its map, so a repeat read skips the map lookup.
|
|
90
|
+
|
|
91
|
+
- [#997](https://github.com/codefastlabs/codefast/pull/997) A container that reads a collection once no longer builds a value list it never reads again. A root-level `resolveAll`
|
|
92
|
+
keeps its stable value list from the second read on, the way an instantiation plan compiles on repeat, so a fresh
|
|
93
|
+
container per request pays only for the members it resolves.
|
|
94
|
+
|
|
95
|
+
- [#996](https://github.com/codefastlabs/codefast/pull/996) A resolve that carries options on a root container gets back much of the speed 0.11.0 took from it. The check that
|
|
96
|
+
refuses a disposed chain grew in that release enough that V8 stopped inlining the lookup behind that entry point; a live
|
|
97
|
+
root now pays two field reads for it, and only a child reads the dispose epoch.
|
|
98
|
+
|
|
99
|
+
- [#998](https://github.com/codefastlabs/codefast/pull/998) A root container's `resolve(token, options)` no longer pays for the check a child makes that no ancestor was disposed.
|
|
100
|
+
Each container is created with the `resolve` of its role, so the root's never carries the child's path and the rule that
|
|
101
|
+
a disposed ancestor refuses its descendants stays as it was.
|
|
102
|
+
|
|
103
|
+
- [#994](https://github.com/codefastlabs/codefast/pull/994) A scoped binding whose scope changes stops handing children the instance they cached before the change. A binding
|
|
104
|
+
registered on a parent, resolved from a child, then refined `scoped()` → `transient()` → `scoped()` gave that child its
|
|
105
|
+
old scoped instance back, because only the registering container's cache was cleared. Every container now files a scoped
|
|
106
|
+
instance under a key the binding replaces when it leaves `scoped`, so the cached instance is never found again.
|
|
107
|
+
|
|
108
|
+
- [#1003](https://github.com/codefastlabs/codefast/pull/1003) A teardown reads a class's `@preDestroy` methods once for a run of bindings of that class, instead of once per binding,
|
|
109
|
+
and names the token only when a hook fails.
|
|
110
|
+
|
|
111
|
+
- [#1002](https://github.com/codefastlabs/codefast/pull/1002) A rebind that owes no teardown allocates nothing for the binding it displaces, and a collection read skips its scan for
|
|
112
|
+
dangling aliases in a chain that never held one.
|
|
113
|
+
|
|
114
|
+
## 0.11.0
|
|
115
|
+
|
|
116
|
+
### Minor Changes
|
|
117
|
+
|
|
118
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) A default-slot alias is a transparent pointer: a request whose criteria no slot of the alias's own token matches is
|
|
119
|
+
forwarded, criteria and all, to its target — `resolve(AbstractLogger, { name: "file" })` reaches `Logger`'s `"file"`
|
|
120
|
+
binding. An exact slot on the alias's token still wins, the nearest container still answers first, and `has` agrees;
|
|
121
|
+
`resolveAll` keeps filtering by the alias token's own slots.
|
|
122
|
+
|
|
123
|
+
- [#896](https://github.com/codefastlabs/codefast/pull/896) Retire the async cascade lane: every `resolveAsync` now runs on the branch lane, so a factory's context answers from its
|
|
124
|
+
own ancestors before and after an `await`. A `whenParentIs` binding requested after an `await` is now selected as the
|
|
125
|
+
sync lane selects it, `ctx.graph.resolutionPath` names the level's own ancestors, and a cycle formed entirely from
|
|
126
|
+
post-`await` edges is named from the true root. A transient class or factory root with a statically visible graph is
|
|
127
|
+
answered by the compiled async plan. `AsyncCascadeContext` and `resolveAsyncFromCascade` are removed from
|
|
128
|
+
`resolution/context`; `ResolutionDiagnostics.builtSubsystems` reports `resolver.asyncRootLevel` once the async lane has
|
|
129
|
+
been entered.
|
|
130
|
+
|
|
131
|
+
- [#937](https://github.com/codefastlabs/codefast/pull/937) Add declared modules: `Module.fromBindings(name, [binding(key, definition), …])` (also `SyncModule.fromBindings`) builds
|
|
132
|
+
a `SyncModule` from a list, each `binding()` stating one binding with the fluent chain's own vocabulary — `to`,
|
|
133
|
+
`toSelf: true`, `toConstantValue`, `toDynamic`, `toDynamicAsync`, `toResolved` / `toResolvedAsync` with `deps`,
|
|
134
|
+
`toAlias`, then `whenNamed`, `whenTagged` (one criterion or several), `when`, `many: true`, `scope` and the lifecycle
|
|
135
|
+
hooks. A definition is typed exactly as the chain is — the key alone decides the value type and the slot names, and the
|
|
136
|
+
compiler rejects a second strategy, a scope on a constant or an alias, a hook on an alias, `onDeactivation` off a
|
|
137
|
+
singleton, and a slot on a collection member. `binding()` checks the same rules at runtime for plain JavaScript and
|
|
138
|
+
throws the new `InvalidBindingDeclarationError` (`INVALID_BINDING_DECLARATION`, with `tokenName` and `reason`), or
|
|
139
|
+
`ManyBindingSlotError` / `SelfBindingRequiresClassError` as the chain does.
|
|
140
|
+
|
|
141
|
+
A declared module loads, unloads, ref-counts and imports exactly as the equivalent `Module.create` setup would, and
|
|
142
|
+
registers each binding already in its final shape, so a binding with a slot, a scope or a hook skips the chain steps a
|
|
143
|
+
fluent setup pays on every load. Nothing a resolve reads changes. `SyncModule`'s `MODULE_SETUP` member now holds either
|
|
144
|
+
the setup callback or the declarations.
|
|
145
|
+
|
|
146
|
+
- [#925](https://github.com/codefastlabs/codefast/pull/925) A constant that a plain last-wins `bind()` displaces now still runs its `onDeactivation` hook at `dispose()`. A
|
|
147
|
+
displaced constant left the registry — so the dispose sweep, which walks the registry, never reached it — and, carrying
|
|
148
|
+
no cached instance, it was absent from the singleton cache too, falling between the two. The container now records a
|
|
149
|
+
displaced constant that still owes a deactivation and drains it at dispose; a refinement that restores the binding (a
|
|
150
|
+
`when*()` that moves the winner off the slot) takes it back out of that set, so nothing deactivates twice. The
|
|
151
|
+
park-and-restore behaviour of `bind()` is otherwise unchanged.
|
|
152
|
+
|
|
153
|
+
- [#931](https://github.com/codefastlabs/codefast/pull/931) Disposing a container now reaches its descendants. A child whose parent (or any ancestor) has been disposed is itself
|
|
154
|
+
disposed: `isDisposed` reads `true`, and every `resolve`, `has` and mutation is refused with `DisposedContainerError`,
|
|
155
|
+
including the child's own bindings — previously a child kept building transients and constants from a torn-down chain,
|
|
156
|
+
and `child.has` and `child.resolve` disagreed. The guard keeps the resolve path cheap: a root still reads only its own
|
|
157
|
+
`#disposed` field, and a child adds one call-free compare of a dedicated dispose-epoch cell against the epoch at which
|
|
158
|
+
its chain was last confirmed live — the ancestor walk runs only after some container in the process is disposed. The
|
|
159
|
+
dispose epoch is separate from the state epoch, so a per-request child's dispose does not invalidate the chain-version
|
|
160
|
+
memo that keeps deep resolves cheap, and `createChild()` is untouched. `resolveAsync` refuses a disposed chain with a
|
|
161
|
+
synchronous throw at the entry, matching how it already guarded a self-disposed container, and `dispose()` stays ungated
|
|
162
|
+
so an `await using` child of a disposed ancestor still tears down cleanly.
|
|
163
|
+
|
|
164
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) `generateDependencyGraph({ includeParent: true })` follows resolution's own walk up the chain: every ancestor's bindings
|
|
165
|
+
join the graph, a single dependency connects to the nearest container holding a binding its slot matches — a child
|
|
166
|
+
binding the request cannot select no longer hides the parent's — and an `injectAll` fans out across the whole chain.
|
|
167
|
+
`buildDependencyGraph` takes the ancestor registries, nearest first, in place of the parent registry.
|
|
168
|
+
|
|
169
|
+
- [#921](https://github.com/codefastlabs/codefast/pull/921) A subclass that inherits declared constructor dependencies but declares none of its own — the natural
|
|
170
|
+
`class Derived extends Base {}` with an implicit constructor — is now rejected with `MissingMetadataError` instead of
|
|
171
|
+
being built with `undefined` arguments. Constructor metadata is opt-in per class, so an implicit constructor previously
|
|
172
|
+
slipped through the `target.length === 0` check and produced a silently-broken instance. The error names the base and
|
|
173
|
+
how many dependencies it declares. A subclass whose own `@injectable([])` declares zero deps is unaffected — it is built
|
|
174
|
+
with zero arguments, as declared.
|
|
175
|
+
|
|
176
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) Decorators report a runtime without `Symbol.metadata` as `MissingDecoratorMetadataError` at the declaration, naming the
|
|
177
|
+
decorator and the fix (`(Symbol as { metadata?: symbol }).metadata ??= Symbol.for("Symbol.metadata")` in a module
|
|
178
|
+
imported first), instead of failing with a bare `TypeError` on the first metadata write. TypeScript compiles
|
|
179
|
+
`context.metadata` to `undefined` on such a runtime; the library does not install the symbol itself, since the package
|
|
180
|
+
declares no side effects.
|
|
181
|
+
|
|
182
|
+
- [#926](https://github.com/codefastlabs/codefast/pull/926) Module `unload` is now symmetric with `import`, and `unbindAll` resets module bookkeeping. An `import` inside a module's
|
|
183
|
+
setup incremented the imported module's ref-count but nothing ever decremented it, so `unload(A)` left a module `A`
|
|
184
|
+
imported behind bound forever — the container now records each module's imports and, when a module's own ref-count
|
|
185
|
+
reaches 0, releases the imports it took, unbinding an imported module only when its ref-count reaches 0. And
|
|
186
|
+
`unbindAll()` / `unbindAllAsync()` now clear the module ref-count, binding-id and import tables after deactivation, so a
|
|
187
|
+
module loaded before `unbindAll` can be `load()`ed again instead of being silently skipped as already-loaded.
|
|
188
|
+
|
|
189
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) A sync `rebind()` whose old binding owes an async deactivation now fails exactly like a sync `unbind()` whatever shape
|
|
190
|
+
the token has: `rebind()` itself throws `AsyncDeactivationError`, the old bindings are removed, and nothing is
|
|
191
|
+
committed. A lone default binding used to let `rebind()` pass and then throw from `.to*()` after the replacement was
|
|
192
|
+
already registered.
|
|
193
|
+
|
|
194
|
+
- [#929](https://github.com/codefastlabs/codefast/pull/929) `runWithContainer` is now typed as synchronous-only. The ambient container lives in a module-level variable restored in
|
|
195
|
+
a `finally`, so it lasts only the callback's synchronous run — it does not survive an `await`. A callback returning a
|
|
196
|
+
`Promise` was silently accepted and lost the context; its return type is now
|
|
197
|
+
`Result extends Promise<unknown> ? never : Result`, so an async callback resolves to `never`.
|
|
198
|
+
`MissingContainerContextError` now says the context does not survive an `await`, and the TSDoc, README and SPEC state
|
|
199
|
+
the sync-only constraint. The engine internals are unchanged — they only ever ran synchronous callbacks.
|
|
200
|
+
|
|
201
|
+
- [#894](https://github.com/codefastlabs/codefast/pull/894) Four size thresholds are gone, each replaced by the algorithm it approximated: alias chains fold exactly to any length
|
|
202
|
+
(`ALIAS_HOP_LIMIT` is removed from `resolution/cache/binding-lookup-cache`); a generated plan is one statement per node
|
|
203
|
+
and inlines to any depth; every synchronous lane checks a cycle by the binding's in-flight flag at any depth
|
|
204
|
+
(`RESOLUTION_SET_THRESHOLD` and `enterResolutionPath` are removed from `resolution/path/resolution-path`); a
|
|
205
|
+
multi-criterion request is selected by one scan at any binding count. `PLAN_CODEGEN_THRESHOLD` is 1024, the measured
|
|
206
|
+
break-even of generating a plan against running its closure.
|
|
207
|
+
|
|
208
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) `UnreachableLifecycleHookError` carries a `reason` — `"unbound"` or `"no-deactivatable-binding"` — and an
|
|
209
|
+
`onDeactivation` hook on a token whose every binding is transient or scoped is reported as that, rather than as a token
|
|
210
|
+
nothing is bound to.
|
|
211
|
+
|
|
212
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) `validate()` checks an `optional()` dependency whenever it is bound, so a singleton capturing a bound transient or
|
|
213
|
+
scoped dependency through `optional()` is a `ScopeViolationError`; an optional dependency that is not bound still
|
|
214
|
+
imposes nothing.
|
|
215
|
+
|
|
216
|
+
- [#976](https://github.com/codefastlabs/codefast/pull/976) `engines.node` is now `>=24.0.0`, up from `>=22.12.0`, and Node 22 is no longer supported. Node 24.0.0 is the first
|
|
217
|
+
release with explicit resource management built in (`using`, `await using`, `DisposableStack`, `AsyncDisposableStack`,
|
|
218
|
+
`SuppressedError`) and all of ES2025, so the packages use both as the platform ships them instead of shimming them for
|
|
219
|
+
an older line, and the CI matrix runs the unit suite on 24.0.0 itself. Move to Node 24, or stay on the current minor
|
|
220
|
+
while a deployment still runs Node 22.
|
|
221
|
+
|
|
222
|
+
### Patch Changes
|
|
223
|
+
|
|
224
|
+
- [#977](https://github.com/codefastlabs/codefast/pull/977) The README states the browser floor: Chrome and Edge 136, Firefox 136, and Safari 18.4 or later, the first releases that
|
|
225
|
+
ship every ES2025 builtin. `@codefast/di`'s README also says what a browser program without explicit resource management
|
|
226
|
+
does: it keeps `skipLibCheck` on and calls `dispose()` instead of `await using`.
|
|
227
|
+
|
|
228
|
+
- [#922](https://github.com/codefastlabs/codefast/pull/922) A lifecycle hook that returns a rejecting `Promise` when reached from a synchronous lane no longer crashes the process.
|
|
229
|
+
`runActivationSync` and `runDeactivationSync` call each hook before they can tell it is async, so on discovering a
|
|
230
|
+
returned `Promise` they now adopt its rejection (a no-op `.catch`) before throwing `AsyncActivationError` /
|
|
231
|
+
`AsyncDeactivationError` — a rejecting `@postConstruct`, `onActivation`, `onDeactivation` or `@preDestroy` can no longer
|
|
232
|
+
become an unhandled rejection that ends the process. The hook has still run; retry on `resolveAsync` / `unbindAsync` to
|
|
233
|
+
await it properly.
|
|
234
|
+
|
|
235
|
+
- [#896](https://github.com/codefastlabs/codefast/pull/896) An async level with several dependencies, and a `resolveAllAsync` collection, report the first failing dependency in
|
|
236
|
+
declaration order — the order the sync lanes report — instead of whichever rejection happened to settle first. Every
|
|
237
|
+
dependency still starts before anything is reported. A node with one dependency awaits it directly, with no fan-out to
|
|
238
|
+
settle.
|
|
239
|
+
|
|
240
|
+
- [#924](https://github.com/codefastlabs/codefast/pull/924) `child.has(token)` no longer throws when a parent container is disposed but the child is not. `has` recursed through the
|
|
241
|
+
parent's **public** `has`, which asserts the container is live, so a live child that could still `resolve` a
|
|
242
|
+
parent-owned token threw `DisposedContainerError` from `has` alone — breaking the `if (c.has(token)) c.resolve(token)`
|
|
243
|
+
guard on a healthy container. It now recurses through an internal `#hasInChain` that reads the parent's registry
|
|
244
|
+
directly, keeping the disposed-guard on the public entry point of the container being called.
|
|
245
|
+
|
|
246
|
+
- [#894](https://github.com/codefastlabs/codefast/pull/894) The cold path allocates only what it uses: a container builds its lookup memo, class introspector, context pools and
|
|
247
|
+
lone map on first use; a root's plan is compiled on the request that repeats it, so a container that resolves a root
|
|
248
|
+
once never compiles; a binding's activation need is stamped on the binding instead of memoized in a per-resolver map
|
|
249
|
+
(`ResolutionDiagnostics.builtSubsystems` no longer lists `resolver.activationNeedMemo`); teardown clears instances
|
|
250
|
+
without splicing the singleton list or pairing each binding; a rebind of a lone token is one registration whose
|
|
251
|
+
displaced binding is deactivated on the spot; a fresh registration is one probe and one write; a chain's own `.many()`
|
|
252
|
+
re-slots without probing the registry; each error class names itself with a literal.
|
|
253
|
+
|
|
254
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) A reader passed through `ContainerOptions.metadataReader` now outranks a `MetadataReaderToken` binding for that
|
|
255
|
+
container's children too, at every depth, as it already did for the container itself; a token binding still reaches the
|
|
256
|
+
children of a container given no reader.
|
|
257
|
+
|
|
258
|
+
- [#920](https://github.com/codefastlabs/codefast/pull/920) `resolveOptional`, `resolveOptionalAsync`, `resolveAll` and `resolveAllAsync` no longer throw when they reach an alias
|
|
259
|
+
whose chain ends at a token nothing matches. An alias is a transparent pointer, so a dangling chain is the same miss the
|
|
260
|
+
target itself would be: the optional lanes return `undefined`, and a dangling alias member is skipped from a collection
|
|
261
|
+
rather than failing the whole fan-out. A required `resolve` / `resolveAsync` still throws, and an alias **cycle** still
|
|
262
|
+
throws `CircularDependencyError` — a cycle has no absent reading.
|
|
263
|
+
|
|
264
|
+
- [#976](https://github.com/codefastlabs/codefast/pull/976) The README states what a program needs for the declarations' disposal members: the explicit resource management types,
|
|
265
|
+
which no numbered `lib` declares before ES2027. `@types/node` 24 or later loads them, and so does `ESNext.Disposable` in
|
|
266
|
+
`lib`. Without either, TypeScript 7 fails inside `container.d.ts` with TS2550 under `skipLibCheck: false`, and at
|
|
267
|
+
`await using` with TS2318. `@codefast/di-testing` drops a `/// <reference lib="esnext.disposable" />` that TypeScript 7
|
|
268
|
+
stripped from its emitted declarations anyway, and takes the lib from its `tsconfig.json` instead.
|
|
269
|
+
|
|
270
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) A dependency slot declaring `tags: []` states no criterion, as a binding slot already did:
|
|
271
|
+
`injectionSlotToResolveOptions` answers `undefined` for it, and such a dependency no longer escapes a compiled plan.
|
|
272
|
+
|
|
273
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) A `MetadataReader` passed through `ContainerOptions` is asked about a class once for the life of the reader: its answers
|
|
274
|
+
are memoized per class, and every container handed the same reader shares one verifying wrapper. Lifecycle metadata used
|
|
275
|
+
to be read on every activation, and each root container asked again.
|
|
276
|
+
|
|
277
|
+
- [#894](https://github.com/codefastlabs/codefast/pull/894) The React Flow graph export lays its nodes out on a square grid derived from the node count rather than a fixed five
|
|
278
|
+
columns.
|
|
279
|
+
|
|
280
|
+
- [#934](https://github.com/codefastlabs/codefast/pull/934) `MissingMetadataError` for a subclass that inherits declared constructor dependencies no longer advises giving it an
|
|
281
|
+
explicit constructor, which is rejected the same way. It points at `@injectable([...deps])` — `@injectable([])` for a
|
|
282
|
+
class that takes none — or a `toDynamic()`/`toResolved()` binding.
|
|
283
|
+
|
|
284
|
+
- [#894](https://github.com/codefastlabs/codefast/pull/894) Every resolution lane now answers a graph identically. A per-request child that misses a token its parent's bindings all
|
|
285
|
+
declined reports `NoMatchingBindingError` as the parent does, not `TokenNotBoundError`; a sibling on the async
|
|
286
|
+
interpreted lane is selected against its own path rather than the earlier sibling's frame; a dynamic factory that
|
|
287
|
+
resolves its own token from its synchronous prefix is reported as a cycle before it runs a second time.
|
|
288
|
+
|
|
289
|
+
- [#905](https://github.com/codefastlabs/codefast/pull/905) The chain-versioned lookup memo answers a repeated token from a method small enough for its hot callers to inline: the
|
|
290
|
+
hit is the whole of `defaultEntry`, and everything that fills the memo is the miss. An alias resolve, a parent-owned
|
|
291
|
+
resolve from a child and every other lookup that reaches the memo pays one inlined compare where it paid a call.
|
|
292
|
+
|
|
293
|
+
- [#938](https://github.com/codefastlabs/codefast/pull/938) A constant with an `onDeactivation` hook that a bind inside a module load displaces now runs that hook at `dispose()`,
|
|
294
|
+
as it already did when a plain `container.bind()` displaced it. A module's chains registered without the container's
|
|
295
|
+
displacement bookkeeping, so the displaced constant left the registry untracked and nothing ever tore it down; this held
|
|
296
|
+
for `Module.create` setups and declared modules (`Module.fromBindings`) alike. Unloading the displacing module still
|
|
297
|
+
does not restore the displaced binding — its deactivation stays owed to `dispose()`.
|
|
298
|
+
|
|
299
|
+
- [#918](https://github.com/codefastlabs/codefast/pull/918) A `{ name, tag }` request now selects the same binding as its `{ tags: [slotName.of(n), tag] }` spelling, closing two
|
|
300
|
+
selection bugs. A name that has been interned nowhere in the process no longer makes the request a miss: the request's
|
|
301
|
+
criteria can still be a superset of a slot's, so an index miss falls through to the scan and the parent walk instead of
|
|
302
|
+
returning early — the answer no longer depends on whether unrelated code has ever called `whenNamed` with that string.
|
|
303
|
+
And the name-plus-tag fast lane now declines to full selection whenever the token carries any `when()` predicate
|
|
304
|
+
candidate, so the more-specific rule's predicate step is honoured on this lane as on every other.
|
|
305
|
+
|
|
306
|
+
- [#896](https://github.com/codefastlabs/codefast/pull/896) Every binding predicate now reads one `ConstraintContext` shape: the shared root context, the context a selection builds
|
|
307
|
+
over a live path, an async level's prefix and the inspector's probe are all `DefaultConstraintContext`, exported from
|
|
308
|
+
`resolution/context`. A predicate's call site stays monomorphic, and the per-selection object literal with its eager
|
|
309
|
+
`ancestors` slice is gone — `ancestors` is now sliced on first read, as `ctx.graph` already did.
|
|
310
|
+
|
|
311
|
+
- [#916](https://github.com/codefastlabs/codefast/pull/916) `LEARNING.md` is removed, and with it the Learning page the docs site rendered for this package. What a shape is and
|
|
312
|
+
what it guarantees stays in `ARCHITECTURE.md`, the behavioural contract in `SPEC.md`, and usage in `README.md`, whose
|
|
313
|
+
document list no longer points at the removed file.
|
|
314
|
+
|
|
315
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) `resolveAll` returns a token's bindings in registration order however a chain refines its own binding. Binding a default
|
|
316
|
+
and then a named, tagged, member or predicate binding used to list the later one first, because the refinement re-added
|
|
317
|
+
what it had displaced behind it; each binding now keeps the place its first registration gave it.
|
|
318
|
+
|
|
319
|
+
- [#902](https://github.com/codefastlabs/codefast/pull/902) A transient factory root resolved with `resolveAsync()` is handed one resolution context per binding, built on the first
|
|
320
|
+
resolve and reused by every later one — its path is its own frame alone and the request carries no options, so the
|
|
321
|
+
context is a function of the binding. The root allocated an array and a context per resolve; it allocates nothing now,
|
|
322
|
+
and a concurrent root reads the same, correct, path after an `await`.
|
|
323
|
+
|
|
324
|
+
- [#919](https://github.com/codefastlabs/codefast/pull/919) A `NoMatchingBindingError`'s "Available slots" diagnostic no longer throws when a bound slot carries a tag value that
|
|
325
|
+
cannot be stringified — a bigint, a null-prototype object, or one whose `toString` throws. `bindingSlotToString` (and
|
|
326
|
+
the dependency-graph edge label) now render such a value as `<unprintable>` through a shared `stringifyTagValue` guard,
|
|
327
|
+
so the real `NoMatchingBindingError` surfaces instead of a masking `TypeError`, matching the guard the request-side
|
|
328
|
+
diagnostic already had.
|
|
329
|
+
|
|
330
|
+
- [#932](https://github.com/codefastlabs/codefast/pull/932) `SPEC.md` carries the behavioural contract and nothing else. The source-tree listing, the copied export barrel, the
|
|
331
|
+
build configuration and the roadmap of already-shipped work are gone — a reader can now rename any file under `src/`
|
|
332
|
+
without contradicting the spec. `Public API` states the surface as a rule instead of a second copy of `index.ts`, which
|
|
333
|
+
had already drifted from it, and `Scope and requirements` states the runtime, module format and TypeScript floor a
|
|
334
|
+
consumer must bring. New `DECISIONS.md` takes the background and the whole InversifyJS v8 comparison, `README.md` takes
|
|
335
|
+
the testing patterns with the `MetadataReader` example corrected to `Container.create({ metadataReader })`, and
|
|
336
|
+
`CONTRIBUTING.md` takes the packaging and decorator-toolchain notes.
|
|
337
|
+
|
|
338
|
+
Checking the spec against the source turned up four public names it never described: `BindingSnapshot.isMany`, the
|
|
339
|
+
`DependencySlot` that both `InjectionDescriptor` and `ParamMetadata` extend, and `bindingSlotToResolveOptions`, which
|
|
340
|
+
turns a snapshot's slot back into the `ResolveOptions` that selects it. All three are specified now, and the claim that
|
|
341
|
+
`toAlias()` returns the one builder with no type parameter is corrected — it has no _value_ type parameter, but it does
|
|
342
|
+
carry the token's slot names.
|
|
343
|
+
|
|
344
|
+
- [#927](https://github.com/codefastlabs/codefast/pull/927) `@postConstruct` / `@preDestroy` on a symbol-keyed method now fail at the declaration with the new
|
|
345
|
+
`SymbolKeyedLifecycleError`, instead of a misleading `InvalidMetadataError` at resolve that blamed a `MetadataReader`
|
|
346
|
+
the caller never configured. The lifecycle reader keys methods by their string name, so a symbol-keyed method could
|
|
347
|
+
never be found again; the decorator now rejects it up front, where the mistake is.
|
|
348
|
+
|
|
349
|
+
- [#957](https://github.com/codefastlabs/codefast/pull/957) `MissingDecoratorMetadataError` suggests installing `Symbol.metadata` as
|
|
350
|
+
`(Symbol as { metadata?: symbol }).metadata ??= Symbol.for("Symbol.metadata")`, which type-checks in a `.ts` file; the
|
|
351
|
+
bare `Symbol.metadata ??= …` it quoted failed with TS2540, since TypeScript declares the symbol `readonly`.
|
|
352
|
+
|
|
353
|
+
- [#933](https://github.com/codefastlabs/codefast/pull/933) A rejected async `onActivation` on a transient `toDynamic` binding — per-binding or container-level — no longer surfaces
|
|
354
|
+
as an unhandled rejection: the sync resolve still throws `AsyncActivationError`, and the hook's promise is adopted as it
|
|
355
|
+
already was on every other lane.
|
|
356
|
+
|
|
357
|
+
- [#965](https://github.com/codefastlabs/codefast/pull/965) The documented TypeScript floor is now 7, up from 5.9: TypeScript 7 is the one compiler that type-checks the package and
|
|
358
|
+
emits its published declarations, so it is the floor they support. `README.md`, `SPEC.md` and `DECISIONS.md` state it;
|
|
359
|
+
no code or declaration changed.
|
|
360
|
+
|
|
361
|
+
- [#940](https://github.com/codefastlabs/codefast/pull/940) `unbind(id)` and `unbindAsync(id)` now tear down a binding that a later last-wins bind displaced: its cached singleton
|
|
362
|
+
is deactivated, and a displaced constant runs its `onDeactivation`, at that call instead of waiting for `dispose()`. The
|
|
363
|
+
id lookup asked only the registry, which a displaced binding had already left, so the call did nothing. The unbind also
|
|
364
|
+
invalidates what the displacing chain holds parked, so a later `when*()` refinement of that chain can no longer restore
|
|
365
|
+
the unbound binding.
|
|
366
|
+
|
|
367
|
+
- [#939](https://github.com/codefastlabs/codefast/pull/939) Unloading a module now tears down a binding it registered that a later last-wins bind displaced: a displaced cached
|
|
368
|
+
singleton is deactivated, and a displaced constant runs its `onDeactivation`, at that `unload()`/`unloadAsync()` instead
|
|
369
|
+
of waiting for `dispose()`. Unload looked each of the module's bindings up in the registry, which a displaced binding
|
|
370
|
+
had already left, so it was skipped. The unload also invalidates what the displacing chain holds parked, so a later
|
|
371
|
+
`when*()` refinement of that chain can no longer restore a binding of the unloaded module.
|
|
372
|
+
|
|
373
|
+
- [#923](https://github.com/codefastlabs/codefast/pull/923) `validate()` now reports a container-level `onDeactivation` hook that can never run. The builder type blocks
|
|
374
|
+
`onDeactivation` on `scoped`/`transient` bindings, but `container.onDeactivation(token, handler)` takes any token with
|
|
375
|
+
no such gate, so a hook keyed to a token whose every binding is scoped or transient used to pass validation silently.
|
|
376
|
+
`validate()` now raises `UnreachableLifecycleHookError` for it, reusing the mechanism that already catches a hook on an
|
|
377
|
+
unbound token. A token that also has a singleton or constant binding, and any `onActivation` hook, stay valid.
|
|
378
|
+
|
|
3
379
|
## 0.10.1
|
|
4
380
|
|
|
5
381
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -35,9 +35,14 @@ its keep as the dependency graph grows.
|
|
|
35
35
|
pnpm add @codefast/di
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
`@codefast/di`
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
`@codefast/di` runs on Node.js 24 or later and in Chrome and Edge 136, Firefox 136, or Safari 18.4 or later
|
|
39
|
+
([support policy](../../SUPPORT.md#browsers)). It requires TypeScript 7 or later, with native Stage 3 decorators. Leave
|
|
40
|
+
`experimentalDecorators` off — it's off by default. Its declarations use explicit resource management, which no numbered
|
|
41
|
+
`lib` declares yet, so your program needs those types: `@types/node` 24 or later loads them, and any other program adds
|
|
42
|
+
`ESNext.Disposable` to `lib` once its runtime ships explicit resource management. A browser program that targets Safari,
|
|
43
|
+
which has not shipped it, keeps `skipLibCheck` on and calls `dispose()` instead of `await using`. The package is
|
|
44
|
+
published on 0.x and versioned on its own track: breaking changes ship as minor versions, so pin the minor version when
|
|
45
|
+
you need stability.
|
|
41
46
|
|
|
42
47
|
## Quick start
|
|
43
48
|
|
|
@@ -164,8 +169,8 @@ constraints is its slot — think of a slot as a labelled variant of the token.
|
|
|
164
169
|
`{ name }`:
|
|
165
170
|
|
|
166
171
|
```ts
|
|
167
|
-
container.bind(LoggerToken).
|
|
168
|
-
container.bind(LoggerToken).
|
|
172
|
+
container.bind(LoggerToken).whenNamed("console").toConstantValue(consoleLogger);
|
|
173
|
+
container.bind(LoggerToken).whenNamed("file").toConstantValue(fileLogger);
|
|
169
174
|
|
|
170
175
|
container.resolve(LoggerToken, { name: "file" }); // → fileLogger
|
|
171
176
|
```
|
|
@@ -175,7 +180,7 @@ Declare the names on the token and they become checked, completable literals at
|
|
|
175
180
|
```ts
|
|
176
181
|
const LoggerToken = token<Logger, "console" | "file">("app:Logger");
|
|
177
182
|
|
|
178
|
-
container.bind(LoggerToken).
|
|
183
|
+
container.bind(LoggerToken).whenNamed("file").toConstantValue(fileLogger);
|
|
179
184
|
container.resolve(LoggerToken, { name: "file" }); // { name: "fiel" } is a compile error
|
|
180
185
|
```
|
|
181
186
|
|
|
@@ -188,7 +193,7 @@ import { tag } from "@codefast/di";
|
|
|
188
193
|
|
|
189
194
|
const Provider = tag<"s3" | "gcs">("app:provider");
|
|
190
195
|
|
|
191
|
-
container.bind(StorageToken).
|
|
196
|
+
container.bind(StorageToken).whenTagged(Provider.of("s3")).to(S3Storage);
|
|
192
197
|
container.resolve(StorageToken, { tag: Provider.of("s3") }); // → S3Storage
|
|
193
198
|
```
|
|
194
199
|
|
|
@@ -225,7 +230,7 @@ predicates ship from the root entry:
|
|
|
225
230
|
| `whenAnyAncestorTaggedAll(criteria)` | some ancestor's slot carries all criteria in the array |
|
|
226
231
|
|
|
227
232
|
For the exact matching and most-specific-wins rules, see
|
|
228
|
-
[
|
|
233
|
+
[the specification → Slots and last-wins](./spec/binding.md#slots-and-last-wins--the-exact-definition).
|
|
229
234
|
|
|
230
235
|
## Decorators
|
|
231
236
|
|
|
@@ -282,7 +287,8 @@ const byHand = runWithContainer(container, () => new Controller());
|
|
|
282
287
|
|
|
283
288
|
An accessor resolves from the container that's constructing the instance. When something else owns the `new` — a router,
|
|
284
289
|
an ORM, a test helper — open that context with `runWithContainer`. Without one, the accessor throws
|
|
285
|
-
`MissingContainerContextError`. `getActiveContainer()` reads the open context.
|
|
290
|
+
`MissingContainerContextError`. `getActiveContainer()` reads the open context. `runWithContainer` is **synchronous
|
|
291
|
+
only**: the context does not survive an `await`, so a callback returning a `Promise` resolves to `never`.
|
|
286
292
|
|
|
287
293
|
### Auto-registration
|
|
288
294
|
|
|
@@ -361,6 +367,18 @@ exits. Synchronous `using` isn't supported — `onDeactivation` may be async —
|
|
|
361
367
|
token's bindings; and `container.generateDependencyGraph()` returns a JSON graph. Adapters render that graph for common
|
|
362
368
|
viewers: `toDotGraph`, `toMermaidGraph`, `toCytoscapeGraph`, and `toReactFlowGraph`.
|
|
363
369
|
|
|
370
|
+
`container.explain(token, options)` answers why a request selects the binding it does, without instantiating anything:
|
|
371
|
+
every candidate each registry offered and what selection made of it, the rule that decided, and the binding it ends on.
|
|
372
|
+
It evaluates `when()` predicates the way `resolve` does, and `options.ancestors` explains a request made from inside
|
|
373
|
+
other resolutions, so a predicate that reads the parent sees the one it would:
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
const explanation = container.explain(Logger, { ancestors: [Settlement] });
|
|
377
|
+
|
|
378
|
+
explanation.selected?.id; // the binding `resolve` would answer with
|
|
379
|
+
explanation.steps[0]?.rule; // "sole-predicate": the only candidate with a `when()` beat the rest
|
|
380
|
+
```
|
|
381
|
+
|
|
364
382
|
## Modules
|
|
365
383
|
|
|
366
384
|
A module is a reusable, stateless bundle of related bindings. Group them once, then load them into any container.
|
|
@@ -384,6 +402,62 @@ const container = Container.fromModules(AppModule);
|
|
|
384
402
|
`Container.fromModulesAsync` or `container.loadAsync`. Modules are ref-counted: loading one twice counts once, and
|
|
385
403
|
`unload` removes its bindings only when the count reaches zero.
|
|
386
404
|
|
|
405
|
+
A module with no logic of its own can be written as a list instead. Each `binding()` states one binding with the fluent
|
|
406
|
+
chain's own vocabulary, is typed exactly as the chain is, and is checked where it is written:
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import { binding, Module } from "@codefast/di";
|
|
410
|
+
|
|
411
|
+
const InfrastructureModule = Module.fromBindings("app:Infra", [
|
|
412
|
+
binding(LoggerToken, { toConstantValue: console }),
|
|
413
|
+
binding(UserRepository, { toSelf: true, scope: "singleton" }),
|
|
414
|
+
binding(CacheToken, { toDynamic: () => new Map(), whenNamed: "session", scope: "scoped" }),
|
|
415
|
+
]);
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
It loads exactly as the equivalent `Module.create` would, and never slower: each binding registers in its final shape,
|
|
419
|
+
so one with a slot, a scope or a hook skips the chain steps a fluent setup pays on every load. A declared module mixes
|
|
420
|
+
into a fluent one with `api.import(...)`.
|
|
421
|
+
|
|
422
|
+
## Testing
|
|
423
|
+
|
|
424
|
+
Give every test its own container and dispose it afterwards. A container shared across tests carries cached singletons
|
|
425
|
+
from one into the next.
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
let container: Container;
|
|
429
|
+
|
|
430
|
+
beforeEach(() => {
|
|
431
|
+
container = Container.create();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
afterEach(async () => {
|
|
435
|
+
await container.dispose();
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
To swap one binding out of a module you already loaded, bind it at a **child** container — the child has no binding of
|
|
440
|
+
its own yet, and resolution prefers the child over its parent:
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
const testContainer = Container.fromModules(AppModule).createChild();
|
|
444
|
+
testContainer.bind(DbToken).toConstantValue(fakeDatabase);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
`rebind()` is for replacing a binding that already exists in the **same** container — it throws
|
|
448
|
+
`RebindUnboundTokenError` when the token is only bound at a parent, so it is the wrong tool for the override above.
|
|
449
|
+
|
|
450
|
+
To construct a class the container was never told about — plain JavaScript, generated code, a class you cannot decorate
|
|
451
|
+
— supply the metadata yourself. Pass the reader to `Container.create`: a reader bound through `MetadataReaderToken` on
|
|
452
|
+
the container you are about to use is read too late, because the resolver was built with the container.
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
const container = Container.create({ metadataReader: customReader });
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
For unit tests where every collaborator should be a mock, [`@codefast/di-testing`](../di-testing/README.md) builds the
|
|
459
|
+
mocks from the class's own declared dependencies, through a real container.
|
|
460
|
+
|
|
387
461
|
## Errors
|
|
388
462
|
|
|
389
463
|
Every error extends `DiError` and carries a stable `code`, so you can branch on the code rather than the message:
|
|
@@ -402,18 +476,21 @@ Every error extends `DiError` and carries a stable `code`, so you can branch on
|
|
|
402
476
|
| `DisposedContainerError` | `DISPOSED_CONTAINER` | A disposed container is used |
|
|
403
477
|
|
|
404
478
|
The full taxonomy — including `MissingMetadataError`, `InvalidMetadataError`, `RebindUnboundTokenError`,
|
|
405
|
-
`AsyncModuleLoadError`, and the rest — is exported from the root entry and from
|
|
479
|
+
`AsyncModuleLoadError`, `InvalidBindingDeclarationError`, and the rest — is exported from the root entry and from
|
|
480
|
+
`@codefast/di/errors`.
|
|
406
481
|
|
|
407
482
|
## Subpath exports
|
|
408
483
|
|
|
409
484
|
Prefer the root entry: it re-exports the whole public API. Reach for a subpath only to trim a bundle, or to pull in
|
|
410
485
|
something the root doesn't surface.
|
|
411
486
|
|
|
412
|
-
Every module is also published as a subpath that
|
|
413
|
-
(`core/token`, `core/tag`, `core/module`, …),
|
|
414
|
-
`lifecycle/*`, and `resolution/*` (for example
|
|
415
|
-
metadata under `decorators/*` and `metadata
|
|
416
|
-
`@codefast/di/
|
|
487
|
+
Every module is also published as a subpath that is its source path, with no exception: the model under
|
|
488
|
+
`@codefast/di/core/*` (`core/token`, `core/tag`, `core/module`, …), the error taxonomy at `@codefast/di/errors`, the
|
|
489
|
+
runtime under `container/*`, `injection/*`, `lifecycle/*`, and `resolution/*` (for example
|
|
490
|
+
`@codefast/di/resolution/select/constraints`), decorators and metadata under `decorators/*` and `metadata/*`, and
|
|
491
|
+
introspection under `introspection/*`: `@codefast/di/introspection/inspector`, `@codefast/di/introspection/explanation`,
|
|
492
|
+
`@codefast/di/introspection/dependency-graph`, and
|
|
493
|
+
`@codefast/di/introspection/graph-adapters/{dot,mermaid,cytoscape,reactflow}`.
|
|
417
494
|
|
|
418
495
|
## Benchmarks
|
|
419
496
|
|
|
@@ -424,9 +501,9 @@ alongside the method that produced them. Run it yourself rather than taking any
|
|
|
424
501
|
## Documentation
|
|
425
502
|
|
|
426
503
|
- [Rendered docs on codefastlabs.com](https://codefastlabs.com/docs/di)
|
|
427
|
-
- [`
|
|
504
|
+
- [`spec/`](./spec/README.md) — the behavioural contract: public API, semantics, and errors, one document per API area.
|
|
428
505
|
- [`ARCHITECTURE.md`](./ARCHITECTURE.md) — the internal shape and the invariants the hot paths depend on.
|
|
429
|
-
- [`
|
|
506
|
+
- [`DECISIONS.md`](./DECISIONS.md) — why the API looks like this, and what it did not take from InversifyJS v8.
|
|
430
507
|
- [`examples/`](./examples/README.md) — runnable examples from basic tokens to a multi-file Ports & Adapters app.
|
|
431
508
|
- [`CONTRIBUTING.md`](./CONTRIBUTING.md) — the package workflow: build, exports mirror, tests, and the perf guard.
|
|
432
509
|
- [`CHANGELOG.md`](./CHANGELOG.md) — release history.
|
|
@@ -16,11 +16,16 @@ export interface AmbientResolution {
|
|
|
16
16
|
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Runs a callback with the given container installed as the ambient one and returns its result.
|
|
19
|
+
* Runs a synchronous callback with the given container installed as the ambient one and returns its result.
|
|
20
|
+
*
|
|
21
|
+
* @remarks The context lives in a module-level variable restored in a `finally`, so it lasts only the
|
|
22
|
+
* synchronous run of `fn` — it does not survive an `await`. A callback returning a `Promise` is a
|
|
23
|
+
* mistake the return type flags: it resolves to `never`, and the ambient context would already be torn
|
|
24
|
+
* down by the time the promise settled.
|
|
20
25
|
*
|
|
21
26
|
* @since 0.3.16-canary.0
|
|
22
27
|
*/
|
|
23
|
-
export declare function runWithContainer<Result>(container: Container, fn: () => Result): Result;
|
|
28
|
+
export declare function runWithContainer<Result>(container: Container, fn: () => Result): Result extends Promise<unknown> ? never : Result;
|
|
24
29
|
/**
|
|
25
30
|
* Engine-internal variant of {@link runWithContainer} that also installs a path-continuing resolver.
|
|
26
31
|
*
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
let activeContainer;
|
|
2
2
|
let activeResolution;
|
|
3
3
|
/**
|
|
4
|
-
* Runs a callback with the given container installed as the ambient one and returns its result.
|
|
4
|
+
* Runs a synchronous callback with the given container installed as the ambient one and returns its result.
|
|
5
|
+
*
|
|
6
|
+
* @remarks The context lives in a module-level variable restored in a `finally`, so it lasts only the
|
|
7
|
+
* synchronous run of `fn` — it does not survive an `await`. A callback returning a `Promise` is a
|
|
8
|
+
* mistake the return type flags: it resolves to `never`, and the ambient context would already be torn
|
|
9
|
+
* down by the time the promise settled.
|
|
5
10
|
*
|
|
6
11
|
* @since 0.3.16-canary.0
|
|
7
12
|
*/
|