@codefast/di 0.9.0 → 0.10.1
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 +165 -0
- package/README.md +6 -5
- package/dist/ambient/active-container.d.ts +14 -4
- package/dist/ambient/active-container.js +22 -1
- package/dist/container/binding-builders.d.ts +35 -13
- package/dist/container/binding-builders.js +174 -97
- package/dist/container/container.d.ts +10 -10
- package/dist/container/container.js +53 -36
- package/dist/core/binding-scope.d.ts +2 -2
- package/dist/core/binding.d.ts +42 -49
- package/dist/core/binding.js +12 -38
- package/dist/core/constraint-requirement.d.ts +1 -1
- package/dist/core/module.d.ts +3 -3
- package/dist/core/registry.d.ts +52 -9
- package/dist/core/registry.js +376 -162
- package/dist/core/state-epoch.d.ts +16 -0
- package/dist/core/state-epoch.js +21 -0
- package/dist/core/token.d.ts +1 -1
- package/dist/core/types.d.ts +12 -9
- package/dist/decorators/inject.d.ts +3 -3
- package/dist/decorators/inject.js +5 -5
- package/dist/decorators/injectable.d.ts +2 -2
- package/dist/decorators/injectable.js +2 -2
- package/dist/decorators/lifecycle-decorators.js +2 -2
- package/dist/errors/diagnostics.d.ts +2 -0
- package/dist/errors/errors.d.ts +29 -3
- package/dist/errors/errors.js +34 -2
- package/dist/index.d.ts +35 -35
- package/dist/index.js +19 -19
- package/dist/injection/descriptor.d.ts +9 -7
- package/dist/injection/descriptor.js +3 -1
- package/dist/injection/resolve-options.d.ts +9 -3
- package/dist/injection/resolve-options.js +17 -1
- package/dist/introspection/dependency-graph.d.ts +3 -3
- package/dist/introspection/dependency-graph.js +15 -10
- package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -1
- package/dist/introspection/graph-adapters/dot.d.ts +1 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts +1 -1
- package/dist/introspection/graph-adapters/reactflow.d.ts +1 -1
- package/dist/introspection/inspector.d.ts +7 -5
- package/dist/introspection/inspector.js +13 -27
- package/dist/lifecycle/lifecycle-manager.d.ts +4 -4
- package/dist/lifecycle/lifecycle-manager.js +13 -11
- package/dist/lifecycle/scope-manager.d.ts +2 -2
- package/dist/lifecycle/scope-manager.js +4 -4
- package/dist/metadata/metadata-reader-token.d.ts +2 -2
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/metadata/metadata-types.d.ts +3 -3
- package/dist/metadata/symbol-metadata-reader.d.ts +3 -3
- package/dist/metadata/symbol-metadata-reader.js +1 -1
- package/dist/metadata/verifying-metadata-reader.d.ts +1 -1
- package/dist/metadata/verifying-metadata-reader.js +2 -2
- package/dist/resolution/cache/activation-need.d.ts +6 -4
- package/dist/resolution/cache/activation-need.js +13 -6
- package/dist/resolution/cache/binding-lookup-cache.d.ts +39 -6
- package/dist/resolution/cache/binding-lookup-cache.js +97 -13
- package/dist/resolution/cache/class-introspector.d.ts +6 -6
- package/dist/resolution/cache/class-introspector.js +82 -69
- package/dist/resolution/context.d.ts +11 -11
- package/dist/resolution/context.js +1 -1
- package/dist/resolution/path/resolution-path.d.ts +1 -1
- package/dist/resolution/path/resolution-path.js +1 -1
- package/dist/resolution/plan/instantiation-plan.d.ts +16 -4
- package/dist/resolution/plan/instantiation-plan.js +160 -69
- package/dist/resolution/plan/plan-codegen.d.ts +100 -0
- package/dist/resolution/plan/plan-codegen.js +185 -0
- package/dist/resolution/resolver.d.ts +26 -16
- package/dist/resolution/resolver.js +401 -147
- package/dist/resolution/select/binding-select.d.ts +6 -5
- package/dist/resolution/select/binding-select.js +17 -11
- package/dist/resolution/select/constraints.d.ts +3 -3
- package/dist/resolution/select/constraints.js +4 -4
- package/package.json +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,170 @@
|
|
|
1
1
|
# @codefast/di
|
|
2
2
|
|
|
3
|
+
## 0.10.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#892](https://github.com/codefastlabs/codefast/pull/892) [`4c97c5b`](https://github.com/codefastlabs/codefast/commit/4c97c5b8c2314f25ee9df7eac5a4b5dd723d6d90) Thanks [@thevuong](https://github.com/thevuong)! - Lower the monorepo's Node floor from 24 to 22.12, so the packages install and run on the active Node 22 LTS line.
|
|
8
|
+
|
|
9
|
+
`engines.node` becomes `>=22.12.0` across every package — the floor the shared toolchain (oxlint, Vite, Vitest, TanStack
|
|
10
|
+
Start) already requires. Development stays on the latest Node (`.node-version`) for speed, and a CI matrix exercises the
|
|
11
|
+
floor and the active LTS directly, so the floor is a contract CI proves rather than one everyone has to run.
|
|
12
|
+
`@types/node` is pinned to the floor's major (`^22`), with a workspace override holding the whole tree there so a dev
|
|
13
|
+
tool's `@types/node: "*"` peer can no longer pull a newer major and mask an API the floor lacks. The floor stays
|
|
14
|
+
mechanical, not advisory: `@codefast/di` keeps its own `Map` upsert helpers rather than the ES2025
|
|
15
|
+
`Map.prototype.getOrInsert` (which would raise the floor to 26) and its `lib` stays `ES2024`. `@codefast/cli`'s mirror
|
|
16
|
+
step now calls the `node:path` functions directly instead of aliasing them, which the floor's types correctly flag as
|
|
17
|
+
unbound methods.
|
|
18
|
+
|
|
19
|
+
The shared `@codefast/typescript-config` presets pin `lib` and `target` to `ES2024` (was `ESNext`) so the compiler's
|
|
20
|
+
ECMAScript surface matches the Node floor: an ES2025 builtin such as `Map.prototype.getOrInsert` now fails to type-check
|
|
21
|
+
rather than compiling and crashing on Node 22.12. `@codefast/di` and `@codefast/di-testing` already pinned `lib` and are
|
|
22
|
+
unchanged.
|
|
23
|
+
|
|
24
|
+
Internal subpath imports move from a `#/` prefix to a bare `#` (`#core/token`, not `#/core/token`), and the
|
|
25
|
+
`package.json#imports` keys become `#*`/`#tests/*`/`#examples/*` to match. Node's native ESM resolver rejects a
|
|
26
|
+
`#/`-prefixed specifier with `ERR_INVALID_MODULE_SPECIFIER` on the whole Node 22 line (and on Node 24 before 24.14), and
|
|
27
|
+
each package ships those specifiers verbatim inside its published `dist/*.js` for a consumer's Node to resolve — so this
|
|
28
|
+
rename is what actually lets the packages import on the new floor. Purely internal: a consumer's own import paths are
|
|
29
|
+
unchanged.
|
|
30
|
+
|
|
31
|
+
## 0.10.0
|
|
32
|
+
|
|
33
|
+
### Minor Changes
|
|
34
|
+
|
|
35
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`3621384`](https://github.com/codefastlabs/codefast/commit/36213842e7e64284dfb0c5b0d651368debf20a13) Thanks [@thevuong](https://github.com/thevuong)! - `BindingIdentifier` is a branded number, minted from a process-wide counter, instead of a branded string. The id stays
|
|
36
|
+
opaque — obtained from `.id()`, handed back to `unbind(id)`, reported by `inspect()` and the dependency graph — and a
|
|
37
|
+
plain bind no longer allocates a string for it. Code that treated the id as a string (interpolating, parsing or storing
|
|
38
|
+
it as text) must treat it as a number.
|
|
39
|
+
|
|
40
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`67c7eb4`](https://github.com/codefastlabs/codefast/commit/67c7eb4a63cee26c8b3d4954965a5fdacbfe3533) Thanks [@thevuong](https://github.com/thevuong)! - The fluent chain `bind()` returns is now the binding it registers: one object is every builder step, the registry's
|
|
41
|
+
record and what the resolver reads, so a plain bind is one allocation instead of three and a copy of every field. Two
|
|
42
|
+
names on the `Binding` shapes change to make room for the builder's methods — `id` is `identifier`, and the hook fields
|
|
43
|
+
are `activationHook` / `deactivationHook` — and a chain registers exactly once: a second `to*()` throws
|
|
44
|
+
`ChainAlreadyRegisteredError` instead of minting a second binding.
|
|
45
|
+
|
|
46
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`7f4efc2`](https://github.com/codefastlabs/codefast/commit/7f4efc2faf96ffdd4279241c9bc83aba53bc64af) Thanks [@thevuong](https://github.com/thevuong)! - `resolveAll`, `resolveAllAsync` and the value an `injectAll()` dependency delivers are `ReadonlyArray`s, and a
|
|
47
|
+
root-level, options-less `resolveAll` hands out the engine's own list — the same array on every call while no registry
|
|
48
|
+
in the chain has changed — instead of a copy per read. That list is kept while every member is a hook-free constant or a
|
|
49
|
+
hook-free singleton whose instance is cached, so a collection of singleton handlers is one lookup per read. Callers that
|
|
50
|
+
mutated the returned array spread it first; a constructor parameter typed `Array<T>` for an `injectAll` dependency
|
|
51
|
+
becomes `ReadonlyArray<T>`.
|
|
52
|
+
|
|
53
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`6980f8e`](https://github.com/codefastlabs/codefast/commit/6980f8ee5b804b17946ac959a9581f207e7ad008) Thanks [@thevuong](https://github.com/thevuong)! - `many()` marks a binding as a collection member: several members of one token coexist on the default slot, `resolveAll`
|
|
54
|
+
returns every member, a single `resolve` never selects one, and membership replaces slot last-wins. A member keeps the
|
|
55
|
+
default slot (`ManyBindingSlotError` otherwise) and may carry `when()` predicates. It is the intended form of a strategy
|
|
56
|
+
set, where a predicate that always passes used to stand in — and it is what lets a root-level collection of constants be
|
|
57
|
+
served from its memo without evaluating anything. `BindingSnapshot` gains `isMany`.
|
|
58
|
+
|
|
59
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`a56390f`](https://github.com/codefastlabs/codefast/commit/a56390f79bb62e6e33ecb79cbd158ad1349d7bee) Thanks [@thevuong](https://github.com/thevuong)! - A root-level `resolveAll` with no options memoizes its candidate list until any registry in the chain changes, and its
|
|
60
|
+
value list while every member is a hook-free constant with no activation hook anywhere in the chain. The contract
|
|
61
|
+
already required `when()` predicates to be pure; the engine now relies on it there, evaluating a predicate once per
|
|
62
|
+
container state for that read instead of on every call. Reads carrying options, or made from inside a factory, are
|
|
63
|
+
unchanged.
|
|
64
|
+
|
|
65
|
+
### Patch Changes
|
|
66
|
+
|
|
67
|
+
- [#863](https://github.com/codefastlabs/codefast/pull/863) [`ccc5ed1`](https://github.com/codefastlabs/codefast/commit/ccc5ed193f15d9edc7c3e6475a6bfda22e2c6319) Thanks [@thevuong](https://github.com/thevuong)! - Rewrite `benchmarks/di/RESULTS.md` as a single machine-derived snapshot on `@codefast/di` 0.9.0 (full profile,
|
|
68
|
+
GC-exposed, interleaved) instead of an accreted dated ledger, weighting wins and losses equally so the page shows where
|
|
69
|
+
the engine is slower. The snapshot records that `@codefast/di` loses the aggregate to ditox (0.79× median) and the
|
|
70
|
+
geomean to injection-js (0.56×), and breaks down each loss as a real deficit or a by-design work difference — the
|
|
71
|
+
`resolve-all-strategies` collapse (0.03× at N=100) is the rivals returning a memoized collection where `@codefast/di`
|
|
72
|
+
re-gathers per op.
|
|
73
|
+
|
|
74
|
+
Assert the A/B method in `BENCH_GUIDE.md`: because `run.ts` rebuilds `packages/di/dist` from `src` unconditionally
|
|
75
|
+
before spawning, swapping the source is the one method, and swapping `dist` directly is a demoted escape hatch that must
|
|
76
|
+
use the child entries. Repoint the `packages/di/ARCHITECTURE.md` tag-chain-walk-memo reference from `RESULTS.md` to the
|
|
77
|
+
package `CHANGELOG.md`, where that A/B now lives.
|
|
78
|
+
|
|
79
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`85adfd0`](https://github.com/codefastlabs/codefast/commit/85adfd004920f99ec8552745b7726e03e971cbb2) Thanks [@thevuong](https://github.com/thevuong)! - Construct an accessor-injected class without the two per-instantiation allocations it used to pay: the ambient
|
|
80
|
+
resolution handed to its accessors is built once per resolver for the lent root stack, and the construction itself runs
|
|
81
|
+
inside the ambient scope directly instead of through a wrapping closure.
|
|
82
|
+
|
|
83
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`d541dee`](https://github.com/codefastlabs/codefast/commit/d541dee97719a3151dd4daa9675846cb11aac82b) Thanks [@thevuong](https://github.com/thevuong)! - Compile an accessor-injected class as a plan root instead of declining the plan: its constructor parameters compile as
|
|
84
|
+
usual and construction runs through the host, which puts the class's frame on the resolution path and the container
|
|
85
|
+
ambient before constructing, so an accessor that cycles back is still reported as a `CircularDependencyError`. Below a
|
|
86
|
+
plan's root such a class stays an escape.
|
|
87
|
+
|
|
88
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`793f5d0`](https://github.com/codefastlabs/codefast/commit/793f5d0a13092144b2ca83ec23142e68011cd5ca) Thanks [@thevuong](https://github.com/thevuong)! - Route every member of a `resolveAllAsync` collection that is a transient factory with no activation and no request
|
|
89
|
+
options through the same non-`async` lane a single `resolveAsync` already takes, so a fan-out costs one factory promise
|
|
90
|
+
per member instead of an async state machine on top of each.
|
|
91
|
+
|
|
92
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`7a00e30`](https://github.com/codefastlabs/codefast/commit/7a00e306de98a829385e3238095333f094a843f7) Thanks [@thevuong](https://github.com/thevuong)! - Build collections in linear time. A token's binding list now appends in place and is replaced only on removal or
|
|
93
|
+
displacement, with every selection walk reading its starting length first, so a predicate that binds mid-walk is still
|
|
94
|
+
invisible to that walk; and a bare `when()` rewrites the binding's predicate in place — moving a lone binding into a
|
|
95
|
+
record — instead of re-registering it, whenever the chain owns the registry's last write and has nothing parked. A
|
|
96
|
+
hundred `when()` bindings on one token no longer copy the list a hundred times.
|
|
97
|
+
|
|
98
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`3613886`](https://github.com/codefastlabs/codefast/commit/36138863480e0fa48ca522ad56fa1ee98a528307) Thanks [@thevuong](https://github.com/thevuong)! - Defer every per-container allocation a resolve can happen without. The resolver builds its plan compiler and plan maps
|
|
99
|
+
on the first plan request, the lookup cache allocates its memo maps only once a second distinct token or tag appears in
|
|
100
|
+
one cache generation, the activation-need memo is allocated by the first answer its early returns cannot give, and the
|
|
101
|
+
registry allocates its record map on the first bind. A per-request child that is created, asked one parent-owned token
|
|
102
|
+
and disposed now allocates none of them; the registry's fast-default map stays eager so the first read of every
|
|
103
|
+
synchronous resolve is still a bare `Map.get`.
|
|
104
|
+
|
|
105
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`b823fc8`](https://github.com/codefastlabs/codefast/commit/b823fc89c09fb200d0d9262f16f34f32cd27a63e) Thanks [@thevuong](https://github.com/thevuong)! - Price the two lanes the full pass showed paying for the rewrite: a request for an unbound or record-less token reads the
|
|
106
|
+
record map alone once its lone-map probe has missed, instead of probing the lone map again, and a root container's
|
|
107
|
+
chain-summed versions are read as its own version instead of through the epoch memo, so a `resolveAll` over a root no
|
|
108
|
+
longer pays a compare and a stamp per candidate.
|
|
109
|
+
|
|
110
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`5fbc3de`](https://github.com/codefastlabs/codefast/commit/5fbc3dea835987f8eb857529a5a6b1293ac523b6) Thanks [@thevuong](https://github.com/thevuong)! - A request carrying a name and one tag is answered from a memoized lookup of the exact two-criterion slot, in either
|
|
111
|
+
declaration order, instead of a scan of the token's bindings; a predicate on that binding still runs, and a name no
|
|
112
|
+
binding has declared is a miss at once.
|
|
113
|
+
|
|
114
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`b46c0cd`](https://github.com/codefastlabs/codefast/commit/b46c0cd9473a662f294231796c4be2a206c83033) Thanks [@thevuong](https://github.com/thevuong)! - `resolveOptional` with no options answers a lone default binding in its own registry on the lane a plain `resolve`
|
|
115
|
+
takes, and a root that keeps no records answers a miss without the selection walk; a generated `toResolved` plan checks
|
|
116
|
+
its factory's result for a promise inline instead of through a call.
|
|
117
|
+
|
|
118
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`0506f04`](https://github.com/codefastlabs/codefast/commit/0506f046e9f797086708c186f14f96f13026a8bd) Thanks [@thevuong](https://github.com/thevuong)! - A compiled instantiation plan that keeps running — sync or async — is generated as a function of its own through the
|
|
119
|
+
`Function` constructor and takes the closure's place, so its call sites carry feedback for one plan only instead of for
|
|
120
|
+
every plan the process has compiled; below the threshold a plan stays a closure, so a cold container or a per-request
|
|
121
|
+
child never compiles one. A runtime whose Content Security Policy refuses the constructor keeps every plan a closure and
|
|
122
|
+
behaves identically. `RESOLUTION_DIAGNOSTICS` reports `generatedPlanCount`.
|
|
123
|
+
|
|
124
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`e3e9257`](https://github.com/codefastlabs/codefast/commit/e3e92571dc64f8eb48006b3310d4334ea61cc149) Thanks [@thevuong](https://github.com/thevuong)! - Keep the common token — one default-slot binding, no predicate — in the registry's fast-default map alone, and give a
|
|
125
|
+
record (binding list plus tagged indexes) only to a token that carries a second binding, a tagged slot or a predicate,
|
|
126
|
+
moving it back when the record shrinks to the default slot. A plain bind is now one map write and one binding object;
|
|
127
|
+
`getFastDefault()` is unchanged, a bare `Map.get` on that map, and `has()`/`hasOwn()` without criteria answer from a
|
|
128
|
+
registry presence probe instead of materialising the token's list. `RESOLUTION_DIAGNOSTICS` reports the record map under
|
|
129
|
+
`registry.records`.
|
|
130
|
+
|
|
131
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`414d495`](https://github.com/codefastlabs/codefast/commit/414d495c6db58106e64ba5f74880e12bd85c2459) Thanks [@thevuong](https://github.com/thevuong)! - The binding registry keeps one record per token — its list and its two tagged indexes — in a single map, and builds the
|
|
132
|
+
binding-id index only on the first id-keyed operation. A bind into a fresh token is one record and one map write where
|
|
133
|
+
it was three map writes; `getFastDefault()` keeps its own map so the warm resolve lane still answers from one bare
|
|
134
|
+
`Map.get`. Measured paired and alternating against the previous layout: the bind path runs at roughly 1.6× on the
|
|
135
|
+
128-binding registration row and the warm resolve rows hold at parity.
|
|
136
|
+
|
|
137
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`3db3447`](https://github.com/codefastlabs/codefast/commit/3db3447bfe87b4aaf5f48113cc91b19537eefeb0) Thanks [@thevuong](https://github.com/thevuong)! - Register a binding into a token's record in constant time. Last-wins displacement asked the record's list who occupied
|
|
138
|
+
the incoming binding's slot, walking every binding already on the token; the default slot now has its own index entry
|
|
139
|
+
beside the two tagged ones, so an add finds and displaces exactly its occupant without the walk. A token that grows a
|
|
140
|
+
large collection member by member — the fan-out cold path — was quadratic to build and is now linear: per-bind cost
|
|
141
|
+
stays flat instead of climbing with the collection size, and the `resolve-all-cold` rows move from a loss to a win
|
|
142
|
+
against the decorator-free rivals. The slot index is dropped in the same step a binding leaves its slot — `many()`
|
|
143
|
+
turning it into a member, a predicate making it predicate-only — so a later add never displaces a binding that has
|
|
144
|
+
already moved on.
|
|
145
|
+
|
|
146
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`8bb19e3`](https://github.com/codefastlabs/codefast/commit/8bb19e3ab11c4176f307765321e431cfa94d37bb) Thanks [@thevuong](https://github.com/thevuong)! - Answer a request the slot indexes decline with an allocation-free first pass over the token's candidates: one slot match
|
|
147
|
+
with no predicate is returned outright and no match is a clean miss, without building a constraint context, a display
|
|
148
|
+
name or a candidate array. A second match, or a predicate on a match, still goes through full selection, so specificity
|
|
149
|
+
and ambiguity are decided exactly as before.
|
|
150
|
+
|
|
151
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`5e57752`](https://github.com/codefastlabs/codefast/commit/5e57752b2fcad8a1c44e5bff61d5f91f7cdb27da) Thanks [@thevuong](https://github.com/thevuong)! - A container's class-metadata caches are keyed by the metadata reader and shared by every container that reads through
|
|
152
|
+
it, so a child inheriting its parent's reader resolves a class the parent already met without reading its metadata
|
|
153
|
+
again; the activation-need cache is built by the first interpreted resolve that asks for it instead of with the
|
|
154
|
+
container.
|
|
155
|
+
|
|
156
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`bee69af`](https://github.com/codefastlabs/codefast/commit/bee69af3688a67234ecd113159ca7e767e14699a) Thanks [@thevuong](https://github.com/thevuong)! - Memoize the lookup cache's chain-summed registry version against a process-wide state epoch that every registry mutation
|
|
157
|
+
and activation-hook registration advances, and let a root container read both chain sums as its own version. A resolve
|
|
158
|
+
from a deep child no longer walks its whole ancestor chain on every lookup while nothing has changed; the first read
|
|
159
|
+
after any change re-sums exactly as before. The activation sum stays a walk: the memo that would serve it costs fields
|
|
160
|
+
on the resolver that the warm transient class lane pays for.
|
|
161
|
+
|
|
162
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`f78726e`](https://github.com/codefastlabs/codefast/commit/f78726e00b9c388ceb03f7fad116593d7b752a94) Thanks [@thevuong](https://github.com/thevuong)! - A request carrying one criterion that the token's tagged index does not hold is answered as a miss for that container
|
|
163
|
+
without scanning its bindings, and walks to the parent directly.
|
|
164
|
+
|
|
165
|
+
- [#866](https://github.com/codefastlabs/codefast/pull/866) [`5536e62`](https://github.com/codefastlabs/codefast/commit/5536e6208ab58955fb551866c3b9703e7f2c55e0) Thanks [@thevuong](https://github.com/thevuong)! - Allocate the deactivation-pair list only when an unbind, rebind or teardown actually owes a deactivation; the hot-swap
|
|
166
|
+
shape, a binding nothing ever cached, hands back a shared empty list instead.
|
|
167
|
+
|
|
3
168
|
## 0.9.0
|
|
4
169
|
|
|
5
170
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ its keep as the dependency graph grows.
|
|
|
35
35
|
pnpm add @codefast/di
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
`@codefast/di` requires Node.js
|
|
38
|
+
`@codefast/di` requires Node.js 22.12 or later and TypeScript 5.9 or later, with native Stage 3 decorators. Leave
|
|
39
39
|
`experimentalDecorators` off — it's off by default. The package is published on 0.x and versioned on its own track:
|
|
40
40
|
breaking changes ship as minor versions, so pin the minor version when you need stability.
|
|
41
41
|
|
|
@@ -224,7 +224,8 @@ predicates ship from the root entry:
|
|
|
224
224
|
| `whenParentTaggedAll(criteria)` | the parent's slot carries all criteria in the array |
|
|
225
225
|
| `whenAnyAncestorTaggedAll(criteria)` | some ancestor's slot carries all criteria in the array |
|
|
226
226
|
|
|
227
|
-
For the exact matching and most-specific-wins rules, see
|
|
227
|
+
For the exact matching and most-specific-wins rules, see
|
|
228
|
+
[`SPEC.md` → Slots and last-wins](./SPEC.md#slots-and-last-wins--the-exact-definition).
|
|
228
229
|
|
|
229
230
|
## Decorators
|
|
230
231
|
|
|
@@ -416,9 +417,9 @@ metadata under `decorators/*` and `metadata/*`. Introspection ships at flat spec
|
|
|
416
417
|
|
|
417
418
|
## Benchmarks
|
|
418
419
|
|
|
419
|
-
A first-party benchmark suite lives in the monorepo, at [`benchmarks/di
|
|
420
|
-
|
|
421
|
-
|
|
420
|
+
A first-party benchmark suite lives in the monorepo, at [`benchmarks/di`](../../benchmarks/di). It runs the same
|
|
421
|
+
workloads through `@codefast/di`, InversifyJS, Awilix, and tsyringe, and its `RESULTS.md` ledger records the numbers
|
|
422
|
+
alongside the method that produced them. Run it yourself rather than taking any figure on faith.
|
|
422
423
|
|
|
423
424
|
## Documentation
|
|
424
425
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** The container an `@inject` accessor initializer resolves from when it has no other handle. */
|
|
2
|
-
import type { Container } from "
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
2
|
+
import type { Container } from "#container/container";
|
|
3
|
+
import type { ConstructorInvocation } from "#core/constructor-type";
|
|
4
|
+
import type { Token } from "#core/token";
|
|
5
|
+
import type { Constructor, ResolveOptions } from "#core/types";
|
|
5
6
|
/**
|
|
6
7
|
* Path-continuing resolution the engine installs while constructing a class with `@inject` accessors.
|
|
7
8
|
*
|
|
@@ -26,10 +27,19 @@ export declare function runWithContainer<Result>(container: Container, fn: () =>
|
|
|
26
27
|
* @since 0.6.0
|
|
27
28
|
*/
|
|
28
29
|
export declare function runWithAmbientResolution<Result>(container: Container, resolution: AmbientResolution | undefined, fn: () => Result): Result;
|
|
30
|
+
/**
|
|
31
|
+
* Constructs `target` with `container` and `resolution` ambient for the duration of its constructor.
|
|
32
|
+
*
|
|
33
|
+
* @remarks The construction is written out rather than wrapped in a closure: an accessor-injected
|
|
34
|
+
* class pays this on every instantiation, and the closure was one of two allocations it paid for.
|
|
35
|
+
*
|
|
36
|
+
* @since 0.10.0
|
|
37
|
+
*/
|
|
38
|
+
export declare function constructWithAmbientResolution(container: Container, resolution: AmbientResolution, target: ConstructorInvocation, deps: ReadonlyArray<unknown>): unknown;
|
|
29
39
|
/**
|
|
30
40
|
* Returns the ambient container for the construction in flight, if one is installed.
|
|
31
41
|
*
|
|
32
|
-
* @since 0.
|
|
42
|
+
* @since 0.10.0
|
|
33
43
|
*/
|
|
34
44
|
export declare function getActiveContainer(): Container | undefined;
|
|
35
45
|
/**
|
|
@@ -26,10 +26,31 @@ export function runWithAmbientResolution(container, resolution, fn) {
|
|
|
26
26
|
activeResolution = previousResolution;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Constructs `target` with `container` and `resolution` ambient for the duration of its constructor.
|
|
31
|
+
*
|
|
32
|
+
* @remarks The construction is written out rather than wrapped in a closure: an accessor-injected
|
|
33
|
+
* class pays this on every instantiation, and the closure was one of two allocations it paid for.
|
|
34
|
+
*
|
|
35
|
+
* @since 0.10.0
|
|
36
|
+
*/
|
|
37
|
+
export function constructWithAmbientResolution(container, resolution, target, deps) {
|
|
38
|
+
const previousContainer = activeContainer;
|
|
39
|
+
const previousResolution = activeResolution;
|
|
40
|
+
activeContainer = container;
|
|
41
|
+
activeResolution = resolution;
|
|
42
|
+
try {
|
|
43
|
+
return new target(...deps);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
activeContainer = previousContainer;
|
|
47
|
+
activeResolution = previousResolution;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
29
50
|
/**
|
|
30
51
|
* Returns the ambient container for the construction in flight, if one is installed.
|
|
31
52
|
*
|
|
32
|
-
* @since 0.
|
|
53
|
+
* @since 0.10.0
|
|
33
54
|
*/
|
|
34
55
|
export function getActiveContainer() {
|
|
35
56
|
return activeContainer;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The fluent chain `bind()` returns
|
|
2
|
+
* The fluent chain `bind()` returns, which is also the binding it registers.
|
|
3
3
|
*
|
|
4
|
-
* @remarks One object plays every role
|
|
5
|
-
*
|
|
4
|
+
* @remarks One object plays every role: each builder step, the record the registry stores, and the
|
|
5
|
+
* binding the resolver reads. The binding fields are declared first and in a fixed order, so every
|
|
6
|
+
* binding in the process shares one hidden class; the return type of each step is what pins the
|
|
7
|
+
* chain's order, so no runtime check has to.
|
|
6
8
|
*/
|
|
7
|
-
import type { AliasBindingBuilder, BindingBuilder, BindToBuilder, ConstantBindingBuilder, ScopedBindingBuilder, SingletonBindingBuilder, SingletonLifecycleBuilder, TransientBindingBuilder } from "
|
|
8
|
-
import type { BindingRegistry } from "
|
|
9
|
-
import type { BindingTag } from "
|
|
10
|
-
import type { Token } from "
|
|
11
|
-
import type { ActivationHandler, BindingConstraint, BindingIdentifier, Constructor, DeactivationHandler, ResolutionContext } from "
|
|
12
|
-
import type { InjectableDependency, ResolvedDependencyValue } from "
|
|
13
|
-
import type { ScopeManager } from "
|
|
9
|
+
import type { AliasBindingBuilder, BindingBuilder, BindingSlot, BindToBuilder, ConstantBindingBuilder, ScopedBindingBuilder, SingletonBindingBuilder, SingletonLifecycleBuilder, TransientBindingBuilder } from "#core/binding";
|
|
10
|
+
import type { BindingRegistry } from "#core/registry";
|
|
11
|
+
import type { BindingTag } from "#core/tag";
|
|
12
|
+
import type { Token } from "#core/token";
|
|
13
|
+
import type { ActivationHandler, BindingConstraint, BindingIdentifier, BindingKind, BindingScope, Constructor, DeactivationHandler, ResolutionContext, ResolutionFrame } from "#core/types";
|
|
14
|
+
import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "#injection/descriptor";
|
|
15
|
+
import type { ScopeManager } from "#lifecycle/scope-manager";
|
|
14
16
|
/**
|
|
15
17
|
* Where a chain registers, and on whose behalf.
|
|
16
18
|
*
|
|
@@ -25,14 +27,33 @@ export interface BindingRegistration {
|
|
|
25
27
|
readonly moduleBindingIds: Array<BindingIdentifier> | undefined;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
|
-
* The one builder behind `bind()` and every `to*()` return type
|
|
29
|
-
*
|
|
30
|
-
*
|
|
30
|
+
* The one builder behind `bind()` and every `to*()` return type — and the binding it registers.
|
|
31
|
+
*
|
|
32
|
+
* @remarks The binding fields come first, in the order every binding shares, and the chain's own
|
|
33
|
+
* bookkeeping follows as private fields; `to*()` fills the fields in place and hands this object to
|
|
34
|
+
* the registry, so a plain bind is one allocation. Refinements write the registered object: a scope
|
|
35
|
+
* or hook in place, a slot or predicate through the registry so its indexes follow.
|
|
31
36
|
*
|
|
32
37
|
* @since 0.5.0-canary.8
|
|
33
38
|
*/
|
|
34
39
|
export declare class BindingChain<Value, Names extends string = string> implements AliasBindingBuilder<Names>, BindingBuilder<Value, Names>, BindToBuilder<Value, Names>, ConstantBindingBuilder<Value, Names>, ScopedBindingBuilder<Value>, SingletonBindingBuilder<Value>, SingletonLifecycleBuilder<Value>, TransientBindingBuilder<Value> {
|
|
35
40
|
#private;
|
|
41
|
+
kind: BindingKind;
|
|
42
|
+
readonly identifier: BindingIdentifier;
|
|
43
|
+
inFlight: boolean;
|
|
44
|
+
frame: ResolutionFrame | undefined;
|
|
45
|
+
instance: unknown;
|
|
46
|
+
readonly token: Token<Value, Names> | Constructor<Value>;
|
|
47
|
+
slot: BindingSlot;
|
|
48
|
+
predicate: BindingConstraint | undefined;
|
|
49
|
+
isMany: boolean;
|
|
50
|
+
scope: BindingScope;
|
|
51
|
+
target: unknown;
|
|
52
|
+
factory: unknown;
|
|
53
|
+
deps: ReadonlyArray<InjectionDescriptor> | undefined;
|
|
54
|
+
value: unknown;
|
|
55
|
+
activationHook: ActivationHandler<Value> | undefined;
|
|
56
|
+
deactivationHook: DeactivationHandler<Value> | undefined;
|
|
36
57
|
constructor(token: Token<Value, Names> | Constructor<Value>, registration: BindingRegistration);
|
|
37
58
|
to(type: Constructor<Value>): BindingBuilder<Value, Names>;
|
|
38
59
|
toSelf(): BindingBuilder<Value, Names>;
|
|
@@ -49,6 +70,7 @@ export declare class BindingChain<Value, Names extends string = string> implemen
|
|
|
49
70
|
when(predicate: BindingConstraint): this;
|
|
50
71
|
whenNamed(name: Names): this;
|
|
51
72
|
whenTagged(criterion: BindingTag): this;
|
|
73
|
+
many(): this;
|
|
52
74
|
whenDefault(): this;
|
|
53
75
|
singleton(): SingletonBindingBuilder<Value>;
|
|
54
76
|
transient(): TransientBindingBuilder<Value>;
|