@codefast/di 0.11.0 → 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.
- package/CHANGELOG.md +111 -0
- package/README.md +26 -12
- package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +21 -28
- package/dist/container/{binding-builders.js → binding-chain.js} +53 -163
- package/dist/container/container.d.ts +11 -2
- package/dist/container/container.js +107 -64
- package/dist/core/binding-builders.d.ts +114 -0
- package/dist/core/binding-declaration.d.ts +7 -0
- package/dist/core/binding-declaration.js +3 -2
- package/dist/core/binding.d.ts +21 -149
- package/dist/core/binding.js +15 -25
- package/dist/core/module.d.ts +1 -1
- package/dist/core/registry.d.ts +9 -24
- package/dist/core/registry.js +30 -155
- package/dist/core/tag.js +1 -1
- package/dist/decorators/inject.js +5 -5
- package/dist/decorators/injectable.js +2 -2
- package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +3 -3
- package/dist/decorators/{decorator-metadata.js → metadata-record.js} +1 -1
- package/dist/{errors/errors.d.ts → errors.d.ts} +7 -6
- package/dist/{errors/errors.js → errors.js} +8 -7
- package/dist/index.d.ts +12 -10
- package/dist/index.js +6 -6
- package/dist/injection/descriptor.d.ts +1 -1
- package/dist/introspection/dependency-graph.d.ts +1 -1
- package/dist/introspection/dependency-graph.js +3 -4
- package/dist/introspection/explanation.d.ts +101 -0
- package/dist/introspection/explanation.js +151 -0
- package/dist/introspection/inspector.d.ts +9 -2
- package/dist/introspection/inspector.js +25 -20
- package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +9 -2
- package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +45 -28
- package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +3 -5
- package/dist/lifecycle/{scope-manager.js → scopes.js} +8 -19
- package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
- package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
- package/dist/metadata/types.js +1 -0
- package/dist/metadata/verify.d.ts +43 -0
- package/dist/metadata/verify.js +133 -0
- package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +1 -1
- package/dist/metadata/{verifying-metadata-reader.js → verifying-reader.js} +2 -2
- package/dist/resolution/cache/activation-need.d.ts +1 -1
- package/dist/resolution/cache/activation-need.js +5 -1
- package/dist/resolution/cache/class-introspector.d.ts +27 -36
- package/dist/resolution/cache/class-introspector.js +61 -159
- package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -2
- package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +40 -8
- package/dist/resolution/context.d.ts +1 -1
- package/dist/resolution/context.js +1 -1
- package/dist/resolution/{path/resolution-path.js → path.js} +1 -1
- package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +2 -4
- package/dist/resolution/plan/{plan-codegen.js → codegen.js} +2 -4
- package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
- package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +4 -4
- package/dist/resolution/resolver.d.ts +7 -7
- package/dist/resolution/resolver.js +308 -121
- package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
- package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
- package/dist/resolution/select/constraints.js +1 -1
- package/package.json +79 -71
- package/dist/core/binding-scope.d.ts +0 -12
- package/dist/core/binding-scope.js +0 -12
- /package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +0 -0
- /package/dist/{ambient/active-container.js → ambient-container.js} +0 -0
- /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
- /package/dist/decorators/{lifecycle-decorators.d.ts → lifecycle.d.ts} +0 -0
- /package/dist/decorators/{decorator-metadata.d.ts → metadata-record.d.ts} +0 -0
- /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
- /package/dist/injection/{resolve-options.js → dependency-slot.js} +0 -0
- /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
- /package/dist/{errors → introspection}/diagnostics.js +0 -0
- /package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +0 -0
- /package/dist/metadata/{metadata-keys.js → keys.js} +0 -0
- /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
- /package/dist/resolution/{path/resolution-path.d.ts → path.d.ts} +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { BindToBuilder } from "#core/binding";
|
|
1
|
+
import type { BindToBuilder } from "#core/binding-builders";
|
|
2
2
|
import type { AsyncModule, SyncModule } from "#core/module";
|
|
3
3
|
import type { Token } from "#core/token";
|
|
4
4
|
import type { ActivationHandler, BindingIdentifier, Constructor, DeactivationHandler, ResolveOptions } from "#core/types";
|
|
5
5
|
import type { AutoRegisterRegistry } from "#decorators/injectable";
|
|
6
6
|
import type { ContainerGraphJson, GraphOptions } from "#introspection/dependency-graph";
|
|
7
|
+
import type { ExplainOptions, ResolutionExplanation } from "#introspection/explanation";
|
|
7
8
|
import type { BindingSnapshot, ContainerSnapshot } from "#introspection/inspector";
|
|
8
|
-
import type { MetadataReader } from "#metadata/
|
|
9
|
+
import type { MetadataReader } from "#metadata/types";
|
|
9
10
|
/**
|
|
10
11
|
* The public surface: binding, resolving, modules, lifecycle hooks, child scopes, and disposal.
|
|
11
12
|
*
|
|
@@ -42,6 +43,14 @@ export interface Container {
|
|
|
42
43
|
hasOwn<Names extends string = string>(token: Token<unknown, Names> | Constructor, options?: NoInfer<ResolveOptions<Names>>): boolean;
|
|
43
44
|
lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot>;
|
|
44
45
|
inspect(): ContainerSnapshot;
|
|
46
|
+
/**
|
|
47
|
+
* Explains why a request selects the binding it does, reading each registry the way `resolve` does and deciding
|
|
48
|
+
* by the same rules, without instantiating anything.
|
|
49
|
+
*
|
|
50
|
+
* @remarks A `when()` predicate runs here as it would in `resolve`. `options.ancestors` names the resolutions the
|
|
51
|
+
* request is nested in, so a predicate that reads the parent sees the one it would.
|
|
52
|
+
*/
|
|
53
|
+
explain<Names extends string = string>(token: Token<unknown, Names> | Constructor, options?: NoInfer<ExplainOptions<Names>>): ResolutionExplanation;
|
|
45
54
|
generateDependencyGraph(options?: GraphOptions): ContainerGraphJson;
|
|
46
55
|
}
|
|
47
56
|
/**
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { BindingChain } from "#container/binding-
|
|
2
|
-
import { NO_INSTANCE } from "#core/binding";
|
|
3
|
-
import { effectiveBindingScope } from "#core/binding-scope";
|
|
1
|
+
import { BindingChain } from "#container/binding-chain";
|
|
2
|
+
import { effectiveBindingScope, NO_INSTANCE } from "#core/binding";
|
|
4
3
|
import { constraintRequirementsOf } from "#core/constraint-requirement";
|
|
5
4
|
import { getOrInsert, getOrInsertComputed } from "#core/map-upsert";
|
|
6
5
|
import { isSyncModule, MODULE_SETUP } from "#core/module";
|
|
7
6
|
import { BindingRegistry } from "#core/registry";
|
|
8
7
|
import { advanceDisposeEpoch, disposeEpochRef } from "#core/state-epoch";
|
|
9
8
|
import { tokenName } from "#core/token";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#injection/resolve-options";
|
|
9
|
+
import { AsyncModuleLoadError, CircularDependencyError, DisposedContainerError, InternalError, RebindUnboundTokenError, ScopeViolationError, SyncDisposalNotSupportedError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#errors";
|
|
10
|
+
import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#injection/dependency-slot";
|
|
13
11
|
import { buildDependencyGraph } from "#introspection/dependency-graph";
|
|
12
|
+
import { RESOLUTION_DIAGNOSTICS } from "#introspection/diagnostics";
|
|
13
|
+
import { explainRequest } from "#introspection/explanation";
|
|
14
14
|
import { Inspector } from "#introspection/inspector";
|
|
15
|
-
import { LifecycleManager } from "#lifecycle/
|
|
16
|
-
import { ScopeManager } from "#lifecycle/
|
|
17
|
-
import { MetadataReaderToken } from "#metadata/
|
|
18
|
-
import { defaultMetadataReader } from "#metadata/symbol-
|
|
19
|
-
import { verifyingMetadataReader } from "#metadata/verifying-
|
|
20
|
-
import { ROOT_BRANCH } from "#resolution/path
|
|
15
|
+
import { LifecycleManager } from "#lifecycle/hooks";
|
|
16
|
+
import { ScopeManager } from "#lifecycle/scopes";
|
|
17
|
+
import { MetadataReaderToken } from "#metadata/reader-token";
|
|
18
|
+
import { defaultMetadataReader } from "#metadata/symbol-reader";
|
|
19
|
+
import { verifyingMetadataReader } from "#metadata/verifying-reader";
|
|
20
|
+
import { ROOT_BRANCH } from "#resolution/path";
|
|
21
21
|
import { DependencyResolver } from "#resolution/resolver";
|
|
22
22
|
/** Whether a requirement's name is declared — on its token when it names one, on any token otherwise. */
|
|
23
23
|
function isSlotNameDeclared(declared, requirement) {
|
|
@@ -76,6 +76,8 @@ class DefaultContainer {
|
|
|
76
76
|
// unload of its module drains it, and dispose runs a constant's hook, which neither the registry nor the cache holds.
|
|
77
77
|
#orphanedBindings;
|
|
78
78
|
constructor(parent, options) {
|
|
79
|
+
// oxlint-disable-next-line typescript/unbound-method -- installed as this instance's own method, so `this` is it
|
|
80
|
+
this.resolve = parent === undefined ? DefaultContainer.#rootResolve : DefaultContainer.#childResolve;
|
|
79
81
|
this.#parent = parent;
|
|
80
82
|
this.#registry = new BindingRegistry();
|
|
81
83
|
this.#scope = new ScopeManager(parent !== undefined);
|
|
@@ -152,13 +154,10 @@ class DefaultContainer {
|
|
|
152
154
|
onDisplaced: (binding) => {
|
|
153
155
|
this.#recordDisplaced(binding);
|
|
154
156
|
},
|
|
155
|
-
onRestored: (binding) => {
|
|
156
|
-
this.#orphanedBindings?.delete(binding.identifier);
|
|
157
|
-
},
|
|
158
157
|
});
|
|
159
158
|
}
|
|
160
|
-
// A displaced binding holding a cached instance or owing a constant's deactivation is remembered
|
|
161
|
-
//
|
|
159
|
+
// A displaced binding holding a cached instance or owing a constant's deactivation is remembered until
|
|
160
|
+
// dispose, an unbind of its id or its module's unload; every other displaced binding is dropped.
|
|
162
161
|
#recordDisplaced(binding) {
|
|
163
162
|
if (binding.instance !== NO_INSTANCE || this.#owesConstantDeactivation(binding)) {
|
|
164
163
|
(this.#orphanedBindings ??= new Map()).set(binding.identifier, binding);
|
|
@@ -167,18 +166,17 @@ class DefaultContainer {
|
|
|
167
166
|
/**
|
|
168
167
|
* One registration per module load, holding that module's id list directly.
|
|
169
168
|
*
|
|
170
|
-
* @remarks It borrows the container's own displacement
|
|
171
|
-
* displaces is
|
|
169
|
+
* @remarks It borrows the container's own displacement callback, so a constant a module's bind
|
|
170
|
+
* displaces is kept for dispose exactly as one displaced by `bind()` is.
|
|
172
171
|
*/
|
|
173
172
|
#moduleRegistration(moduleRef) {
|
|
174
173
|
this.#moduleBindingIds ??= new Map();
|
|
175
|
-
const { onDisplaced
|
|
174
|
+
const { onDisplaced } = this.#ownRegistration();
|
|
176
175
|
return {
|
|
177
176
|
registry: this.#registry,
|
|
178
177
|
scope: this.#scope,
|
|
179
178
|
moduleBindingIds: getOrInsert(this.#moduleBindingIds, moduleRef, []),
|
|
180
179
|
onDisplaced,
|
|
181
|
-
onRestored,
|
|
182
180
|
};
|
|
183
181
|
}
|
|
184
182
|
#createBindToBuilder(token, registration = this.#ownRegistration()) {
|
|
@@ -198,13 +196,8 @@ class DefaultContainer {
|
|
|
198
196
|
// re-index the token's binding list once per binding.
|
|
199
197
|
return this.#drainSingletons(this.#registry.removeByToken(tokenOrId));
|
|
200
198
|
}
|
|
201
|
-
/**
|
|
202
|
-
* Retires an id the registry no longer holds, returning the displaced binding still owed a teardown.
|
|
203
|
-
*
|
|
204
|
-
* @remarks Counts as a registry write, so no chain holding the binding parked can restore it.
|
|
205
|
-
*/
|
|
199
|
+
/** Retires an id the registry no longer holds, returning the displaced binding still owed a teardown. */
|
|
206
200
|
#retireNotLive(id) {
|
|
207
|
-
this.#registry.touch();
|
|
208
201
|
const orphaned = this.#orphanedBindings?.get(id);
|
|
209
202
|
if (orphaned !== undefined) {
|
|
210
203
|
this.#orphanedBindings.delete(id);
|
|
@@ -217,18 +210,22 @@ class DefaultContainer {
|
|
|
217
210
|
// ever cached — the hot-swap shape — owes no deactivation and hands the shared empty list back.
|
|
218
211
|
let pairs;
|
|
219
212
|
for (let index = 0; index < bindings.length; index += 1) {
|
|
220
|
-
|
|
221
|
-
if (binding.instance !== NO_INSTANCE) {
|
|
222
|
-
(pairs ??= []).push(binding, binding.instance);
|
|
223
|
-
this.#scope.deleteSingleton(binding);
|
|
224
|
-
}
|
|
225
|
-
else if (this.#owesConstantDeactivation(binding)) {
|
|
226
|
-
(pairs ??= []).push(binding, binding.value);
|
|
227
|
-
}
|
|
228
|
-
this.#scope.deleteScoped(binding.identifier);
|
|
213
|
+
pairs = this.#drainSingleton(bindings[index], pairs);
|
|
229
214
|
}
|
|
230
215
|
return pairs ?? NO_DEACTIVATION_PAIRS;
|
|
231
216
|
}
|
|
217
|
+
/** One binding's share of {@link DefaultContainer.#drainSingletons}, allocating `pairs` on the first pair owed. */
|
|
218
|
+
#drainSingleton(binding, pairs) {
|
|
219
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
220
|
+
(pairs ??= []).push(binding, binding.instance);
|
|
221
|
+
this.#scope.deleteSingleton(binding);
|
|
222
|
+
}
|
|
223
|
+
else if (this.#owesConstantDeactivation(binding)) {
|
|
224
|
+
(pairs ??= []).push(binding, binding.value);
|
|
225
|
+
}
|
|
226
|
+
this.#scope.deleteScoped(binding.scopedCacheKey);
|
|
227
|
+
return pairs;
|
|
228
|
+
}
|
|
232
229
|
/**
|
|
233
230
|
* Whether a constant still owes its deactivation.
|
|
234
231
|
*
|
|
@@ -246,10 +243,11 @@ class DefaultContainer {
|
|
|
246
243
|
return;
|
|
247
244
|
}
|
|
248
245
|
const reader = this.#getMetadataReader();
|
|
246
|
+
const lifecycle = this.#lifecycle;
|
|
249
247
|
let errors;
|
|
250
248
|
for (let index = 0; index < pairs.length; index += 2) {
|
|
251
249
|
try {
|
|
252
|
-
|
|
250
|
+
lifecycle.runDeactivationSync(pairs[index], pairs[index + 1], reader);
|
|
253
251
|
}
|
|
254
252
|
catch (error) {
|
|
255
253
|
(errors ??= []).push(error);
|
|
@@ -264,10 +262,11 @@ class DefaultContainer {
|
|
|
264
262
|
return;
|
|
265
263
|
}
|
|
266
264
|
const reader = this.#getMetadataReader();
|
|
265
|
+
const lifecycle = this.#lifecycle;
|
|
267
266
|
let errors;
|
|
268
267
|
for (let index = 0; index < pairs.length; index += 2) {
|
|
269
268
|
try {
|
|
270
|
-
await
|
|
269
|
+
await lifecycle.runDeactivation(pairs[index], pairs[index + 1], reader);
|
|
271
270
|
}
|
|
272
271
|
catch (error) {
|
|
273
272
|
(errors ??= []).push(error);
|
|
@@ -317,14 +316,18 @@ class DefaultContainer {
|
|
|
317
316
|
return new BindingChain(token, this.#rebindRegistration());
|
|
318
317
|
}
|
|
319
318
|
#displacingRegistration;
|
|
320
|
-
/** A registration whose displaced binding is deactivated instead of
|
|
319
|
+
/** A registration whose displaced binding is deactivated on the spot instead of kept for dispose, for `rebind`. */
|
|
321
320
|
#rebindRegistration() {
|
|
322
321
|
return (this.#displacingRegistration ??= {
|
|
323
322
|
registry: this.#registry,
|
|
324
323
|
scope: this.#scope,
|
|
325
324
|
moduleBindingIds: undefined,
|
|
325
|
+
// One binding at a time, so the hot-swap rebind that owes nothing allocates nothing.
|
|
326
326
|
deactivateDisplaced: (binding) => {
|
|
327
|
-
this.#
|
|
327
|
+
const pairs = this.#drainSingleton(binding, undefined);
|
|
328
|
+
if (pairs !== undefined) {
|
|
329
|
+
this.#deactivatePairsSync(pairs);
|
|
330
|
+
}
|
|
328
331
|
},
|
|
329
332
|
});
|
|
330
333
|
}
|
|
@@ -530,9 +533,10 @@ class DefaultContainer {
|
|
|
530
533
|
this.#assertNotDisposed();
|
|
531
534
|
this.#lifecycle.registerDeactivation(token, handler);
|
|
532
535
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
+
static #rootResolve(token, options) {
|
|
537
|
+
if (this.#disposed) {
|
|
538
|
+
throw new DisposedContainerError();
|
|
539
|
+
}
|
|
536
540
|
const rootStack = this.#resolver.rootStack;
|
|
537
541
|
// A resolve already holding the shared stack means this one is nested; it mints its own.
|
|
538
542
|
if (rootStack.length !== 0) {
|
|
@@ -545,8 +549,28 @@ class DefaultContainer {
|
|
|
545
549
|
}
|
|
546
550
|
return this.#resolver.resolve(token, options, rootStack);
|
|
547
551
|
}
|
|
552
|
+
// The root's body again behind the child's check, so neither role's compiled resolve carries the other's path.
|
|
553
|
+
static #childResolve(token, options) {
|
|
554
|
+
this.#assertChainLive();
|
|
555
|
+
const rootStack = this.#resolver.rootStack;
|
|
556
|
+
if (rootStack.length !== 0) {
|
|
557
|
+
return options === undefined
|
|
558
|
+
? this.#resolver.resolveFromContext(token, [])
|
|
559
|
+
: this.#resolver.resolve(token, options, []);
|
|
560
|
+
}
|
|
561
|
+
if (options === undefined) {
|
|
562
|
+
return this.#resolver.resolveFromContext(token, rootStack);
|
|
563
|
+
}
|
|
564
|
+
return this.#resolver.resolve(token, options, rootStack);
|
|
565
|
+
}
|
|
548
566
|
resolveAsync(token, options) {
|
|
549
|
-
this
|
|
567
|
+
// Not `async`: the guard's throw becomes the rejection this method promises.
|
|
568
|
+
try {
|
|
569
|
+
this.#assertNotDisposed();
|
|
570
|
+
}
|
|
571
|
+
catch (disposedError) {
|
|
572
|
+
return Promise.reject(disposedError);
|
|
573
|
+
}
|
|
550
574
|
if (options === undefined) {
|
|
551
575
|
return this.#resolver.resolveAsyncFromRoot(token);
|
|
552
576
|
}
|
|
@@ -560,7 +584,12 @@ class DefaultContainer {
|
|
|
560
584
|
: this.#resolver.resolveOptional(token, options, []);
|
|
561
585
|
}
|
|
562
586
|
resolveOptionalAsync(token, options) {
|
|
563
|
-
|
|
587
|
+
try {
|
|
588
|
+
this.#assertNotDisposed();
|
|
589
|
+
}
|
|
590
|
+
catch (disposedError) {
|
|
591
|
+
return Promise.reject(disposedError);
|
|
592
|
+
}
|
|
564
593
|
return this.#resolver.resolveOptionalAsync(token, options, [], ROOT_BRANCH);
|
|
565
594
|
}
|
|
566
595
|
resolveAll(token, options) {
|
|
@@ -574,7 +603,12 @@ class DefaultContainer {
|
|
|
574
603
|
: this.#resolver.resolveAll(token, options, []);
|
|
575
604
|
}
|
|
576
605
|
resolveAllAsync(token, options) {
|
|
577
|
-
|
|
606
|
+
try {
|
|
607
|
+
this.#assertNotDisposed();
|
|
608
|
+
}
|
|
609
|
+
catch (disposedError) {
|
|
610
|
+
return Promise.reject(disposedError);
|
|
611
|
+
}
|
|
578
612
|
if (options === undefined) {
|
|
579
613
|
return this.#resolver.resolveRootCollectionAsync(token);
|
|
580
614
|
}
|
|
@@ -583,7 +617,10 @@ class DefaultContainer {
|
|
|
583
617
|
// ── Child ──────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
584
618
|
createChild() {
|
|
585
619
|
this.#assertNotDisposed();
|
|
586
|
-
|
|
620
|
+
const child = new DefaultContainer(this);
|
|
621
|
+
// Born confirmed live: the parent's chain was checked just above, so the child's first read skips the walk.
|
|
622
|
+
child.#liveGeneration = disposeEpochRef.value;
|
|
623
|
+
return child;
|
|
587
624
|
}
|
|
588
625
|
// ── Dispose ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
589
626
|
dispose() {
|
|
@@ -879,33 +916,39 @@ class DefaultContainer {
|
|
|
879
916
|
this.#assertNotDisposed();
|
|
880
917
|
return this.#getInspector().inspect();
|
|
881
918
|
}
|
|
919
|
+
explain(token, options) {
|
|
920
|
+
this.#assertNotDisposed();
|
|
921
|
+
return explainRequest([this.#registry, ...this.#ancestorRegistries()], token, options);
|
|
922
|
+
}
|
|
882
923
|
generateDependencyGraph(options) {
|
|
883
924
|
this.#assertNotDisposed();
|
|
884
|
-
|
|
925
|
+
return buildDependencyGraph(this.#registry, this.#getMetadataReader(), options, this.#ancestorRegistries());
|
|
926
|
+
}
|
|
927
|
+
/** The registries above this container, nearest first — the order every chain walk reads them in. */
|
|
928
|
+
#ancestorRegistries() {
|
|
929
|
+
const registries = [];
|
|
885
930
|
for (let ancestor = this.#parent; ancestor !== undefined; ancestor = ancestor.#parent) {
|
|
886
|
-
|
|
931
|
+
registries.push(ancestor.#registry);
|
|
887
932
|
}
|
|
888
|
-
return
|
|
933
|
+
return registries;
|
|
889
934
|
}
|
|
890
935
|
// ── Internal ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
891
|
-
//
|
|
892
|
-
//
|
|
893
|
-
// so an epoch unchanged since we last confirmed the chain live means it is still live. A moved epoch
|
|
894
|
-
// drops to the cold refresh, which walks self and ancestors and either re-confirms or throws.
|
|
895
|
-
// Hot guard. A root's whole answer is its own `#disposed`, kept the one field read it always was —
|
|
896
|
-
// the epoch is never read on the root path. A child adds a call-free epoch compare: an unchanged
|
|
897
|
-
// epoch means no container anywhere has been disposed since we last confirmed the chain live, so the
|
|
898
|
-
// ancestors need no walk; a moved epoch drops to the cold refresh that walks them and re-confirms or throws.
|
|
936
|
+
// Two field reads and no epoch on a live root. This and `#assertChainLive` each stay under V8's small-function
|
|
937
|
+
// size, so every entry point inlines both outright; the walk and the throw sit in `#confirmChainLive`.
|
|
899
938
|
#assertNotDisposed() {
|
|
900
|
-
if (this.#disposed) {
|
|
901
|
-
|
|
939
|
+
if (this.#disposed || this.#parent !== undefined) {
|
|
940
|
+
this.#assertChainLive();
|
|
902
941
|
}
|
|
903
|
-
|
|
904
|
-
|
|
942
|
+
}
|
|
943
|
+
// An unchanged dispose epoch means no container anywhere closed since this chain was last confirmed live;
|
|
944
|
+
// every dispose advances it, this container's own included, so a disposed container always reaches the walk.
|
|
945
|
+
#assertChainLive() {
|
|
946
|
+
if (disposeEpochRef.value !== this.#liveGeneration) {
|
|
947
|
+
this.#confirmChainLive();
|
|
905
948
|
}
|
|
906
949
|
}
|
|
907
|
-
#
|
|
908
|
-
if (this.#
|
|
950
|
+
#confirmChainLive() {
|
|
951
|
+
if (this.#isChainDisposed()) {
|
|
909
952
|
throw new DisposedContainerError();
|
|
910
953
|
}
|
|
911
954
|
this.#liveGeneration = disposeEpochRef.value;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fluent chain's contract: the slot steps `bind()` offers before `to*()`, and the builder each `to*()` returns.
|
|
3
|
+
*/
|
|
4
|
+
import type { BindingTag } from "#core/tag";
|
|
5
|
+
import type { Token } from "#core/token";
|
|
6
|
+
import type { ActivationHandler, BindingConstraint, BindingIdentifier, Constructor, DeactivationHandler, ResolutionContext } from "#core/types";
|
|
7
|
+
import type { InjectableDependency, ResolvedDependencyValue } from "#injection/descriptor";
|
|
8
|
+
/**
|
|
9
|
+
* The slot steps of the fluent chain, taken before `to*()` so the binding registers once, in its final shape.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.3.16-canary.0
|
|
12
|
+
*/
|
|
13
|
+
export interface SlotConstrainedBuilder<Names extends string = string> {
|
|
14
|
+
/** Narrows the binding to requests the predicate accepts, evaluated on every resolve. */
|
|
15
|
+
when(predicate: BindingConstraint): this;
|
|
16
|
+
/** Declares the binding's slot name, one of the names the token declares. */
|
|
17
|
+
whenNamed(name: Names): this;
|
|
18
|
+
/** Declares one criterion of the binding's slot, replacing any earlier criterion of the same key. */
|
|
19
|
+
whenTagged(criterion: BindingTag): this;
|
|
20
|
+
/** Keeps the binding on the default slot, the one an unconstrained request selects. */
|
|
21
|
+
whenDefault(): this;
|
|
22
|
+
/**
|
|
23
|
+
* Makes the binding a collection member: one of several the token's `resolveAll` returns, never
|
|
24
|
+
* what a single `resolve` selects, and outside slot last-wins. It keeps the default slot.
|
|
25
|
+
*/
|
|
26
|
+
many(): this;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The first step of the fluent chain: the slot, then the `to*` strategy that registers the binding.
|
|
30
|
+
*
|
|
31
|
+
* @since 0.3.16-canary.0
|
|
32
|
+
*/
|
|
33
|
+
export interface BindToBuilder<Value, Names extends string = string> extends SlotConstrainedBuilder<Names> {
|
|
34
|
+
to(type: Constructor<Value>): BindingBuilder<Value>;
|
|
35
|
+
toSelf(): BindingBuilder<Value>;
|
|
36
|
+
toConstantValue(value: Value): ConstantBindingBuilder<Value>;
|
|
37
|
+
toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value>;
|
|
38
|
+
toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value>;
|
|
39
|
+
toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
|
|
40
|
+
[K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
|
|
41
|
+
}) => Value, deps: Deps): BindingBuilder<Value>;
|
|
42
|
+
toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
|
|
43
|
+
[K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
|
|
44
|
+
}) => Promise<Value>, deps: Deps): BindingBuilder<Value>;
|
|
45
|
+
toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The scope-selection step of the fluent chain, which the binding enters once registered.
|
|
49
|
+
*
|
|
50
|
+
* @since 0.3.16-canary.0
|
|
51
|
+
*/
|
|
52
|
+
export interface BindingBuilder<Value> {
|
|
53
|
+
singleton(): SingletonBindingBuilder<Value>;
|
|
54
|
+
transient(): TransientBindingBuilder<Value>;
|
|
55
|
+
scoped(): ScopedBindingBuilder<Value>;
|
|
56
|
+
/** The identifier this binding is registered under. */
|
|
57
|
+
id(): BindingIdentifier;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The fluent chain for a constant — lifecycle hooks only, since the scope is fixed.
|
|
61
|
+
*
|
|
62
|
+
* @since 0.3.16-canary.0
|
|
63
|
+
*/
|
|
64
|
+
export interface ConstantBindingBuilder<Value> {
|
|
65
|
+
onActivation(fn: ActivationHandler<Value>): SingletonLifecycleBuilder<Value>;
|
|
66
|
+
onDeactivation(fn: DeactivationHandler<Value>): SingletonLifecycleBuilder<Value>;
|
|
67
|
+
/** The identifier this binding is registered under. */
|
|
68
|
+
id(): BindingIdentifier;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The fluent chain for an alias — nothing to add, since scoping belongs to the target.
|
|
72
|
+
*
|
|
73
|
+
* @since 0.3.16-canary.0
|
|
74
|
+
*/
|
|
75
|
+
export interface AliasBindingBuilder {
|
|
76
|
+
/** The identifier this binding is registered under. */
|
|
77
|
+
id(): BindingIdentifier;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The fluent chain after `singleton()`, where both lifecycle hooks stay available.
|
|
81
|
+
*
|
|
82
|
+
* @since 0.3.16-canary.0
|
|
83
|
+
*/
|
|
84
|
+
export interface SingletonBindingBuilder<Value> {
|
|
85
|
+
onActivation(fn: ActivationHandler<Value>): this;
|
|
86
|
+
onDeactivation(fn: DeactivationHandler<Value>): this;
|
|
87
|
+
id(): BindingIdentifier;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The fluent chain after `transient()`, where activation is the one lifecycle hook offered.
|
|
91
|
+
*
|
|
92
|
+
* @since 0.3.16-canary.0
|
|
93
|
+
*/
|
|
94
|
+
export interface TransientBindingBuilder<Value> {
|
|
95
|
+
onActivation(fn: ActivationHandler<Value>): this;
|
|
96
|
+
id(): BindingIdentifier;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The fluent chain after `scoped()`, sharing the `transient()` surface.
|
|
100
|
+
*
|
|
101
|
+
* @since 0.3.16-canary.0
|
|
102
|
+
*/
|
|
103
|
+
export interface ScopedBindingBuilder<Value> extends TransientBindingBuilder<Value> {
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The fluent chain a constant enters once a lifecycle hook is added.
|
|
107
|
+
*
|
|
108
|
+
* @since 0.3.16-canary.0
|
|
109
|
+
*/
|
|
110
|
+
export interface SingletonLifecycleBuilder<Value> {
|
|
111
|
+
onActivation(fn: ActivationHandler<Value>): this;
|
|
112
|
+
onDeactivation(fn: DeactivationHandler<Value>): this;
|
|
113
|
+
id(): BindingIdentifier;
|
|
114
|
+
}
|
|
@@ -114,7 +114,14 @@ export declare function asDeclaredBinding(entry: BindingDeclaration, moduleName:
|
|
|
114
114
|
* @throws `InvalidBindingDeclarationError` for no strategy, several, or a key the strategy does not allow.
|
|
115
115
|
* @throws `ManyBindingSlotError` for a slot on a collection member.
|
|
116
116
|
* @throws `SelfBindingRequiresClassError` for `toSelf` on a key that is not a class.
|
|
117
|
+
*
|
|
118
|
+
* @since 0.11.0
|
|
117
119
|
*/
|
|
118
120
|
export declare function binding<Value, Names extends string, const Deps extends ReadonlyArray<InjectableDependency> = []>(key: Token<Value, Names>, definition: BindingDefinition<NoInfer<Value>, NoInfer<Names>, Deps>): BindingDeclaration;
|
|
121
|
+
/**
|
|
122
|
+
* Declares one binding keyed by a class, the one kind of key `toSelf` accepts.
|
|
123
|
+
*
|
|
124
|
+
* @since 0.11.0
|
|
125
|
+
*/
|
|
119
126
|
export declare function binding<Value, const Deps extends ReadonlyArray<InjectableDependency> = []>(key: Constructor<Value>, definition: BindingDefinition<NoInfer<Value>, string, Deps> | SelfDefinition<NoInfer<Value>>): BindingDeclaration;
|
|
120
127
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_BINDING_SLOT, withSlotCriterion } from "#core/binding";
|
|
2
2
|
import { slotName } from "#core/tag";
|
|
3
3
|
import { tokenName } from "#core/token";
|
|
4
|
-
import { InvalidBindingDeclarationError, ManyBindingSlotError, SelfBindingRequiresClassError } from "#errors
|
|
4
|
+
import { InvalidBindingDeclarationError, ManyBindingSlotError, SelfBindingRequiresClassError } from "#errors";
|
|
5
5
|
import { normalizeToDescriptor } from "#injection/descriptor";
|
|
6
6
|
// Only this module can mint the symbol, so an entry carrying it was made by `binding()`.
|
|
7
7
|
const declarationBrand = Symbol("di:binding-declaration");
|
|
@@ -83,8 +83,9 @@ function slotOf(definition) {
|
|
|
83
83
|
}
|
|
84
84
|
return slot;
|
|
85
85
|
}
|
|
86
|
-
// The value type is erased here, once: the overloads above are what a caller is checked against.
|
|
87
86
|
/**
|
|
87
|
+
* Implements both overloads with the value type erased once, since a caller is checked against the overloads above.
|
|
88
|
+
*
|
|
88
89
|
* @since 0.11.0
|
|
89
90
|
*/
|
|
90
91
|
export function binding(key, definition) {
|