@codefast/di 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/README.md +71 -7
  3. package/dist/ambient/active-container.d.ts +13 -11
  4. package/dist/ambient/active-container.js +8 -6
  5. package/dist/container/binding-builders.d.ts +25 -7
  6. package/dist/container/binding-builders.js +110 -42
  7. package/dist/container/container.d.ts +8 -8
  8. package/dist/container/container.js +244 -67
  9. package/dist/core/binding-declaration.d.ts +120 -0
  10. package/dist/core/binding-declaration.js +186 -0
  11. package/dist/core/binding-scope.d.ts +2 -2
  12. package/dist/core/binding.d.ts +61 -9
  13. package/dist/core/binding.js +49 -2
  14. package/dist/core/constraint-requirement.d.ts +1 -1
  15. package/dist/core/module.d.ts +10 -7
  16. package/dist/core/module.js +17 -3
  17. package/dist/core/registry.d.ts +17 -13
  18. package/dist/core/registry.js +155 -59
  19. package/dist/core/state-epoch.d.ts +18 -1
  20. package/dist/core/state-epoch.js +17 -0
  21. package/dist/core/tag.js +1 -1
  22. package/dist/core/token.d.ts +1 -1
  23. package/dist/core/types.d.ts +5 -5
  24. package/dist/decorators/decorator-metadata.d.ts +9 -0
  25. package/dist/decorators/decorator-metadata.js +20 -0
  26. package/dist/decorators/inject.d.ts +3 -3
  27. package/dist/decorators/inject.js +7 -6
  28. package/dist/decorators/injectable.d.ts +2 -2
  29. package/dist/decorators/injectable.js +5 -3
  30. package/dist/decorators/lifecycle-decorators.js +9 -3
  31. package/dist/errors/errors.d.ts +85 -14
  32. package/dist/errors/errors.js +100 -20
  33. package/dist/index.d.ts +37 -35
  34. package/dist/index.js +20 -19
  35. package/dist/injection/descriptor.d.ts +3 -3
  36. package/dist/injection/descriptor.js +3 -7
  37. package/dist/injection/resolve-options.d.ts +3 -3
  38. package/dist/injection/resolve-options.js +7 -5
  39. package/dist/introspection/dependency-graph.d.ts +10 -5
  40. package/dist/introspection/dependency-graph.js +51 -28
  41. package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -1
  42. package/dist/introspection/graph-adapters/dot.d.ts +1 -1
  43. package/dist/introspection/graph-adapters/mermaid.d.ts +1 -1
  44. package/dist/introspection/graph-adapters/reactflow.d.ts +1 -1
  45. package/dist/introspection/graph-adapters/reactflow.js +6 -4
  46. package/dist/introspection/inspector.d.ts +4 -4
  47. package/dist/introspection/inspector.js +9 -12
  48. package/dist/lifecycle/lifecycle-manager.d.ts +4 -4
  49. package/dist/lifecycle/lifecycle-manager.js +18 -6
  50. package/dist/lifecycle/scope-manager.d.ts +2 -2
  51. package/dist/lifecycle/scope-manager.js +31 -13
  52. package/dist/metadata/metadata-reader-token.d.ts +2 -2
  53. package/dist/metadata/metadata-reader-token.js +1 -1
  54. package/dist/metadata/metadata-types.d.ts +3 -3
  55. package/dist/metadata/symbol-metadata-reader.d.ts +3 -3
  56. package/dist/metadata/symbol-metadata-reader.js +1 -1
  57. package/dist/metadata/verifying-metadata-reader.d.ts +5 -4
  58. package/dist/metadata/verifying-metadata-reader.js +30 -8
  59. package/dist/resolution/async-fan-out.d.ts +12 -0
  60. package/dist/resolution/async-fan-out.js +26 -0
  61. package/dist/resolution/cache/activation-need.d.ts +4 -5
  62. package/dist/resolution/cache/activation-need.js +11 -18
  63. package/dist/resolution/cache/binding-lookup-cache.d.ts +5 -12
  64. package/dist/resolution/cache/binding-lookup-cache.js +32 -19
  65. package/dist/resolution/cache/class-introspector.d.ts +16 -5
  66. package/dist/resolution/cache/class-introspector.js +73 -56
  67. package/dist/resolution/context.d.ts +17 -25
  68. package/dist/resolution/context.js +47 -56
  69. package/dist/resolution/path/resolution-path.d.ts +48 -13
  70. package/dist/resolution/path/resolution-path.js +90 -39
  71. package/dist/resolution/plan/instantiation-plan.d.ts +5 -5
  72. package/dist/resolution/plan/instantiation-plan.js +66 -26
  73. package/dist/resolution/plan/plan-codegen.d.ts +10 -7
  74. package/dist/resolution/plan/plan-codegen.js +62 -34
  75. package/dist/resolution/resolver.d.ts +16 -17
  76. package/dist/resolution/resolver.js +316 -273
  77. package/dist/resolution/select/binding-select.d.ts +6 -5
  78. package/dist/resolution/select/binding-select.js +5 -4
  79. package/dist/resolution/select/constraints.d.ts +3 -3
  80. package/dist/resolution/select/constraints.js +4 -4
  81. package/package.json +14 -2
@@ -1,21 +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";
7
- /** One criterion per key: re-tagging the same key replaces it rather than asking for both values. */
8
- function updateSlotTag(slot, criterion) {
9
- const tags = [...slot.tags];
10
- const existingIndex = tags.findIndex((existing) => existing.key === criterion.key);
11
- if (existingIndex === -1) {
12
- tags.push(criterion);
13
- }
14
- else {
15
- tags[existingIndex] = criterion;
16
- }
17
- return createBindingSlot(tags);
18
- }
1
+ import { clearBindingFrame, NO_ACTIVATION_STAMP, DEFAULT_BINDING_SLOT, generateBindingId, NO_INSTANCE, UNREGISTERED_ORDER, withSlotCriterion, } 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";
19
7
  // ── BindingChain ─────────────────────────────────────────────────────────────────────────────────────────────────────
20
8
  /**
21
9
  * The one builder behind `bind()` and every `to*()` return type — and the binding it registers.
@@ -33,6 +21,9 @@ export class BindingChain {
33
21
  identifier = generateBindingId();
34
22
  inFlight = false;
35
23
  frame = undefined;
24
+ rootContext = undefined;
25
+ activationStamp = NO_ACTIVATION_STAMP;
26
+ registrationOrder = UNREGISTERED_ORDER;
36
27
  instance = NO_INSTANCE;
37
28
  token;
38
29
  slot = DEFAULT_BINDING_SLOT;
@@ -76,9 +67,70 @@ export class BindingChain {
76
67
  this.kind = kind;
77
68
  this.scope = scope;
78
69
  this.#isRegistered = true;
79
- this.#commit(undefined);
70
+ // A fresh registration parks nothing yet and restores nothing: one add, then the version.
71
+ const registration = this.#registration;
72
+ const registry = registration.registry;
73
+ const displaced = registry.add(this.#binding);
74
+ if (displaced !== undefined) {
75
+ if (registration.deactivateDisplaced !== undefined) {
76
+ registration.deactivateDisplaced(displaced);
77
+ }
78
+ else {
79
+ this.#displacedByChain = [displaced];
80
+ registration.onDisplaced?.(displaced);
81
+ }
82
+ }
83
+ if (registration.moduleBindingIds !== undefined) {
84
+ registration.moduleBindingIds.push(this.identifier);
85
+ }
86
+ this.#versionAfterLastWrite = registry.version;
80
87
  return this;
81
88
  }
89
+ /**
90
+ * Registers a declared module's bindings in list order, each a chain of its own that no step
91
+ * will ever refine.
92
+ *
93
+ * @remarks A displaced binding is handled as `to*()` handles it, never parked: nothing refines the
94
+ * chain, so nothing could restore it.
95
+ */
96
+ static registerDeclared(declarations, registration) {
97
+ const { registry, moduleBindingIds, deactivateDisplaced, onDisplaced } = registration;
98
+ for (let index = 0; index < declarations.length; index += 1) {
99
+ const declaration = declarations[index];
100
+ const binding = BindingChain.#fromDeclaration(declaration, registration);
101
+ const displaced = registry.add(binding);
102
+ if (displaced !== undefined) {
103
+ if (deactivateDisplaced === undefined) {
104
+ onDisplaced?.(displaced);
105
+ }
106
+ else {
107
+ deactivateDisplaced(displaced);
108
+ }
109
+ }
110
+ moduleBindingIds?.push(binding.identifier);
111
+ }
112
+ }
113
+ /**
114
+ * A registered chain in the shape a declaration's steps would have left.
115
+ *
116
+ * @remarks Typed `unknown` like the declaration it copies, so the value type is never asserted here.
117
+ */
118
+ static #fromDeclaration(declaration, registration) {
119
+ const chain = new BindingChain(declaration.token, registration);
120
+ chain.kind = declaration.kind;
121
+ chain.slot = declaration.slot;
122
+ chain.predicate = declaration.predicate;
123
+ chain.isMany = declaration.isMany;
124
+ chain.scope = declaration.scope;
125
+ chain.target = declaration.target;
126
+ chain.factory = declaration.factory;
127
+ chain.deps = declaration.deps;
128
+ chain.value = declaration.value;
129
+ chain.activationHook = declaration.activationHook;
130
+ chain.deactivationHook = declaration.deactivationHook;
131
+ chain.#isRegistered = true;
132
+ return chain.#binding;
133
+ }
82
134
  // ── Registration ───────────────────────────────────────────────────────────────────────────────────────────────────
83
135
  to(type) {
84
136
  this.#requireUnregistered();
@@ -126,6 +178,24 @@ export class BindingChain {
126
178
  return this.#register("alias", "transient");
127
179
  }
128
180
  // ── Refinement ─────────────────────────────────────────────────────────────────────────────────────────────────────
181
+ /**
182
+ * Whether this chain provably still owns the registry's last write with nothing parked.
183
+ *
184
+ * @remarks When it does, a refinement rewrites the live binding in place; otherwise `#reslot`
185
+ * re-checks liveness and restores what the new shape frees.
186
+ */
187
+ get #isProvablyLive() {
188
+ return this.#registration.registry.version === this.#versionAfterLastWrite && this.#displacedByChain === undefined;
189
+ }
190
+ /** Stamps this chain's last-write version after a mutation it made in place. */
191
+ #recordWrite() {
192
+ this.#versionAfterLastWrite = this.#registration.registry.version;
193
+ }
194
+ /** Bumps the registry for an index-neutral mutation (scope, hook) and records this chain's write. */
195
+ #touchAndRecordWrite() {
196
+ this.#registration.registry.touch();
197
+ this.#recordWrite();
198
+ }
129
199
  // Slot and predicate are what the registry indexes on, so a re-slot takes the binding out of the
130
200
  // registry, rewrites the two fields while it is out, and registers it again — same object, same id.
131
201
  #reslot(slot, predicate) {
@@ -149,8 +219,7 @@ export class BindingChain {
149
219
  }
150
220
  // The frame reports the scope, so a resolve before this call memoized the previous one.
151
221
  clearBindingFrame(this.#binding);
152
- this.#registration.registry.touch();
153
- this.#versionAfterLastWrite = this.#registration.registry.version;
222
+ this.#touchAndRecordWrite();
154
223
  return this;
155
224
  }
156
225
  // SPEC calls a candidate a binding that passes *all* of a chain's predicates, and the chain type
@@ -162,13 +231,10 @@ export class BindingChain {
162
231
  const narrowed = previous === undefined
163
232
  ? predicate
164
233
  : 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;
234
+ // The slot is unchanged, so a provably-live binding just takes the predicate in place.
235
+ if (this.#isProvablyLive) {
236
+ this.#registration.registry.setPredicate(this.#binding, narrowed);
237
+ this.#recordWrite();
172
238
  return this;
173
239
  }
174
240
  return this.#reslot(this.slot, narrowed);
@@ -181,7 +247,7 @@ export class BindingChain {
181
247
  if (this.isMany) {
182
248
  throw new ManyBindingSlotError(tokenName(this.token));
183
249
  }
184
- return this.#reslot(updateSlotTag(this.slot, criterion), this.predicate);
250
+ return this.#reslot(withSlotCriterion(this.slot, criterion), this.predicate);
185
251
  }
186
252
  many() {
187
253
  this.#requireRegistered();
@@ -191,14 +257,11 @@ export class BindingChain {
191
257
  if (this.isMany) {
192
258
  return this;
193
259
  }
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;
260
+ // A provably-live binding takes membership in place; the registry only moves it out of the lone
261
+ // map. Otherwise `#commit` re-checks liveness and restores what the freed slot lets back in.
262
+ if (this.#isProvablyLive) {
263
+ this.#registration.registry.setMany(this.#binding);
264
+ this.#recordWrite();
202
265
  return this;
203
266
  }
204
267
  this.#commit(() => {
@@ -224,15 +287,13 @@ export class BindingChain {
224
287
  onActivation(fn) {
225
288
  this.#requireRegistered();
226
289
  this.activationHook = fn;
227
- this.#registration.registry.touch();
228
- this.#versionAfterLastWrite = this.#registration.registry.version;
290
+ this.#touchAndRecordWrite();
229
291
  return this;
230
292
  }
231
293
  onDeactivation(fn) {
232
294
  this.#requireRegistered();
233
295
  this.deactivationHook = fn;
234
- this.#registration.registry.touch();
235
- this.#versionAfterLastWrite = this.#registration.registry.version;
296
+ this.#touchAndRecordWrite();
236
297
  return this;
237
298
  }
238
299
  id() {
@@ -272,7 +333,13 @@ export class BindingChain {
272
333
  }
273
334
  const displaced = registry.add(registered);
274
335
  if (displaced !== undefined) {
275
- (this.#displacedByChain ??= []).push(displaced);
336
+ if (this.#registration.deactivateDisplaced !== undefined && rewrite === undefined) {
337
+ this.#registration.deactivateDisplaced(displaced);
338
+ }
339
+ else {
340
+ (this.#displacedByChain ??= []).push(displaced);
341
+ this.#registration.onDisplaced?.(displaced);
342
+ }
276
343
  }
277
344
  if (rewrite !== undefined && this.#displacedByChain !== undefined) {
278
345
  this.#restoreNonConflicting(this.#displacedByChain);
@@ -289,6 +356,7 @@ export class BindingChain {
289
356
  // the candidate stays parked.
290
357
  if (!this.#registration.registry.hasSlotOccupant(candidate)) {
291
358
  this.#registration.registry.add(candidate);
359
+ this.#registration.onRestored?.(candidate);
292
360
  displaced.splice(index, 1);
293
361
  }
294
362
  }
@@ -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
  *