@codefast/di 0.10.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 (68) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +3 -2
  3. package/dist/ambient/active-container.d.ts +6 -9
  4. package/dist/ambient/active-container.js +2 -5
  5. package/dist/container/binding-builders.d.ts +7 -7
  6. package/dist/container/binding-builders.js +36 -27
  7. package/dist/container/container.d.ts +8 -8
  8. package/dist/container/container.js +20 -20
  9. package/dist/core/binding-scope.d.ts +2 -2
  10. package/dist/core/binding.d.ts +6 -6
  11. package/dist/core/binding.js +1 -1
  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 +6 -6
  15. package/dist/core/registry.js +36 -24
  16. package/dist/core/token.d.ts +1 -1
  17. package/dist/core/types.d.ts +5 -5
  18. package/dist/decorators/inject.d.ts +3 -3
  19. package/dist/decorators/inject.js +5 -5
  20. package/dist/decorators/injectable.d.ts +2 -2
  21. package/dist/decorators/injectable.js +2 -2
  22. package/dist/decorators/lifecycle-decorators.js +2 -2
  23. package/dist/errors/errors.d.ts +8 -11
  24. package/dist/errors/errors.js +7 -10
  25. package/dist/index.d.ts +35 -35
  26. package/dist/index.js +19 -19
  27. package/dist/injection/descriptor.d.ts +3 -3
  28. package/dist/injection/resolve-options.d.ts +3 -3
  29. package/dist/injection/resolve-options.js +1 -1
  30. package/dist/introspection/dependency-graph.d.ts +3 -3
  31. package/dist/introspection/dependency-graph.js +5 -5
  32. package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -1
  33. package/dist/introspection/graph-adapters/dot.d.ts +1 -1
  34. package/dist/introspection/graph-adapters/mermaid.d.ts +1 -1
  35. package/dist/introspection/graph-adapters/reactflow.d.ts +1 -1
  36. package/dist/introspection/inspector.d.ts +4 -4
  37. package/dist/introspection/inspector.js +3 -3
  38. package/dist/lifecycle/lifecycle-manager.d.ts +4 -4
  39. package/dist/lifecycle/lifecycle-manager.js +4 -4
  40. package/dist/lifecycle/scope-manager.d.ts +2 -2
  41. package/dist/lifecycle/scope-manager.js +3 -3
  42. package/dist/metadata/metadata-reader-token.d.ts +2 -2
  43. package/dist/metadata/metadata-reader-token.js +1 -1
  44. package/dist/metadata/metadata-types.d.ts +3 -3
  45. package/dist/metadata/symbol-metadata-reader.d.ts +3 -3
  46. package/dist/metadata/symbol-metadata-reader.js +1 -1
  47. package/dist/metadata/verifying-metadata-reader.d.ts +1 -1
  48. package/dist/metadata/verifying-metadata-reader.js +2 -2
  49. package/dist/resolution/cache/activation-need.d.ts +4 -4
  50. package/dist/resolution/cache/binding-lookup-cache.d.ts +5 -5
  51. package/dist/resolution/cache/binding-lookup-cache.js +2 -2
  52. package/dist/resolution/cache/class-introspector.d.ts +5 -5
  53. package/dist/resolution/cache/class-introspector.js +55 -56
  54. package/dist/resolution/context.d.ts +3 -3
  55. package/dist/resolution/context.js +1 -1
  56. package/dist/resolution/path/resolution-path.d.ts +1 -1
  57. package/dist/resolution/path/resolution-path.js +1 -1
  58. package/dist/resolution/plan/instantiation-plan.d.ts +5 -5
  59. package/dist/resolution/plan/instantiation-plan.js +5 -5
  60. package/dist/resolution/plan/plan-codegen.d.ts +3 -3
  61. package/dist/resolution/plan/plan-codegen.js +2 -2
  62. package/dist/resolution/resolver.d.ts +12 -12
  63. package/dist/resolution/resolver.js +33 -20
  64. package/dist/resolution/select/binding-select.d.ts +6 -5
  65. package/dist/resolution/select/binding-select.js +5 -4
  66. package/dist/resolution/select/constraints.d.ts +3 -3
  67. package/dist/resolution/select/constraints.js +4 -4
  68. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
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
+
3
31
  ## 0.10.0
4
32
 
5
33
  ### 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
 
@@ -1,8 +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 { ConstructorInvocation } from "#/core/constructor-type";
4
- import type { Token } from "#/core/token";
5
- 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";
6
6
  /**
7
7
  * Path-continuing resolution the engine installs while constructing a class with `@inject` accessors.
8
8
  *
@@ -27,11 +27,6 @@ export declare function runWithContainer<Result>(container: Container, fn: () =>
27
27
  * @since 0.6.0
28
28
  */
29
29
  export declare function runWithAmbientResolution<Result>(container: Container, resolution: AmbientResolution | undefined, fn: () => Result): Result;
30
- /**
31
- * Returns the ambient container for the construction in flight, if one is installed.
32
- *
33
- * @since 0.3.16-canary.0
34
- */
35
30
  /**
36
31
  * Constructs `target` with `container` and `resolution` ambient for the duration of its constructor.
37
32
  *
@@ -42,6 +37,8 @@ export declare function runWithAmbientResolution<Result>(container: Container, r
42
37
  */
43
38
  export declare function constructWithAmbientResolution(container: Container, resolution: AmbientResolution, target: ConstructorInvocation, deps: ReadonlyArray<unknown>): unknown;
44
39
  /**
40
+ * Returns the ambient container for the construction in flight, if one is installed.
41
+ *
45
42
  * @since 0.10.0
46
43
  */
47
44
  export declare function getActiveContainer(): Container | undefined;
@@ -26,11 +26,6 @@ export function runWithAmbientResolution(container, resolution, fn) {
26
26
  activeResolution = previousResolution;
27
27
  }
28
28
  }
29
- /**
30
- * Returns the ambient container for the construction in flight, if one is installed.
31
- *
32
- * @since 0.3.16-canary.0
33
- */
34
29
  /**
35
30
  * Constructs `target` with `container` and `resolution` ambient for the duration of its constructor.
36
31
  *
@@ -53,6 +48,8 @@ export function constructWithAmbientResolution(container, resolution, target, de
53
48
  }
54
49
  }
55
50
  /**
51
+ * Returns the ambient container for the construction in flight, if one is installed.
52
+ *
56
53
  * @since 0.10.0
57
54
  */
58
55
  export function getActiveContainer() {
@@ -6,13 +6,13 @@
6
6
  * binding in the process shares one hidden class; the return type of each step is what pins the
7
7
  * chain's order, so no runtime check has to.
8
8
  */
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";
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";
16
16
  /**
17
17
  * Where a chain registers, and on whose behalf.
18
18
  *
@@ -1,9 +1,9 @@
1
- import { clearBindingFrame, createBindingSlot, DEFAULT_BINDING_SLOT, generateBindingId, NO_INSTANCE, } from "#/core/binding";
2
- import { mergingConstraintRequirements } from "#/core/constraint-requirement";
3
- import { slotName } from "#/core/tag";
4
- import { tokenName } from "#/core/token";
5
- import { ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, SelfBindingRequiresClassError, } from "#/errors/errors";
6
- import { normalizeToDescriptor } from "#/injection/descriptor";
1
+ import { clearBindingFrame, createBindingSlot, DEFAULT_BINDING_SLOT, generateBindingId, NO_INSTANCE, } from "#core/binding";
2
+ import { mergingConstraintRequirements } from "#core/constraint-requirement";
3
+ import { slotName } from "#core/tag";
4
+ import { tokenName } from "#core/token";
5
+ import { ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, SelfBindingRequiresClassError, } from "#errors/errors";
6
+ import { normalizeToDescriptor } from "#injection/descriptor";
7
7
  /** One criterion per key: re-tagging the same key replaces it rather than asking for both values. */
8
8
  function updateSlotTag(slot, criterion) {
9
9
  const tags = [...slot.tags];
@@ -126,6 +126,24 @@ export class BindingChain {
126
126
  return this.#register("alias", "transient");
127
127
  }
128
128
  // ── Refinement ─────────────────────────────────────────────────────────────────────────────────────────────────────
129
+ /**
130
+ * Whether this chain provably still owns the registry's last write with nothing parked.
131
+ *
132
+ * @remarks When it does, a refinement rewrites the live binding in place; otherwise `#reslot`
133
+ * re-checks liveness and restores what the new shape frees.
134
+ */
135
+ get #isProvablyLive() {
136
+ return this.#registration.registry.version === this.#versionAfterLastWrite && this.#displacedByChain === undefined;
137
+ }
138
+ /** Stamps this chain's last-write version after a mutation it made in place. */
139
+ #recordWrite() {
140
+ this.#versionAfterLastWrite = this.#registration.registry.version;
141
+ }
142
+ /** Bumps the registry for an index-neutral mutation (scope, hook) and records this chain's write. */
143
+ #touchAndRecordWrite() {
144
+ this.#registration.registry.touch();
145
+ this.#recordWrite();
146
+ }
129
147
  // Slot and predicate are what the registry indexes on, so a re-slot takes the binding out of the
130
148
  // registry, rewrites the two fields while it is out, and registers it again — same object, same id.
131
149
  #reslot(slot, predicate) {
@@ -149,8 +167,7 @@ export class BindingChain {
149
167
  }
150
168
  // The frame reports the scope, so a resolve before this call memoized the previous one.
151
169
  clearBindingFrame(this.#binding);
152
- this.#registration.registry.touch();
153
- this.#versionAfterLastWrite = this.#registration.registry.version;
170
+ this.#touchAndRecordWrite();
154
171
  return this;
155
172
  }
156
173
  // SPEC calls a candidate a binding that passes *all* of a chain's predicates, and the chain type
@@ -162,13 +179,10 @@ export class BindingChain {
162
179
  const narrowed = previous === undefined
163
180
  ? predicate
164
181
  : mergingConstraintRequirements((ctx) => previous(ctx) && predicate(ctx), previous, predicate);
165
- const { registry } = this.#registration;
166
- // The slot is unchanged, so nothing has to be re-indexed or displaced. With the last registry
167
- // write this chain's own and nothing parked, the binding is provably live and is rewritten in
168
- // place; otherwise the re-slot path re-checks liveness and restores what the shape frees.
169
- if (registry.version === this.#versionAfterLastWrite && this.#displacedByChain === undefined) {
170
- registry.setPredicate(this.#binding, narrowed);
171
- this.#versionAfterLastWrite = registry.version;
182
+ // The slot is unchanged, so a provably-live binding just takes the predicate in place.
183
+ if (this.#isProvablyLive) {
184
+ this.#registration.registry.setPredicate(this.#binding, narrowed);
185
+ this.#recordWrite();
172
186
  return this;
173
187
  }
174
188
  return this.#reslot(this.slot, narrowed);
@@ -191,14 +205,11 @@ export class BindingChain {
191
205
  if (this.isMany) {
192
206
  return this;
193
207
  }
194
- const { registry } = this.#registration;
195
- // With the last registry write this chain's own and nothing parked, the binding is provably live
196
- // and displaced nobody, so membership is written in place and the registry only moves it out of
197
- // the lone map. Otherwise it goes through the re-slot path, which re-checks liveness and restores
198
- // an ordinary binding this chain's `to*()` displaced, now that the member frees its slot.
199
- if (registry.version === this.#versionAfterLastWrite && this.#displacedByChain === undefined) {
200
- registry.setMany(this.#binding);
201
- this.#versionAfterLastWrite = registry.version;
208
+ // A provably-live binding takes membership in place; the registry only moves it out of the lone
209
+ // map. Otherwise `#commit` re-checks liveness and restores what the freed slot lets back in.
210
+ if (this.#isProvablyLive) {
211
+ this.#registration.registry.setMany(this.#binding);
212
+ this.#recordWrite();
202
213
  return this;
203
214
  }
204
215
  this.#commit(() => {
@@ -224,15 +235,13 @@ export class BindingChain {
224
235
  onActivation(fn) {
225
236
  this.#requireRegistered();
226
237
  this.activationHook = fn;
227
- this.#registration.registry.touch();
228
- this.#versionAfterLastWrite = this.#registration.registry.version;
238
+ this.#touchAndRecordWrite();
229
239
  return this;
230
240
  }
231
241
  onDeactivation(fn) {
232
242
  this.#requireRegistered();
233
243
  this.deactivationHook = fn;
234
- this.#registration.registry.touch();
235
- this.#versionAfterLastWrite = this.#registration.registry.version;
244
+ this.#touchAndRecordWrite();
236
245
  return this;
237
246
  }
238
247
  id() {
@@ -1,11 +1,11 @@
1
- import type { BindToBuilder } from "#/core/binding";
2
- import type { AsyncModule, SyncModule } from "#/core/module";
3
- import type { Token } from "#/core/token";
4
- import type { ActivationHandler, BindingIdentifier, Constructor, DeactivationHandler, ResolveOptions } from "#/core/types";
5
- import type { AutoRegisterRegistry } from "#/decorators/injectable";
6
- import type { ContainerGraphJson, GraphOptions } from "#/introspection/dependency-graph";
7
- import type { BindingSnapshot, ContainerSnapshot } from "#/introspection/inspector";
8
- import type { MetadataReader } from "#/metadata/metadata-types";
1
+ import type { BindToBuilder } from "#core/binding";
2
+ import type { AsyncModule, SyncModule } from "#core/module";
3
+ import type { Token } from "#core/token";
4
+ import type { ActivationHandler, BindingIdentifier, Constructor, DeactivationHandler, ResolveOptions } from "#core/types";
5
+ import type { AutoRegisterRegistry } from "#decorators/injectable";
6
+ import type { ContainerGraphJson, GraphOptions } from "#introspection/dependency-graph";
7
+ import type { BindingSnapshot, ContainerSnapshot } from "#introspection/inspector";
8
+ import type { MetadataReader } from "#metadata/metadata-types";
9
9
  /**
10
10
  * The public surface: binding, resolving, modules, lifecycle hooks, child scopes, and disposal.
11
11
  *
@@ -1,23 +1,23 @@
1
- import { BindingChain } from "#/container/binding-builders";
2
- import { NO_INSTANCE } from "#/core/binding";
3
- import { effectiveBindingScope } from "#/core/binding-scope";
4
- import { constraintRequirementsOf } from "#/core/constraint-requirement";
5
- import { getOrInsert, getOrInsertComputed } from "#/core/map-upsert";
6
- import { isSyncModule, MODULE_SETUP } from "#/core/module";
7
- import { BindingRegistry } from "#/core/registry";
8
- import { tokenName } from "#/core/token";
9
- import { RESOLUTION_DIAGNOSTICS } from "#/errors/diagnostics";
10
- import { AsyncModuleLoadError, CircularDependencyError, DisposedContainerError, InternalError, RebindUnboundTokenError, ScopeViolationError, SyncDisposalNotSupportedError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#/errors/errors";
11
- import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#/injection/resolve-options";
12
- import { buildDependencyGraph } from "#/introspection/dependency-graph";
13
- import { Inspector } from "#/introspection/inspector";
14
- import { LifecycleManager } from "#/lifecycle/lifecycle-manager";
15
- import { ScopeManager } from "#/lifecycle/scope-manager";
16
- import { MetadataReaderToken } from "#/metadata/metadata-reader-token";
17
- import { defaultMetadataReader } from "#/metadata/symbol-metadata-reader";
18
- import { verifyingMetadataReader } from "#/metadata/verifying-metadata-reader";
19
- import { ROOT_BRANCH } from "#/resolution/path/resolution-path";
20
- import { DependencyResolver } from "#/resolution/resolver";
1
+ import { BindingChain } from "#container/binding-builders";
2
+ import { NO_INSTANCE } from "#core/binding";
3
+ import { effectiveBindingScope } from "#core/binding-scope";
4
+ import { constraintRequirementsOf } from "#core/constraint-requirement";
5
+ import { getOrInsert, getOrInsertComputed } from "#core/map-upsert";
6
+ import { isSyncModule, MODULE_SETUP } from "#core/module";
7
+ import { BindingRegistry } from "#core/registry";
8
+ import { tokenName } from "#core/token";
9
+ import { RESOLUTION_DIAGNOSTICS } from "#errors/diagnostics";
10
+ import { AsyncModuleLoadError, CircularDependencyError, DisposedContainerError, InternalError, RebindUnboundTokenError, ScopeViolationError, SyncDisposalNotSupportedError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#errors/errors";
11
+ import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#injection/resolve-options";
12
+ import { buildDependencyGraph } from "#introspection/dependency-graph";
13
+ import { Inspector } from "#introspection/inspector";
14
+ import { LifecycleManager } from "#lifecycle/lifecycle-manager";
15
+ import { ScopeManager } from "#lifecycle/scope-manager";
16
+ import { MetadataReaderToken } from "#metadata/metadata-reader-token";
17
+ import { defaultMetadataReader } from "#metadata/symbol-metadata-reader";
18
+ import { verifyingMetadataReader } from "#metadata/verifying-metadata-reader";
19
+ import { ROOT_BRANCH } from "#resolution/path/resolution-path";
20
+ import { DependencyResolver } from "#resolution/resolver";
21
21
  /** Whether a requirement's name is declared — on its token when it names one, on any token otherwise. */
22
22
  function isSlotNameDeclared(declared, requirement) {
23
23
  if (requirement.tokenName !== undefined) {
@@ -1,5 +1,5 @@
1
- import type { Binding } from "#/core/binding";
2
- import type { BindingScope } from "#/core/types";
1
+ import type { Binding } from "#core/binding";
2
+ import type { BindingScope } from "#core/types";
3
3
  /**
4
4
  * The scope a binding resolves under.
5
5
  *
@@ -1,7 +1,7 @@
1
- import type { BindingTag, TagKeyMask } from "#/core/tag";
2
- import type { Token } from "#/core/token";
3
- import type { ActivationHandler, BindingIdentifier, BindingScope, Constructor, DeactivationHandler, ResolutionContext, ResolutionFrame, BindingConstraint } from "#/core/types";
4
- import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "#/injection/descriptor";
1
+ import type { BindingTag, TagKeyMask } from "#core/tag";
2
+ import type { Token } from "#core/token";
3
+ import type { ActivationHandler, BindingIdentifier, BindingScope, Constructor, DeactivationHandler, ResolutionContext, ResolutionFrame, BindingConstraint } from "#core/types";
4
+ import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "#injection/descriptor";
5
5
  /**
6
6
  * The criterion set a binding registers under and a request matches against.
7
7
  *
@@ -61,8 +61,8 @@ interface BindingBase<Value> {
61
61
  *
62
62
  * @remarks Both cycle guards that can use an `O(1)` flag read this — the sync transient-dynamic
63
63
  * lane and the async cascade lane — because synchronous code does not interleave, so the flag *is*
64
- * exact path membership. Not optional: `createBinding` always sets it, and a field that may be
65
- * absent is a field that can cost the shared hidden class. Resolver-owned; callers never set it.
64
+ * exact path membership. Not optional: the binding builder always initializes it, and a field that
65
+ * may be absent is a field that can cost the shared hidden class. Resolver-owned; callers never set it.
66
66
  */
67
67
  inFlight: boolean;
68
68
  /**
@@ -1,4 +1,4 @@
1
- import { NO_TAG_KEYS, slotName, tagKeyMaskOf } from "#/core/tag";
1
+ import { NO_TAG_KEYS, slotName, tagKeyMaskOf } from "#core/tag";
2
2
  /**
3
3
  * Builds a slot from its criterion set, deriving the name view and key mask.
4
4
  *
@@ -1,5 +1,5 @@
1
1
  /** What a constraint needs to exist before it can ever match, so `validate()` can check for it. */
2
- import type { BindingConstraint } from "#/core/types";
2
+ import type { BindingConstraint } from "#core/types";
3
3
  /**
4
4
  * Key the requirement is attached under.
5
5
  *
@@ -1,6 +1,6 @@
1
- import type { BindToBuilder } from "#/core/binding";
2
- import type { Token } from "#/core/token";
3
- import type { Constructor } from "#/core/types";
1
+ import type { BindToBuilder } from "#core/binding";
2
+ import type { Token } from "#core/token";
3
+ import type { Constructor } from "#core/types";
4
4
  declare const SYNC_MODULE_BRAND: unique symbol;
5
5
  declare const ASYNC_MODULE_BRAND: unique symbol;
6
6
  /**
@@ -1,7 +1,7 @@
1
- import type { Binding } from "#/core/binding";
2
- import type { BindingTag } from "#/core/tag";
3
- import type { Token } from "#/core/token";
4
- import type { BindingConstraint, BindingIdentifier, Constructor } from "#/core/types";
1
+ import type { Binding } from "#core/binding";
2
+ import type { BindingTag } from "#core/tag";
3
+ import type { Token } from "#core/token";
4
+ import type { BindingConstraint, BindingIdentifier, Constructor } from "#core/types";
5
5
  /**
6
6
  * One container's binding store, indexed by token, binding id, and slot for fast lookup.
7
7
  *
@@ -27,8 +27,8 @@ export declare class BindingRegistry {
27
27
  /**
28
28
  * Adds or replaces a binding using slot-aware last-wins. Returns the displaced binding, if any.
29
29
  *
30
- * @remarks The binding is stored by reference — it must come from `createBinding`, which is
31
- * what guarantees the single hidden class the resolver's hot reads depend on.
30
+ * @remarks The binding is stored by reference — it must come from the one binding builder, which
31
+ * is what guarantees the single hidden class the resolver's hot reads depend on.
32
32
  */
33
33
  add(binding: Binding): Binding | undefined;
34
34
  /** Remove all bindings for a token. Returns removed bindings. */
@@ -1,6 +1,6 @@
1
- import { bindingSlotEquals, bindingSlotToString, writableMembership, writablePredicate } from "#/core/binding";
2
- import { getOrInsert } from "#/core/map-upsert";
3
- import { advanceStateEpoch } from "#/core/state-epoch";
1
+ import { bindingSlotEquals, bindingSlotToString, writableMembership, writablePredicate } from "#core/binding";
2
+ import { getOrInsert } from "#core/map-upsert";
3
+ import { advanceStateEpoch } from "#core/state-epoch";
4
4
  const NO_BINDINGS = Object.freeze([]);
5
5
  /** One construction site, so every record shares a hidden class. */
6
6
  function createTokenRecord(bindings) {
@@ -69,8 +69,8 @@ export class BindingRegistry {
69
69
  /**
70
70
  * Adds or replaces a binding using slot-aware last-wins. Returns the displaced binding, if any.
71
71
  *
72
- * @remarks The binding is stored by reference — it must come from `createBinding`, which is
73
- * what guarantees the single hidden class the resolver's hot reads depend on.
72
+ * @remarks The binding is stored by reference — it must come from the one binding builder, which
73
+ * is what guarantees the single hidden class the resolver's hot reads depend on.
74
74
  */
75
75
  add(binding) {
76
76
  this.#bump();
@@ -299,19 +299,14 @@ export class BindingRegistry {
299
299
  */
300
300
  setMany(binding) {
301
301
  this.#bump();
302
- const key = binding.token;
303
- if (this.#lone.get(key) === binding) {
304
- writableMembership(binding).isMany = true;
305
- this.#lone.delete(key);
306
- this.#createRecord(key, [binding]);
302
+ // Set before any (re)indexing so `#indexSlot` sees a member and leaves it out of every slot.
303
+ writableMembership(binding).isMany = true;
304
+ if (this.#promoteLoneToRecord(binding.token, binding)) {
307
305
  return;
308
306
  }
309
- // A default occupant that becomes a member frees its slot, so drop the stale index entry.
310
- const record = this.#records?.get(key);
311
- if (record?.defaultOccupant === binding) {
312
- record.defaultOccupant = undefined;
313
- }
314
- writableMembership(binding).isMany = true;
307
+ // A default occupant that becomes a member frees its slot, so drop the stale index entry. A
308
+ // member cannot collapse a record back to lone, so this path does not settle.
309
+ this.#clearDefaultOccupant(binding.token, binding);
315
310
  }
316
311
  /**
317
312
  * Adds a predicate to a live binding in place.
@@ -322,21 +317,38 @@ export class BindingRegistry {
322
317
  */
323
318
  setPredicate(binding, predicate) {
324
319
  this.#bump();
320
+ // Set before any (re)indexing: a predicate-only binding holds no default slot.
325
321
  writablePredicate(binding).predicate = predicate;
326
- const key = binding.token;
327
- if (this.#lone.get(key) === binding) {
328
- this.#lone.delete(key);
329
- this.#createRecord(key, [binding]);
322
+ if (this.#promoteLoneToRecord(binding.token, binding)) {
330
323
  return;
331
324
  }
325
+ // The binding may have vacated the default slot, and a narrowed record can collapse back to lone.
326
+ const record = this.#clearDefaultOccupant(binding.token, binding);
327
+ if (record !== undefined) {
328
+ this.#settle(binding.token, record);
329
+ }
330
+ }
331
+ /**
332
+ * Moves a binding still holding the lone seat into a fresh one-binding record, returning whether it did.
333
+ *
334
+ * @remarks Its field (`isMany` or `predicate`) is written before this call, so the founding
335
+ * `#indexSlot` files it under the slot it now holds.
336
+ */
337
+ #promoteLoneToRecord(key, binding) {
338
+ if (this.#lone.get(key) !== binding) {
339
+ return false;
340
+ }
341
+ this.#lone.delete(key);
342
+ this.#createRecord(key, [binding]);
343
+ return true;
344
+ }
345
+ /** Clears the default-slot index entry a binding has vacated, returning its record if one exists. */
346
+ #clearDefaultOccupant(key, binding) {
332
347
  const record = this.#records?.get(key);
333
- // The binding is now predicate-only, so it no longer holds the default slot it may have held.
334
348
  if (record?.defaultOccupant === binding) {
335
349
  record.defaultOccupant = undefined;
336
350
  }
337
- if (record !== undefined) {
338
- this.#settle(key, record);
339
- }
351
+ return record;
340
352
  }
341
353
  /** Summarize available slot strings for a token (for error messages). */
342
354
  availableSlotStrings(token) {
@@ -1,4 +1,4 @@
1
- import type { Constructor } from "#/core/constructor-type";
1
+ import type { Constructor } from "#core/constructor-type";
2
2
  declare const TOKEN_BRAND: unique symbol;
3
3
  declare const TOKEN_NAMES_BRAND: unique symbol;
4
4
  /**
@@ -1,8 +1,8 @@
1
- import type { Constructor } from "#/core/constructor-type";
2
- import type { BindingTag, TagKeyMask } from "#/core/tag";
3
- import type { Token } from "#/core/token";
4
- export type { Constructor } from "#/core/constructor-type";
5
- export type { BindingTag, TagKey, TagKeyMask } from "#/core/tag";
1
+ import type { Constructor } from "#core/constructor-type";
2
+ import type { BindingTag, TagKeyMask } from "#core/tag";
3
+ import type { Token } from "#core/token";
4
+ export type { Constructor } from "#core/constructor-type";
5
+ export type { BindingTag, TagKey, TagKeyMask } from "#core/tag";
6
6
  /**
7
7
  * Token or class constructor used as a binding / injection / resolve key.
8
8
  *
@@ -1,7 +1,7 @@
1
1
  /** `@inject` — the accessor-decorator channel, resolving from the ambient container. */
2
- import type { Token } from "#/core/token";
3
- import type { Constructor } from "#/core/types";
4
- import type { InjectionDescriptor, InjectOptions } from "#/injection/descriptor";
2
+ import type { Token } from "#core/token";
3
+ import type { Constructor } from "#core/types";
4
+ import type { InjectionDescriptor, InjectOptions } from "#injection/descriptor";
5
5
  type ClassAccessorDecorator<This, Value> = (target: ClassAccessorDecoratorTarget<This, Value>, context: ClassAccessorDecoratorContext<This, Value>) => ClassAccessorDecoratorResult<This, Value> | void;
6
6
  /**
7
7
  * Creates a dual-role value: an injection descriptor that also works as a class accessor decorator.
@@ -1,8 +1,8 @@
1
- import { getActiveContainer, getAmbientResolution } from "#/ambient/active-container";
2
- import { MissingContainerContextError, StaticMemberDecoratorError } from "#/errors/errors";
3
- import { buildInjectionDescriptor } from "#/injection/descriptor";
4
- import { injectionSlotToResolveOptions } from "#/injection/resolve-options";
5
- import { INJECT_ACCESSOR_KEY } from "#/metadata/metadata-keys";
1
+ import { getActiveContainer, getAmbientResolution } from "#ambient/active-container";
2
+ import { MissingContainerContextError, StaticMemberDecoratorError } from "#errors/errors";
3
+ import { buildInjectionDescriptor } from "#injection/descriptor";
4
+ import { injectionSlotToResolveOptions } from "#injection/resolve-options";
5
+ import { INJECT_ACCESSOR_KEY } from "#metadata/metadata-keys";
6
6
  /**
7
7
  * The name of the class being constructed, or `undefined` when there is none to report.
8
8
  *
@@ -1,5 +1,5 @@
1
- import type { BindingScope, Constructor } from "#/core/types";
2
- import type { InjectableDependency, ResolvedDependencyValue } from "#/injection/descriptor";
1
+ import type { BindingScope, Constructor } from "#core/types";
2
+ import type { InjectableDependency, ResolvedDependencyValue } from "#injection/descriptor";
3
3
  /**
4
4
  * The collector `@injectable` registers a class into, for a container to bind later.
5
5
  *
@@ -1,5 +1,5 @@
1
- import { normalizeToDescriptor } from "#/injection/descriptor";
2
- import { INJECTABLE_KEY } from "#/metadata/metadata-keys";
1
+ import { normalizeToDescriptor } from "#injection/descriptor";
2
+ import { INJECTABLE_KEY } from "#metadata/metadata-keys";
3
3
  /**
4
4
  * Creates an empty auto-register registry.
5
5
  *
@@ -1,5 +1,5 @@
1
- import { StaticMemberDecoratorError } from "#/errors/errors";
2
- import { LIFECYCLE_KEY } from "#/metadata/metadata-keys";
1
+ import { StaticMemberDecoratorError } from "#errors/errors";
2
+ import { LIFECYCLE_KEY } from "#metadata/metadata-keys";
3
3
  /** Records the decorated method under one lifecycle phase; both decorators differ only in that phase. */
4
4
  function recordLifecycleMethod(phase) {
5
5
  return function (target, context) {
@@ -1,5 +1,5 @@
1
- import type { ConstraintRequirement } from "#/core/constraint-requirement";
2
- import type { BindingIdentifier, BindingScope, ResolveOptions } from "#/core/types";
1
+ import type { ConstraintRequirement } from "#core/constraint-requirement";
2
+ import type { BindingIdentifier, BindingScope, ResolveOptions } from "#core/types";
3
3
  /**
4
4
  * Base class for every error the library throws, each carrying a machine-readable `code`.
5
5
  *
@@ -223,15 +223,6 @@ export declare class MissingContainerContextError extends DiError {
223
223
  readonly accessorName: string | symbol;
224
224
  constructor(className: string | undefined, accessorName: string | symbol);
225
225
  }
226
- /**
227
- * A fluent chain was refined before a `to*()` call gave it a binding to refine.
228
- *
229
- * @remarks The builder types make this unreachable from TypeScript — `bind()` returns
230
- * `BindToBuilder`, which exposes only `to*()`. It exists for JavaScript callers and for anyone who
231
- * casts past the types, so the misuse fails loudly instead of mutating nothing.
232
- *
233
- * @since 0.5.0-canary.8
234
- */
235
226
  /**
236
227
  * A second `to*()` on a chain that already registered its binding.
237
228
  *
@@ -259,6 +250,12 @@ export declare class ManyBindingSlotError extends DiError {
259
250
  constructor(tokenName: string);
260
251
  }
261
252
  /**
253
+ * A fluent chain was refined before a `to*()` call gave it a binding to refine.
254
+ *
255
+ * @remarks The builder types make this unreachable from TypeScript — `bind()` returns
256
+ * `BindToBuilder`, which exposes only `to*()`. It exists for JavaScript callers and for anyone who
257
+ * casts past the types, so the misuse fails loudly instead of mutating nothing.
258
+ *
262
259
  * @since 0.10.0
263
260
  */
264
261
  export declare class ChainNotRegisteredError extends DiError {