@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
@@ -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
  *
@@ -55,14 +55,14 @@ export declare const DEFAULT_BINDING_SLOT: BindingSlot;
55
55
  */
56
56
  export declare function bindingSlotToString(slot: BindingSlot): string;
57
57
  interface BindingBase<Value> {
58
- readonly id: BindingIdentifier;
58
+ readonly identifier: BindingIdentifier;
59
59
  /**
60
60
  * True while this binding's factory is executing on the current synchronous call stack.
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
  /**
@@ -83,20 +83,25 @@ interface BindingBase<Value> {
83
83
  readonly token: Token<Value> | Constructor<Value>;
84
84
  readonly slot: BindingSlot;
85
85
  readonly predicate?: BindingConstraint | undefined;
86
+ /**
87
+ * Whether the binding is a collection member only: `resolveAll` includes it, `resolve` never
88
+ * selects it, and it neither displaces nor is displaced under slot last-wins.
89
+ */
90
+ readonly isMany: boolean;
86
91
  }
87
- type BindingBaseKeys = keyof BindingBase<unknown>;
88
92
  /**
89
93
  * The lifecycle hooks every kind but `alias` may carry.
90
94
  *
91
95
  * @remarks Declared as **methods**, not function-typed properties, so their parameters compare
92
96
  * bivariantly and `Binding<Value>` stays assignable to `Binding`. The engine erases the value type at
93
97
  * every lane boundary regardless; the public `ActivationHandler` / `DeactivationHandler` keep strict
94
- * checking, which is where a user's handler is actually verified. Not `readonly`: a fluent chain
95
- * refines both in place — see {@link RefinableBindingFields}.
98
+ * checking, which is where a user's handler is actually verified. Named apart from the fluent
99
+ * `onActivation()` / `onDeactivation()` steps because the chain that registers them is the binding
100
+ * itself, and a field cannot share a name with a method on the same object.
96
101
  */
97
102
  interface BindingLifecycleHooks<Value> {
98
- onActivation?(ctx: ResolutionContext, instance: Value): Value | Promise<Value>;
99
- onDeactivation?(instance: Value): void | Promise<void>;
103
+ activationHook?(ctx: ResolutionContext, instance: Value): Value | Promise<Value>;
104
+ deactivationHook?(instance: Value): void | Promise<void>;
100
105
  }
101
106
  /**
102
107
  * A binding that instantiates a constructor.
@@ -182,17 +187,6 @@ export interface AliasBinding<Value> extends BindingBase<Value> {
182
187
  * @since 0.3.16-canary.0
183
188
  */
184
189
  export type Binding<Value = unknown> = ClassBinding<Value> | DynamicBinding<Value> | DynamicAsyncBinding<Value> | ResolvedBinding<Value> | ResolvedAsyncBinding<Value> | ConstantBinding<Value> | AliasBinding<Value>;
185
- /** `Omit` applied per union member, since a bare `Omit` would collapse the union into one shape. */
186
- type DistributiveOmit<Union, Keys extends PropertyKey> = Union extends unknown ? Omit<Union, Keys> : never;
187
- /**
188
- * Builder-only payload before `id`, `token`, `slot`, and `predicate` are applied.
189
- *
190
- * @remarks Derived rather than listed: a new binding kind joins this the moment it joins
191
- * {@link Binding}, so the two unions cannot diverge.
192
- *
193
- * @since 0.3.16-canary.0
194
- */
195
- export type PartialBinding<Value> = DistributiveOmit<Binding<Value>, BindingBaseKeys>;
196
190
  /**
197
191
  * Returns a process-unique identifier for a new binding.
198
192
  *
@@ -200,45 +194,39 @@ export type PartialBinding<Value> = DistributiveOmit<Binding<Value>, BindingBase
200
194
  */
201
195
  export declare function generateBindingId(): BindingIdentifier;
202
196
  /**
203
- * The single construction site for bindings — one literal, one V8 hidden class.
204
- *
205
- * @remarks Field order is fixed and this is the only construction site, so every binding shares one
206
- * hidden class. Reordering the fields, or adding a second site, gives that up.
207
- *
208
- * @param source - the kind-specific payload, or an existing binding to re-slot
209
- * @param token - the key requests resolve the binding by
210
- * @param slot - the name + tags a request must match to select this binding
211
- * @param predicate - a custom constraint, or `undefined` for none
212
- * @param id - reuse a caller's id to keep a fluent chain's `id()` stable across refinements
197
+ * Writable view of the one selection field a chain may refine without re-registering: nothing
198
+ * indexes on the predicate, so the registry rewrites it in place and re-homes the binding itself.
213
199
  *
214
- * @since 0.5.0-canary.8
200
+ * @since 0.10.0
215
201
  */
216
- export declare function createBinding<Value>(source: PartialBinding<Value> | Binding<Value>, token: Token<Value> | Constructor<Value>, slot: BindingSlot, predicate: BindingConstraint | undefined, id?: BindingIdentifier): Binding<Value>;
202
+ export interface PredicateField {
203
+ predicate: BindingConstraint | undefined;
204
+ }
217
205
  /**
218
- * Writable view of the only fields a fluent chain may refine after registration.
206
+ * Narrows a registered binding to its predicate for the registry to rewrite.
219
207
  *
220
- * @remarks No registry index is keyed on these, so a builder that owns the registered object
221
- * can write them directly instead of re-registering. `token`, `slot`, `predicate` and `id`
222
- * are excluded on purpose — changing those means re-indexing.
208
+ * @since 0.10.0
209
+ */
210
+ export declare function writablePredicate(binding: Binding): PredicateField;
211
+ /**
212
+ * Writable view of collection membership, which the registry sets because it decides the binding's map.
223
213
  *
224
- * @since 0.5.0-canary.8
214
+ * @since 0.10.0
225
215
  */
226
- export interface RefinableBindingFields<Value> {
227
- onActivation: ActivationHandler<Value> | undefined;
228
- onDeactivation: DeactivationHandler<Value> | undefined;
229
- scope: BindingScope;
216
+ export interface MembershipField {
217
+ isMany: boolean;
230
218
  }
231
219
  /**
232
- * Narrows a registered binding to the fields a fluent chain may still refine.
220
+ * Narrows a registered binding to its membership flag for the registry to set.
233
221
  *
234
- * @since 0.5.0-canary.8
222
+ * @since 0.10.0
235
223
  */
236
- export declare function refinableFields<Value>(binding: Binding<Value>): RefinableBindingFields<Value>;
224
+ export declare function writableMembership(binding: Binding): MembershipField;
237
225
  /**
238
226
  * Drops the memoized resolution frame, for a refinement that changes what the frame reports.
239
227
  *
240
- * @remarks `scope` is the only field a chain writes in place that the frame derives from — a
241
- * re-slot builds a fresh binding, whose frame starts empty anyway.
228
+ * @remarks The frame derives from `scope` and `slot`, both of which a chain now writes in place on
229
+ * the registered object, so every such refinement clears it.
242
230
  *
243
231
  * @since 0.5.0-canary.9
244
232
  */
@@ -257,6 +245,11 @@ export interface SlotConstrainedBuilder<Names extends string = string> {
257
245
  whenTagged(criterion: BindingTag): this;
258
246
  /** Keeps the binding on the default slot, the one an unconstrained request selects. */
259
247
  whenDefault(): this;
248
+ /**
249
+ * Makes the binding a collection member: one of several the token's `resolveAll` returns, never
250
+ * what a single `resolve` selects, and outside slot last-wins. It keeps the default slot.
251
+ */
252
+ many(): this;
260
253
  /** The identifier this binding is registered under. */
261
254
  id(): BindingIdentifier;
262
255
  }
@@ -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
  *
@@ -82,56 +82,30 @@ let bindingIdCounter = 0;
82
82
  * @since 0.3.16-canary.0
83
83
  */
84
84
  export function generateBindingId() {
85
- return String(++bindingIdCounter);
85
+ bindingIdCounter += 1;
86
+ return bindingIdCounter;
86
87
  }
87
88
  /**
88
- * The single construction site for bindings — one literal, one V8 hidden class.
89
+ * Narrows a registered binding to its predicate for the registry to rewrite.
89
90
  *
90
- * @remarks Field order is fixed and this is the only construction site, so every binding shares one
91
- * hidden class. Reordering the fields, or adding a second site, gives that up.
92
- *
93
- * @param source - the kind-specific payload, or an existing binding to re-slot
94
- * @param token - the key requests resolve the binding by
95
- * @param slot - the name + tags a request must match to select this binding
96
- * @param predicate - a custom constraint, or `undefined` for none
97
- * @param id - reuse a caller's id to keep a fluent chain's `id()` stable across refinements
98
- *
99
- * @since 0.5.0-canary.8
91
+ * @since 0.10.0
100
92
  */
101
- export function createBinding(source, token, slot, predicate, id = generateBindingId()) {
102
- const fields = source;
103
- return {
104
- kind: fields.kind,
105
- id,
106
- inFlight: false,
107
- frame: undefined,
108
- // An `in` probe, not `??`: a re-slotted singleton may legitimately hold a cached `undefined`.
109
- instance: "instance" in fields ? fields.instance : NO_INSTANCE,
110
- token,
111
- slot,
112
- predicate,
113
- scope: fields.scope,
114
- target: fields.target,
115
- factory: fields.factory,
116
- deps: fields.deps,
117
- value: fields.value,
118
- onActivation: fields.onActivation,
119
- onDeactivation: fields.onDeactivation,
120
- };
93
+ export function writablePredicate(binding) {
94
+ return binding;
121
95
  }
122
96
  /**
123
- * Narrows a registered binding to the fields a fluent chain may still refine.
97
+ * Narrows a registered binding to its membership flag for the registry to set.
124
98
  *
125
- * @since 0.5.0-canary.8
99
+ * @since 0.10.0
126
100
  */
127
- export function refinableFields(binding) {
101
+ export function writableMembership(binding) {
128
102
  return binding;
129
103
  }
130
104
  /**
131
105
  * Drops the memoized resolution frame, for a refinement that changes what the frame reports.
132
106
  *
133
- * @remarks `scope` is the only field a chain writes in place that the frame derives from — a
134
- * re-slot builds a fresh binding, whose frame starts empty anyway.
107
+ * @remarks The frame derives from `scope` and `slot`, both of which a chain now writes in place on
108
+ * the registered object, so every such refinement clears it.
135
109
  *
136
110
  * @since 0.5.0-canary.9
137
111
  */
@@ -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 { 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
  *
@@ -13,6 +13,12 @@ export declare class BindingRegistry {
13
13
  get version(): number;
14
14
  /** Whether a constant has ever been registered here, and so whether teardown has anything to sweep. */
15
15
  get hasHeldConstantBinding(): boolean;
16
+ /** Whether the deferred tagged-slot index has had to be built. */
17
+ get isTaggedIndexBuilt(): boolean;
18
+ /** Whether an id-keyed operation has had to build the id index. */
19
+ get isIdIndexBuilt(): boolean;
20
+ /** Whether a token carrying more than one default-slot binding has had to build the record map. */
21
+ get isRecordMapBuilt(): boolean;
16
22
  /**
17
23
  * Registers a mutation the indexes don't care about (a fluent chain refining scope or an
18
24
  * activation hook in place), so version-stamped resolver caches still invalidate.
@@ -21,16 +27,30 @@ export declare class BindingRegistry {
21
27
  /**
22
28
  * Adds or replaces a binding using slot-aware last-wins. Returns the displaced binding, if any.
23
29
  *
24
- * @remarks The binding is stored by reference — it must come from `createBinding`, which is
25
- * 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.
26
32
  */
27
33
  add(binding: Binding): Binding | undefined;
28
34
  /** Remove all bindings for a token. Returns removed bindings. */
29
35
  removeByToken(token: Token<unknown> | Constructor): Array<Binding>;
30
36
  /** Remove a specific binding by ID. Returns the removed binding or undefined. */
31
37
  removeById(id: BindingIdentifier): Binding | undefined;
32
- /** Get all bindings for a token. */
38
+ /**
39
+ * Get all bindings for a token.
40
+ *
41
+ * @remarks Allocates a one-element list for a lone default-slot binding, so a hot path asks
42
+ * `getFastDefault()` first and reaches here only for a token that keeps a record.
43
+ */
33
44
  getAll(token: Token<unknown> | Constructor): ReadonlyArray<Binding>;
45
+ /**
46
+ * The bindings of a token that keeps a record, or none.
47
+ *
48
+ * @remarks For a caller whose lone-map probe has just missed: the record map is all that is left
49
+ * to ask, and a lone binding's one-element list is never materialised here.
50
+ */
51
+ getRecorded(token: Token<unknown> | Constructor): ReadonlyArray<Binding>;
52
+ /** How many bindings a token holds, without materialising a lone binding's list. */
53
+ countBindings(token: Token<unknown> | Constructor): number;
34
54
  /** Get binding by ID. */
35
55
  getById(id: BindingIdentifier): Binding | undefined;
36
56
  /** Check if any binding exists for token. */
@@ -48,6 +68,8 @@ export declare class BindingRegistry {
48
68
  * identity — where a value-keyed map answered by SameValueZero and parted from `Object.is` on ±0.
49
69
  */
50
70
  getSimpleTagged(token: Token<unknown> | Constructor, criterion: BindingTag): Binding | undefined;
71
+ /** The binding whose slot is exactly these two criteria, declared in either order, or `undefined`. */
72
+ getPairTagged(token: Token<unknown> | Constructor, first: BindingTag, second: BindingTag): Binding | undefined;
51
73
  /**
52
74
  * The multi-tag bindings whose slot's first criterion is `criterion`.
53
75
  *
@@ -55,9 +77,30 @@ export declare class BindingRegistry {
55
77
  * against the request — first-criterion bucketing only guarantees each candidate appears once.
56
78
  */
57
79
  getMultiTagged(token: Token<unknown> | Constructor, criterion: BindingTag): ReadonlyArray<Binding> | undefined;
80
+ /** A token's lone default-slot binding — the first read of every synchronous resolve. */
58
81
  getFastDefault(token: Token<unknown> | Constructor): Binding | undefined;
82
+ /**
83
+ * Takes a live binding out of every index, lets `rewrite` change its slot or predicate, and reports
84
+ * whether it was live; the caller registers it again with `add`.
85
+ *
86
+ * @remarks The binding keeps its object and its id, so the id index needs no touch and nothing
87
+ * that holds the object has to be told. `false` means the binding was unbound or displaced since
88
+ * it registered, and a refinement must not resurrect it.
89
+ */
90
+ reslot(binding: Binding, rewrite: () => void): boolean;
91
+ /**
92
+ * Marks a live binding as a collection member in place, moving it out of the lone map: a member is
93
+ * never the token's lone default answer, and nothing else indexes on membership.
94
+ */
95
+ setMany(binding: Binding): void;
96
+ /**
97
+ * Adds a predicate to a live binding in place.
98
+ *
99
+ * @remarks Only ever narrows — `when()` composes with any existing predicate — so the argument is
100
+ * never absent. Nothing indexes on the predicate, so the binding object and its id stay; a lone
101
+ * binding moves to a record because the lone map holds default-slot bindings with no predicate.
102
+ */
103
+ setPredicate(binding: Binding, predicate: BindingConstraint): void;
59
104
  /** Summarize available slot strings for a token (for error messages). */
60
105
  availableSlotStrings(token: Token<unknown> | Constructor): Array<string>;
61
- /** Whether the deferred tagged-slot index has had to be built. */
62
- get isTaggedIndexBuilt(): boolean;
63
106
  }