@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/README.md +6 -5
  3. package/dist/ambient/active-container.d.ts +14 -4
  4. package/dist/ambient/active-container.js +22 -1
  5. package/dist/container/binding-builders.d.ts +35 -13
  6. package/dist/container/binding-builders.js +174 -97
  7. package/dist/container/container.d.ts +10 -10
  8. package/dist/container/container.js +53 -36
  9. package/dist/core/binding-scope.d.ts +2 -2
  10. package/dist/core/binding.d.ts +42 -49
  11. package/dist/core/binding.js +12 -38
  12. package/dist/core/constraint-requirement.d.ts +1 -1
  13. package/dist/core/module.d.ts +3 -3
  14. package/dist/core/registry.d.ts +52 -9
  15. package/dist/core/registry.js +376 -162
  16. package/dist/core/state-epoch.d.ts +16 -0
  17. package/dist/core/state-epoch.js +21 -0
  18. package/dist/core/token.d.ts +1 -1
  19. package/dist/core/types.d.ts +12 -9
  20. package/dist/decorators/inject.d.ts +3 -3
  21. package/dist/decorators/inject.js +5 -5
  22. package/dist/decorators/injectable.d.ts +2 -2
  23. package/dist/decorators/injectable.js +2 -2
  24. package/dist/decorators/lifecycle-decorators.js +2 -2
  25. package/dist/errors/diagnostics.d.ts +2 -0
  26. package/dist/errors/errors.d.ts +29 -3
  27. package/dist/errors/errors.js +34 -2
  28. package/dist/index.d.ts +35 -35
  29. package/dist/index.js +19 -19
  30. package/dist/injection/descriptor.d.ts +9 -7
  31. package/dist/injection/descriptor.js +3 -1
  32. package/dist/injection/resolve-options.d.ts +9 -3
  33. package/dist/injection/resolve-options.js +17 -1
  34. package/dist/introspection/dependency-graph.d.ts +3 -3
  35. package/dist/introspection/dependency-graph.js +15 -10
  36. package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -1
  37. package/dist/introspection/graph-adapters/dot.d.ts +1 -1
  38. package/dist/introspection/graph-adapters/mermaid.d.ts +1 -1
  39. package/dist/introspection/graph-adapters/reactflow.d.ts +1 -1
  40. package/dist/introspection/inspector.d.ts +7 -5
  41. package/dist/introspection/inspector.js +13 -27
  42. package/dist/lifecycle/lifecycle-manager.d.ts +4 -4
  43. package/dist/lifecycle/lifecycle-manager.js +13 -11
  44. package/dist/lifecycle/scope-manager.d.ts +2 -2
  45. package/dist/lifecycle/scope-manager.js +4 -4
  46. package/dist/metadata/metadata-reader-token.d.ts +2 -2
  47. package/dist/metadata/metadata-reader-token.js +1 -1
  48. package/dist/metadata/metadata-types.d.ts +3 -3
  49. package/dist/metadata/symbol-metadata-reader.d.ts +3 -3
  50. package/dist/metadata/symbol-metadata-reader.js +1 -1
  51. package/dist/metadata/verifying-metadata-reader.d.ts +1 -1
  52. package/dist/metadata/verifying-metadata-reader.js +2 -2
  53. package/dist/resolution/cache/activation-need.d.ts +6 -4
  54. package/dist/resolution/cache/activation-need.js +13 -6
  55. package/dist/resolution/cache/binding-lookup-cache.d.ts +39 -6
  56. package/dist/resolution/cache/binding-lookup-cache.js +97 -13
  57. package/dist/resolution/cache/class-introspector.d.ts +6 -6
  58. package/dist/resolution/cache/class-introspector.js +82 -69
  59. package/dist/resolution/context.d.ts +11 -11
  60. package/dist/resolution/context.js +1 -1
  61. package/dist/resolution/path/resolution-path.d.ts +1 -1
  62. package/dist/resolution/path/resolution-path.js +1 -1
  63. package/dist/resolution/plan/instantiation-plan.d.ts +16 -4
  64. package/dist/resolution/plan/instantiation-plan.js +160 -69
  65. package/dist/resolution/plan/plan-codegen.d.ts +100 -0
  66. package/dist/resolution/plan/plan-codegen.js +185 -0
  67. package/dist/resolution/resolver.d.ts +26 -16
  68. package/dist/resolution/resolver.js +401 -147
  69. package/dist/resolution/select/binding-select.d.ts +6 -5
  70. package/dist/resolution/select/binding-select.js +17 -11
  71. package/dist/resolution/select/constraints.d.ts +3 -3
  72. package/dist/resolution/select/constraints.js +4 -4
  73. 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 24 or later and TypeScript 5.9 or later, with native Stage 3 decorators. Leave
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 [`SPEC.md` → Slots and last-wins](./SPEC.md#slot-matching).
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-inversify`](../../benchmarks/di-inversify). It
420
- runs the same workloads through `@codefast/di`, InversifyJS, Awilix, and tsyringe, and its `RESULTS.md` ledger records
421
- the numbers alongside the method that produced them. Run it yourself rather than taking any figure on faith.
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 "#/container/container";
3
- import type { Token } from "#/core/token";
4
- import type { Constructor, ResolveOptions } from "#/core/types";
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.3.16-canary.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.3.16-canary.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: it registers the binding and refines it in place.
2
+ * The fluent chain `bind()` returns, which is also the binding it registers.
3
3
  *
4
- * @remarks One object plays every role in the chain; the return type of each step is what pins the
5
- * order, so no runtime check has to.
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 "#/core/binding";
8
- import type { BindingRegistry } from "#/core/registry";
9
- import type { BindingTag } from "#/core/tag";
10
- import type { Token } from "#/core/token";
11
- import type { ActivationHandler, BindingConstraint, BindingIdentifier, Constructor, DeactivationHandler, ResolutionContext } from "#/core/types";
12
- import type { InjectableDependency, ResolvedDependencyValue } from "#/injection/descriptor";
13
- import type { ScopeManager } from "#/lifecycle/scope-manager";
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. Each interface exposes only the
29
- * calls that are legal at that point in the chain; the runtime object is shared because every
30
- * refinement is the same operation — narrow the registered binding, keep its id.
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>;