@codefast/di 0.10.1 → 0.12.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 (88) hide show
  1. package/CHANGELOG.md +376 -0
  2. package/README.md +94 -17
  3. package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +7 -2
  4. package/dist/{ambient/active-container.js → ambient-container.js} +6 -1
  5. package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +27 -16
  6. package/dist/container/binding-chain.js +254 -0
  7. package/dist/container/container.d.ts +11 -2
  8. package/dist/container/container.js +293 -73
  9. package/dist/core/binding-builders.d.ts +114 -0
  10. package/dist/core/binding-declaration.d.ts +127 -0
  11. package/dist/core/binding-declaration.js +187 -0
  12. package/dist/core/binding.d.ts +62 -138
  13. package/dist/core/binding.js +54 -17
  14. package/dist/core/module.d.ts +8 -5
  15. package/dist/core/module.js +17 -3
  16. package/dist/core/registry.d.ts +17 -28
  17. package/dist/core/registry.js +92 -133
  18. package/dist/core/state-epoch.d.ts +18 -1
  19. package/dist/core/state-epoch.js +17 -0
  20. package/dist/core/tag.js +1 -1
  21. package/dist/decorators/inject.js +6 -5
  22. package/dist/decorators/injectable.js +4 -2
  23. package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +9 -3
  24. package/dist/decorators/metadata-record.d.ts +9 -0
  25. package/dist/decorators/metadata-record.js +20 -0
  26. package/dist/{errors/errors.d.ts → errors.d.ts} +84 -9
  27. package/dist/{errors/errors.js → errors.js} +101 -17
  28. package/dist/index.d.ts +14 -10
  29. package/dist/index.js +7 -6
  30. package/dist/injection/{resolve-options.js → dependency-slot.js} +6 -4
  31. package/dist/injection/descriptor.d.ts +1 -1
  32. package/dist/injection/descriptor.js +3 -7
  33. package/dist/introspection/dependency-graph.d.ts +8 -3
  34. package/dist/introspection/dependency-graph.js +48 -26
  35. package/dist/introspection/explanation.d.ts +101 -0
  36. package/dist/introspection/explanation.js +151 -0
  37. package/dist/introspection/graph-adapters/reactflow.js +6 -4
  38. package/dist/introspection/inspector.d.ts +9 -2
  39. package/dist/introspection/inspector.js +30 -28
  40. package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +9 -2
  41. package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +59 -30
  42. package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +3 -5
  43. package/dist/lifecycle/{scope-manager.js → scopes.js} +36 -29
  44. package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
  45. package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
  46. package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
  47. package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
  48. package/dist/metadata/types.js +1 -0
  49. package/dist/metadata/verify.d.ts +43 -0
  50. package/dist/metadata/verify.js +133 -0
  51. package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +5 -4
  52. package/dist/metadata/verifying-reader.js +53 -0
  53. package/dist/resolution/async-fan-out.d.ts +12 -0
  54. package/dist/resolution/async-fan-out.js +26 -0
  55. package/dist/resolution/cache/activation-need.d.ts +1 -2
  56. package/dist/resolution/cache/activation-need.js +16 -19
  57. package/dist/resolution/cache/class-introspector.d.ts +38 -36
  58. package/dist/resolution/cache/class-introspector.js +79 -159
  59. package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -9
  60. package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +70 -25
  61. package/dist/resolution/context.d.ts +16 -24
  62. package/dist/resolution/context.js +47 -56
  63. package/dist/resolution/path.d.ts +128 -0
  64. package/dist/resolution/path.js +167 -0
  65. package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +5 -4
  66. package/dist/resolution/plan/{plan-codegen.js → codegen.js} +58 -32
  67. package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
  68. package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +64 -24
  69. package/dist/resolution/resolver.d.ts +11 -12
  70. package/dist/resolution/resolver.js +573 -356
  71. package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
  72. package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
  73. package/dist/resolution/select/constraints.js +1 -1
  74. package/package.json +87 -67
  75. package/dist/container/binding-builders.js +0 -305
  76. package/dist/core/binding-scope.d.ts +0 -12
  77. package/dist/core/binding-scope.js +0 -12
  78. package/dist/metadata/verifying-metadata-reader.js +0 -31
  79. package/dist/resolution/path/resolution-path.d.ts +0 -93
  80. package/dist/resolution/path/resolution-path.js +0 -116
  81. /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
  82. /package/dist/decorators/{lifecycle-decorators.d.ts → lifecycle.d.ts} +0 -0
  83. /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
  84. /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
  85. /package/dist/{errors → introspection}/diagnostics.js +0 -0
  86. /package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +0 -0
  87. /package/dist/metadata/{metadata-keys.js → keys.js} +0 -0
  88. /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
@@ -1,305 +0,0 @@
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
- }
19
- // ── BindingChain ─────────────────────────────────────────────────────────────────────────────────────────────────────
20
- /**
21
- * The one builder behind `bind()` and every `to*()` return type — and the binding it registers.
22
- *
23
- * @remarks The binding fields come first, in the order every binding shares, and the chain's own
24
- * bookkeeping follows as private fields; `to*()` fills the fields in place and hands this object to
25
- * the registry, so a plain bind is one allocation. Refinements write the registered object: a scope
26
- * or hook in place, a slot or predicate through the registry so its indexes follow.
27
- *
28
- * @since 0.5.0-canary.8
29
- */
30
- export class BindingChain {
31
- // The binding. Every field, every kind, written in this order and nowhere else: one hidden class.
32
- kind = "constant";
33
- identifier = generateBindingId();
34
- inFlight = false;
35
- frame = undefined;
36
- instance = NO_INSTANCE;
37
- token;
38
- slot = DEFAULT_BINDING_SLOT;
39
- predicate = undefined;
40
- isMany = false;
41
- scope = "singleton";
42
- target = undefined;
43
- factory = undefined;
44
- deps = undefined;
45
- value = undefined;
46
- activationHook = undefined;
47
- deactivationHook = undefined;
48
- // The chain. Set by the first `to*()`, which is the only one a chain accepts.
49
- #isRegistered = false;
50
- // Allocated only by a chain that actually displaces something — most never do.
51
- #displacedByChain;
52
- // Registry version after this chain's last write — a mismatch means someone else wrote in between.
53
- #versionAfterLastWrite = -1;
54
- #registration;
55
- constructor(token, registration) {
56
- this.token = token;
57
- this.#registration = registration;
58
- }
59
- /** This object as the registry and the resolver see it: the value type erased once, here. */
60
- get #binding() {
61
- return this;
62
- }
63
- /** Loud failure for a refinement before `to*()`. */
64
- #requireRegistered() {
65
- if (!this.#isRegistered) {
66
- throw new ChainNotRegisteredError(tokenName(this.token));
67
- }
68
- }
69
- /** Loud failure for a second `to*()`, raised before the first one's fields can be overwritten. */
70
- #requireUnregistered() {
71
- if (this.#isRegistered) {
72
- throw new ChainAlreadyRegisteredError(tokenName(this.token));
73
- }
74
- }
75
- #register(kind, scope) {
76
- this.kind = kind;
77
- this.scope = scope;
78
- this.#isRegistered = true;
79
- this.#commit(undefined);
80
- return this;
81
- }
82
- // ── Registration ───────────────────────────────────────────────────────────────────────────────────────────────────
83
- to(type) {
84
- this.#requireUnregistered();
85
- this.target = type;
86
- return this.#register("class", "transient");
87
- }
88
- toSelf() {
89
- this.#requireUnregistered();
90
- if (typeof this.token !== "function") {
91
- throw new SelfBindingRequiresClassError(tokenName(this.token));
92
- }
93
- this.target = this.token;
94
- return this.#register("class", "transient");
95
- }
96
- toConstantValue(value) {
97
- this.#requireUnregistered();
98
- this.value = value;
99
- return this.#register("constant", "singleton");
100
- }
101
- toDynamic(factory) {
102
- this.#requireUnregistered();
103
- this.factory = factory;
104
- return this.#register("dynamic", "transient");
105
- }
106
- toDynamicAsync(factory) {
107
- this.#requireUnregistered();
108
- this.factory = factory;
109
- return this.#register("dynamic-async", "transient");
110
- }
111
- toResolved(factory, deps) {
112
- this.#requireUnregistered();
113
- this.factory = factory;
114
- this.deps = deps.map((dependency) => normalizeToDescriptor(dependency));
115
- return this.#register("resolved", "transient");
116
- }
117
- toResolvedAsync(factory, deps) {
118
- this.#requireUnregistered();
119
- this.factory = factory;
120
- this.deps = deps.map((dependency) => normalizeToDescriptor(dependency));
121
- return this.#register("resolved-async", "transient");
122
- }
123
- toAlias(target) {
124
- this.#requireUnregistered();
125
- this.target = target;
126
- return this.#register("alias", "transient");
127
- }
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
- }
147
- // Slot and predicate are what the registry indexes on, so a re-slot takes the binding out of the
148
- // registry, rewrites the two fields while it is out, and registers it again — same object, same id.
149
- #reslot(slot, predicate) {
150
- this.#requireRegistered();
151
- this.#commit(() => {
152
- this.slot = slot;
153
- this.predicate = predicate;
154
- // The frame reports the slot, so a resolve before this call memoized the previous one.
155
- clearBindingFrame(this.#binding);
156
- });
157
- return this;
158
- }
159
- #withScope(scope) {
160
- this.#requireRegistered();
161
- if (this.scope !== scope) {
162
- // An instance cached under the old scope must not survive the change — a later flip back
163
- // to that scope would resurrect it.
164
- this.#registration.scope.deleteSingleton(this.#binding);
165
- this.#registration.scope.deleteScoped(this.identifier);
166
- this.scope = scope;
167
- }
168
- // The frame reports the scope, so a resolve before this call memoized the previous one.
169
- clearBindingFrame(this.#binding);
170
- this.#touchAndRecordWrite();
171
- return this;
172
- }
173
- // SPEC calls a candidate a binding that passes *all* of a chain's predicates, and the chain type
174
- // reads as refinement, so a second `when()` narrows rather than replaces.
175
- when(predicate) {
176
- this.#requireRegistered();
177
- const previous = this.predicate;
178
- // The composite carries both sides' requirements, so validate() still sees them.
179
- const narrowed = previous === undefined
180
- ? predicate
181
- : mergingConstraintRequirements((ctx) => previous(ctx) && predicate(ctx), previous, predicate);
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();
186
- return this;
187
- }
188
- return this.#reslot(this.slot, narrowed);
189
- }
190
- whenNamed(name) {
191
- return this.whenTagged(slotName.of(name));
192
- }
193
- whenTagged(criterion) {
194
- this.#requireRegistered();
195
- if (this.isMany) {
196
- throw new ManyBindingSlotError(tokenName(this.token));
197
- }
198
- return this.#reslot(updateSlotTag(this.slot, criterion), this.predicate);
199
- }
200
- many() {
201
- this.#requireRegistered();
202
- if (this.slot.tags.length !== 0) {
203
- throw new ManyBindingSlotError(tokenName(this.token));
204
- }
205
- if (this.isMany) {
206
- return this;
207
- }
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();
213
- return this;
214
- }
215
- this.#commit(() => {
216
- this.isMany = true;
217
- });
218
- return this;
219
- }
220
- whenDefault() {
221
- // The default slot is what a fresh registration already has, so there is nothing to re-slot —
222
- // but an unregistered chain must fail here exactly as it does in every other refinement.
223
- this.#requireRegistered();
224
- return this;
225
- }
226
- singleton() {
227
- return this.#withScope("singleton");
228
- }
229
- transient() {
230
- return this.#withScope("transient");
231
- }
232
- scoped() {
233
- return this.#withScope("scoped");
234
- }
235
- onActivation(fn) {
236
- this.#requireRegistered();
237
- this.activationHook = fn;
238
- this.#touchAndRecordWrite();
239
- return this;
240
- }
241
- onDeactivation(fn) {
242
- this.#requireRegistered();
243
- this.deactivationHook = fn;
244
- this.#touchAndRecordWrite();
245
- return this;
246
- }
247
- id() {
248
- this.#requireRegistered();
249
- return this.identifier;
250
- }
251
- // ── Registry ───────────────────────────────────────────────────────────────────────────────────────────────────────
252
- /**
253
- * Registers this binding; with `rewrite` given, first takes the live binding out, rewrites it, and
254
- * registers it again.
255
- *
256
- * @remarks A `when*()` that follows `to*()` re-slots an already-live binding and can displace one
257
- * the final shape would never conflict with. Those stay parked in `#displacedByChain` until the
258
- * chain settles, then get restored.
259
- */
260
- #commit(rewrite) {
261
- const { registry, moduleBindingIds } = this.#registration;
262
- const registered = this.#binding;
263
- if (rewrite !== undefined) {
264
- // A registry someone else wrote since this chain's last write invalidates the parked
265
- // snapshot: restoring it could undo an unbind or shadow a newer binding.
266
- if (registry.version !== this.#versionAfterLastWrite) {
267
- this.#displacedByChain = undefined;
268
- }
269
- if (!registry.reslot(registered, rewrite)) {
270
- // The chain's binding is no longer live (unbound or displaced) — a refinement must not
271
- // resurrect it, so the chain goes inert against the registry.
272
- this.#displacedByChain = undefined;
273
- this.#versionAfterLastWrite = registry.version;
274
- return;
275
- }
276
- }
277
- else {
278
- // Each `to*()` starts its own registration, so anything a previous one displaced is not this
279
- // registration's to restore.
280
- this.#displacedByChain = undefined;
281
- }
282
- const displaced = registry.add(registered);
283
- if (displaced !== undefined) {
284
- (this.#displacedByChain ??= []).push(displaced);
285
- }
286
- if (rewrite !== undefined && this.#displacedByChain !== undefined) {
287
- this.#restoreNonConflicting(this.#displacedByChain);
288
- }
289
- if (rewrite === undefined && moduleBindingIds !== undefined) {
290
- moduleBindingIds.push(this.identifier);
291
- }
292
- this.#versionAfterLastWrite = registry.version;
293
- }
294
- #restoreNonConflicting(displaced) {
295
- for (let index = displaced.length - 1; index >= 0; index -= 1) {
296
- const candidate = displaced[index];
297
- // A restore must never displace: a slot that has been re-occupied keeps its occupant, and
298
- // the candidate stays parked.
299
- if (!this.#registration.registry.hasSlotOccupant(candidate)) {
300
- this.#registration.registry.add(candidate);
301
- displaced.splice(index, 1);
302
- }
303
- }
304
- }
305
- }
@@ -1,12 +0,0 @@
1
- import type { Binding } from "#core/binding";
2
- import type { BindingScope } from "#core/types";
3
- /**
4
- * The scope a binding resolves under.
5
- *
6
- * @remarks Every kind declares one — an alias declares `transient`, since it defers scoping to the
7
- * binding it points at — so this is a field read, kept as a named function because it is the
8
- * vocabulary validation and introspection speak.
9
- *
10
- * @since 0.3.16-canary.0
11
- */
12
- export declare function effectiveBindingScope(binding: Binding): BindingScope;
@@ -1,12 +0,0 @@
1
- /**
2
- * The scope a binding resolves under.
3
- *
4
- * @remarks Every kind declares one — an alias declares `transient`, since it defers scoping to the
5
- * binding it points at — so this is a field read, kept as a named function because it is the
6
- * vocabulary validation and introspection speak.
7
- *
8
- * @since 0.3.16-canary.0
9
- */
10
- export function effectiveBindingScope(binding) {
11
- return binding.scope;
12
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * Wraps a foreign {@link MetadataReader} so its answers are verified before anything dereferences
3
- * them.
4
- */
5
- import { defaultMetadataReader } from "#metadata/symbol-metadata-reader";
6
- import { verifyAccessorMetadata, verifyConstructorMetadata, verifyLifecycleMetadata, } from "#resolution/cache/class-introspector";
7
- // Wrapping a wrapper would stack a layer per child container, so each one is remembered.
8
- const verifyingReaders = new WeakSet();
9
- /**
10
- * The reader a container should hand its resolver: verified when it came from outside.
11
- *
12
- * @remarks The decorator reader writes the metadata it later reads, so there is nothing to check and
13
- * nothing to pay — a container that supplies no reader of its own is left on the same code path it
14
- * has always taken. A supplied reader is a claim, and only its callers can be charged for checking.
15
- *
16
- * @since 0.6.0
17
- */
18
- export function verifyingMetadataReader(reader) {
19
- if (reader === defaultMetadataReader || verifyingReaders.has(reader)) {
20
- return reader;
21
- }
22
- const verifying = {
23
- getConstructorMetadata: (target) => verifyConstructorMetadata(reader, target),
24
- getLifecycleMetadata: (target) => verifyLifecycleMetadata(reader, target),
25
- ...(reader.getAccessorMetadata === undefined
26
- ? {}
27
- : { getAccessorMetadata: (target) => verifyAccessorMetadata(reader, target) }),
28
- };
29
- verifyingReaders.add(verifying);
30
- return verifying;
31
- }
@@ -1,93 +0,0 @@
1
- import type { BindingIdentifier, ResolutionFrame } from "#core/types";
2
- /**
3
- * Where the cycle check switches from a linear frame scan to an attached Set.
4
- *
5
- * @remarks Measured rather than guessed: below this depth the linear scan wins, above it the Set does.
6
- *
7
- * @since 0.5.0-canary.7
8
- */
9
- export declare const RESOLUTION_SET_THRESHOLD = 32;
10
- /**
11
- * The token names along a stack plus the frame that closed the cycle — built only to throw.
12
- *
13
- * @since 0.6.0
14
- */
15
- export declare function cycleNamesOf(resolutionStack: ReadonlyArray<ResolutionFrame>, closingName: string): Array<string>;
16
- /**
17
- * Marks a level as in-flight on this stack, throwing if its binding is already an ancestor.
18
- *
19
- * @remarks The check keys on binding identity — two distinct tokens may share a display name — and
20
- * the names an error prints are derived from the frames at the throw site. Unmark by popping the
21
- * stack plus `set?.delete(frame.bindingId)`. Sync only — the async lane never removes an entry, so
22
- * it extends a branch instead; see {@link extendResolutionBranch}.
23
- *
24
- * @returns the membership set once the stack is deep enough to carry one, else `undefined`.
25
- *
26
- * @since 0.5.0-canary.7
27
- */
28
- export declare function enterResolutionPath(resolutionStack: Array<ResolutionFrame>, frame: ResolutionFrame): Set<BindingIdentifier> | undefined;
29
- declare const BRANCH_BRAND: unique symbol;
30
- /**
31
- * A resolution stack one async branch owns, so appending to it cannot disturb another branch.
32
- *
33
- * @remarks Only {@link extendResolutionBranch} mints one. That is what makes "may this lane append
34
- * to this array" a question the compiler answers instead of a rule in a doc — a sync frame's stack,
35
- * which that frame will pop, is a plain `Array<ResolutionFrame>` and cannot reach a level that owns
36
- * its own.
37
- *
38
- * @since 0.5.0-canary.9
39
- */
40
- export type OwnedBranchStack = Array<ResolutionFrame> & {
41
- readonly [BRANCH_BRAND]: true;
42
- };
43
- declare const BRANCH_DEPTH_BRAND: unique symbol;
44
- /**
45
- * How many leading entries of a stack belong to one async branch.
46
- *
47
- * @remarks Branded so a bare number cannot be passed: a depth from anywhere but this branch silently
48
- * re-parents a level.
49
- *
50
- * @since 0.5.0-canary.9
51
- */
52
- export type OwnedBranchDepth = number & {
53
- readonly [BRANCH_DEPTH_BRAND]: true;
54
- };
55
- /**
56
- * A stack no async branch owns yet, so its first extension must copy rather than append.
57
- *
58
- * @since 0.5.0-canary.9
59
- */
60
- export declare const UNOWNED_BRANCH = -1;
61
- /**
62
- * How far into a stack one extension may reach: this branch's own depth, or nobody's.
63
- *
64
- * @remarks A union rather than a sentinel hidden inside the branded number, so the two cases are
65
- * visible at every signature that takes one and `=== UNOWNED_BRANCH` narrows to the owned case.
66
- *
67
- * @since 0.5.0-canary.9
68
- */
69
- export type BranchDepth = OwnedBranchDepth | typeof UNOWNED_BRANCH;
70
- /**
71
- * The depth a chain's first level extends from, over an array its caller just minted.
72
- *
73
- * @since 0.5.0-canary.9
74
- */
75
- export declare const ROOT_BRANCH: OwnedBranchDepth;
76
- /**
77
- * A branch's own depth: the length its stack had when this level took it.
78
- *
79
- * @since 0.5.0-canary.9
80
- */
81
- export declare function branchDepthOf(branch: OwnedBranchStack): OwnedBranchDepth;
82
- /**
83
- * Extends one branch of an append-only stack, throwing if the binding is already an ancestor.
84
- *
85
- * @remarks The check compares binding ids on the frames; the names an error prints are derived at
86
- * the throw site. Appends in place while this branch still owns the next slot, and copies its own
87
- * prefix once a sibling has claimed it. Nothing is ever removed, so no async level has to observe
88
- * its own settlement to unwind.
89
- *
90
- * @since 0.5.0-canary.9
91
- */
92
- export declare function extendResolutionBranch(resolutionStack: Array<ResolutionFrame>, branchDepth: BranchDepth, frame: ResolutionFrame): OwnedBranchStack;
93
- export {};
@@ -1,116 +0,0 @@
1
- /** Cycle-detection bookkeeping carried on the resolution stack itself. */
2
- import { CircularDependencyError } from "#errors/errors";
3
- const RESOLUTION_SET_KEY = Symbol("di:resolution-set");
4
- /**
5
- * Where the cycle check switches from a linear frame scan to an attached Set.
6
- *
7
- * @remarks Measured rather than guessed: below this depth the linear scan wins, above it the Set does.
8
- *
9
- * @since 0.5.0-canary.7
10
- */
11
- export const RESOLUTION_SET_THRESHOLD = 32;
12
- /**
13
- * The token names along a stack plus the frame that closed the cycle — built only to throw.
14
- *
15
- * @since 0.6.0
16
- */
17
- export function cycleNamesOf(resolutionStack, closingName) {
18
- const names = new Array(resolutionStack.length + 1);
19
- for (let index = 0; index < resolutionStack.length; index += 1) {
20
- names[index] = resolutionStack[index].tokenName;
21
- }
22
- names[resolutionStack.length] = closingName;
23
- return names;
24
- }
25
- /**
26
- * Marks a level as in-flight on this stack, throwing if its binding is already an ancestor.
27
- *
28
- * @remarks The check keys on binding identity — two distinct tokens may share a display name — and
29
- * the names an error prints are derived from the frames at the throw site. Unmark by popping the
30
- * stack plus `set?.delete(frame.bindingId)`. Sync only — the async lane never removes an entry, so
31
- * it extends a branch instead; see {@link extendResolutionBranch}.
32
- *
33
- * @returns the membership set once the stack is deep enough to carry one, else `undefined`.
34
- *
35
- * @since 0.5.0-canary.7
36
- */
37
- export function enterResolutionPath(resolutionStack, frame) {
38
- const stackWithSet = resolutionStack;
39
- let resolutionSet = stackWithSet[RESOLUTION_SET_KEY];
40
- // A live set mirrors the stack exactly, so a size that disagrees means it is holding ids of
41
- // frames that unwound: the ones already on the stack when it attached were handed no set to
42
- // delete from. Dropped rather than repaired, because the next deep frame rebuilds it.
43
- if (resolutionSet !== undefined && resolutionSet.size !== resolutionStack.length) {
44
- resolutionSet = undefined;
45
- stackWithSet[RESOLUTION_SET_KEY] = undefined;
46
- }
47
- if (resolutionSet === undefined && resolutionStack.length >= RESOLUTION_SET_THRESHOLD) {
48
- resolutionSet = new Set();
49
- for (let index = 0; index < resolutionStack.length; index += 1) {
50
- resolutionSet.add(resolutionStack[index].bindingId);
51
- }
52
- stackWithSet[RESOLUTION_SET_KEY] = resolutionSet;
53
- }
54
- if (resolutionSet === undefined) {
55
- for (let index = 0; index < resolutionStack.length; index += 1) {
56
- if (resolutionStack[index].bindingId === frame.bindingId) {
57
- throw new CircularDependencyError(cycleNamesOf(resolutionStack, frame.tokenName));
58
- }
59
- }
60
- }
61
- else if (resolutionSet.has(frame.bindingId)) {
62
- throw new CircularDependencyError(cycleNamesOf(resolutionStack, frame.tokenName));
63
- }
64
- resolutionStack.push(frame);
65
- resolutionSet?.add(frame.bindingId);
66
- return resolutionSet;
67
- }
68
- /**
69
- * A stack no async branch owns yet, so its first extension must copy rather than append.
70
- *
71
- * @since 0.5.0-canary.9
72
- */
73
- export const UNOWNED_BRANCH = -1;
74
- /**
75
- * The depth a chain's first level extends from, over an array its caller just minted.
76
- *
77
- * @since 0.5.0-canary.9
78
- */
79
- export const ROOT_BRANCH = 0;
80
- /**
81
- * A branch's own depth: the length its stack had when this level took it.
82
- *
83
- * @since 0.5.0-canary.9
84
- */
85
- export function branchDepthOf(branch) {
86
- return branch.length;
87
- }
88
- /**
89
- * Extends one branch of an append-only stack, throwing if the binding is already an ancestor.
90
- *
91
- * @remarks The check compares binding ids on the frames; the names an error prints are derived at
92
- * the throw site. Appends in place while this branch still owns the next slot, and copies its own
93
- * prefix once a sibling has claimed it. Nothing is ever removed, so no async level has to observe
94
- * its own settlement to unwind.
95
- *
96
- * @since 0.5.0-canary.9
97
- */
98
- export function extendResolutionBranch(resolutionStack, branchDepth, frame) {
99
- const depth = branchDepth === UNOWNED_BRANCH ? resolutionStack.length : branchDepth;
100
- for (let index = 0; index < depth; index += 1) {
101
- if (resolutionStack[index].bindingId === frame.bindingId) {
102
- throw new CircularDependencyError(cycleNamesOf(resolutionStack.slice(0, depth), frame.tokenName));
103
- }
104
- }
105
- // An unowned array belongs to a sync frame that will pop it, or carries a membership Set this
106
- // lane cannot keep true; copying is what makes the branch's own appends safe.
107
- if (branchDepth === resolutionStack.length) {
108
- // The sole mint: appending in place needs a depth that came from a branch already owned, or
109
- // ROOT_BRANCH over an array its caller minted for this chain alone.
110
- resolutionStack.push(frame);
111
- return resolutionStack;
112
- }
113
- const branch = resolutionStack.slice(0, depth);
114
- branch.push(frame);
115
- return branch;
116
- }
File without changes
File without changes
File without changes
File without changes