@codefast/di 0.5.0 → 0.6.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 +615 -0
- package/README.md +119 -30
- package/dist/ambient/active-container.d.ts +37 -0
- package/dist/ambient/active-container.d.ts.map +1 -0
- package/dist/ambient/active-container.js +41 -0
- package/dist/ambient/active-container.js.map +1 -0
- package/dist/container/binding-builders.d.ts +12 -8
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +56 -31
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts +33 -19
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +279 -118
- package/dist/container/container.js.map +1 -1
- package/dist/{resolution → core}/binding-scope.d.ts +2 -2
- package/dist/core/binding-scope.d.ts.map +1 -0
- package/dist/core/binding-scope.js.map +1 -0
- package/dist/{binding.d.ts → core/binding.d.ts} +70 -15
- package/dist/core/binding.d.ts.map +1 -0
- package/dist/{binding.js → core/binding.js} +27 -12
- package/dist/core/binding.js.map +1 -0
- package/dist/core/constraint-requirement.d.ts +38 -0
- package/dist/core/constraint-requirement.d.ts.map +1 -0
- package/dist/core/constraint-requirement.js +28 -0
- package/dist/core/constraint-requirement.js.map +1 -0
- package/dist/{constructor-type.d.ts → core/constructor-type.d.ts} +1 -1
- package/dist/core/constructor-type.d.ts.map +1 -0
- package/dist/core/constructor-type.js.map +1 -0
- package/dist/{module.d.ts → core/module.d.ts} +5 -5
- package/dist/core/module.d.ts.map +1 -0
- package/dist/{module.js → core/module.js} +4 -4
- package/dist/core/module.js.map +1 -0
- package/dist/{registry.d.ts → core/registry.d.ts} +27 -7
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/{registry.js → core/registry.js} +125 -52
- package/dist/core/registry.js.map +1 -0
- package/dist/core/tag.d.ts +80 -0
- package/dist/core/tag.d.ts.map +1 -0
- package/dist/core/tag.js +84 -0
- package/dist/core/tag.js.map +1 -0
- package/dist/{token.d.ts → core/token.d.ts} +2 -6
- package/dist/core/token.d.ts.map +1 -0
- package/dist/core/token.js +13 -0
- package/dist/core/token.js.map +1 -0
- package/dist/{types.d.ts → core/types.d.ts} +27 -21
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js.map +1 -0
- package/dist/decorators/inject.d.ts +5 -49
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js +34 -104
- package/dist/decorators/inject.js.map +1 -1
- package/dist/decorators/injectable.d.ts +25 -5
- package/dist/decorators/injectable.d.ts.map +1 -1
- package/dist/decorators/injectable.js +2 -3
- package/dist/decorators/injectable.js.map +1 -1
- package/dist/decorators/lifecycle-decorators.d.ts +4 -2
- package/dist/decorators/lifecycle-decorators.d.ts.map +1 -1
- package/dist/decorators/lifecycle-decorators.js +19 -25
- package/dist/decorators/lifecycle-decorators.js.map +1 -1
- package/dist/{resolution → errors}/diagnostics.d.ts +2 -0
- package/dist/errors/diagnostics.d.ts.map +1 -0
- package/dist/errors/diagnostics.js.map +1 -0
- package/dist/{errors.d.ts → errors/errors.d.ts} +89 -4
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/{errors.js → errors/errors.js} +129 -7
- package/dist/errors/errors.js.map +1 -0
- package/dist/index.d.ts +21 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/injection/descriptor.d.ts +79 -0
- package/dist/injection/descriptor.d.ts.map +1 -0
- package/dist/injection/descriptor.js +118 -0
- package/dist/injection/descriptor.js.map +1 -0
- package/dist/{resolution → injection}/resolve-options.d.ts +21 -11
- package/dist/injection/resolve-options.d.ts.map +1 -0
- package/dist/injection/resolve-options.js +101 -0
- package/dist/injection/resolve-options.js.map +1 -0
- package/dist/introspection/dependency-graph.d.ts +16 -5
- package/dist/introspection/dependency-graph.d.ts.map +1 -1
- package/dist/introspection/dependency-graph.js +138 -57
- package/dist/introspection/dependency-graph.js.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.d.ts +6 -3
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.js +3 -0
- package/dist/introspection/graph-adapters/cytoscape.js.map +1 -1
- package/dist/introspection/graph-adapters/dot.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/dot.js +12 -5
- package/dist/introspection/graph-adapters/dot.js.map +1 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts +9 -0
- package/dist/introspection/graph-adapters/mermaid.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/mermaid.js +47 -0
- package/dist/introspection/graph-adapters/mermaid.js.map +1 -0
- package/dist/introspection/graph-adapters/reactflow.d.ts +6 -3
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/reactflow.js +5 -1
- package/dist/introspection/graph-adapters/reactflow.js.map +1 -1
- package/dist/introspection/inspector.d.ts +4 -4
- package/dist/introspection/inspector.d.ts.map +1 -1
- package/dist/introspection/inspector.js +10 -6
- package/dist/introspection/inspector.js.map +1 -1
- package/dist/lifecycle/lifecycle-manager.d.ts +27 -0
- package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -0
- package/dist/{resolution/lifecycle.js → lifecycle/lifecycle-manager.js} +39 -9
- package/dist/lifecycle/lifecycle-manager.js.map +1 -0
- package/dist/lifecycle/scope-manager.d.ts +48 -0
- package/dist/lifecycle/scope-manager.d.ts.map +1 -0
- package/dist/lifecycle/scope-manager.js +135 -0
- package/dist/lifecycle/scope-manager.js.map +1 -0
- package/dist/metadata/metadata-reader-token.d.ts +1 -1
- package/dist/metadata/metadata-reader-token.d.ts.map +1 -1
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/metadata/metadata-reader-token.js.map +1 -1
- package/dist/metadata/metadata-types.d.ts +9 -9
- package/dist/metadata/metadata-types.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.d.ts +19 -6
- package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.js +89 -9
- package/dist/metadata/symbol-metadata-reader.js.map +1 -1
- package/dist/metadata/verifying-metadata-reader.d.ts +16 -0
- package/dist/metadata/verifying-metadata-reader.d.ts.map +1 -0
- package/dist/metadata/verifying-metadata-reader.js +32 -0
- package/dist/metadata/verifying-metadata-reader.js.map +1 -0
- package/dist/resolution/{activation-need.d.ts → cache/activation-need.d.ts} +5 -5
- package/dist/resolution/cache/activation-need.d.ts.map +1 -0
- package/dist/resolution/cache/activation-need.js.map +1 -0
- package/dist/resolution/{binding-lookup-cache.d.ts → cache/binding-lookup-cache.d.ts} +6 -5
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/cache/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/cache/class-introspector.d.ts +66 -0
- package/dist/resolution/cache/class-introspector.d.ts.map +1 -0
- package/dist/resolution/cache/class-introspector.js +194 -0
- package/dist/resolution/cache/class-introspector.js.map +1 -0
- package/dist/resolution/context.d.ts +84 -0
- package/dist/resolution/context.d.ts.map +1 -0
- package/dist/resolution/{environment.js → context.js} +54 -80
- package/dist/resolution/context.js.map +1 -0
- package/dist/resolution/path/resolution-path.d.ts +94 -0
- package/dist/resolution/path/resolution-path.d.ts.map +1 -0
- package/dist/resolution/path/resolution-path.js +117 -0
- package/dist/resolution/path/resolution-path.js.map +1 -0
- package/dist/resolution/{instantiation-plan.d.ts → plan/instantiation-plan.d.ts} +30 -7
- package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -0
- package/dist/resolution/plan/instantiation-plan.js +390 -0
- package/dist/resolution/plan/instantiation-plan.js.map +1 -0
- package/dist/resolution/resolver.d.ts +28 -28
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +552 -239
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/select/binding-select.d.ts +34 -0
- package/dist/resolution/select/binding-select.d.ts.map +1 -0
- package/dist/resolution/select/binding-select.js +175 -0
- package/dist/resolution/select/binding-select.js.map +1 -0
- package/dist/resolution/{constraints.d.ts → select/constraints.d.ts} +13 -12
- package/dist/resolution/select/constraints.d.ts.map +1 -0
- package/dist/resolution/{constraints.js → select/constraints.js} +33 -22
- package/dist/resolution/select/constraints.js.map +1 -0
- package/package.json +160 -20
- package/src/ambient/active-container.ts +65 -0
- package/src/container/binding-builders.ts +72 -44
- package/src/container/container.ts +355 -170
- package/src/{resolution → core}/binding-scope.ts +2 -2
- package/src/{binding.ts → core/binding.ts} +101 -39
- package/src/core/constraint-requirement.ts +51 -0
- package/src/{constructor-type.ts → core/constructor-type.ts} +1 -1
- package/src/{module.ts → core/module.ts} +10 -10
- package/src/core/registry.ts +365 -0
- package/src/core/tag.ts +144 -0
- package/src/{token.ts → core/token.ts} +2 -14
- package/src/{types.ts → core/types.ts} +37 -34
- package/src/decorators/inject.ts +39 -175
- package/src/decorators/injectable.ts +40 -10
- package/src/decorators/lifecycle-decorators.ts +22 -34
- package/src/{resolution → errors}/diagnostics.ts +2 -0
- package/src/{errors.ts → errors/errors.ts} +152 -8
- package/src/index.ts +43 -15
- package/src/injection/descriptor.ts +206 -0
- package/src/injection/resolve-options.ts +144 -0
- package/src/introspection/dependency-graph.ts +223 -63
- package/src/introspection/graph-adapters/cytoscape.ts +9 -3
- package/src/introspection/graph-adapters/dot.ts +13 -5
- package/src/introspection/graph-adapters/mermaid.ts +61 -0
- package/src/introspection/graph-adapters/reactflow.ts +11 -4
- package/src/introspection/inspector.ts +16 -12
- package/src/{resolution/lifecycle.ts → lifecycle/lifecycle-manager.ts} +68 -32
- package/src/lifecycle/scope-manager.ts +156 -0
- package/src/metadata/metadata-reader-token.ts +2 -2
- package/src/metadata/metadata-types.ts +9 -9
- package/src/metadata/symbol-metadata-reader.ts +95 -18
- package/src/metadata/verifying-metadata-reader.ts +41 -0
- package/src/resolution/{activation-need.ts → cache/activation-need.ts} +8 -8
- package/src/resolution/{binding-lookup-cache.ts → cache/binding-lookup-cache.ts} +6 -5
- package/src/resolution/cache/class-introspector.ts +251 -0
- package/src/resolution/{environment.ts → context.ts} +84 -145
- package/src/resolution/path/resolution-path.ts +167 -0
- package/src/resolution/plan/instantiation-plan.ts +579 -0
- package/src/resolution/resolver.ts +635 -313
- package/src/resolution/select/binding-select.ts +210 -0
- package/src/resolution/select/constraints.ts +130 -0
- package/dist/binding.d.ts.map +0 -1
- package/dist/binding.js.map +0 -1
- package/dist/constructor-type.d.ts.map +0 -1
- package/dist/constructor-type.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/module.d.ts.map +0 -1
- package/dist/module.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/resolution/activation-need.d.ts.map +0 -1
- package/dist/resolution/activation-need.js.map +0 -1
- package/dist/resolution/binding-lookup-cache.d.ts.map +0 -1
- package/dist/resolution/binding-lookup-cache.js.map +0 -1
- package/dist/resolution/binding-scope.d.ts.map +0 -1
- package/dist/resolution/binding-scope.js.map +0 -1
- package/dist/resolution/binding-select.d.ts +0 -23
- package/dist/resolution/binding-select.d.ts.map +0 -1
- package/dist/resolution/binding-select.js +0 -121
- package/dist/resolution/binding-select.js.map +0 -1
- package/dist/resolution/class-introspector.d.ts +0 -27
- package/dist/resolution/class-introspector.d.ts.map +0 -1
- package/dist/resolution/class-introspector.js +0 -60
- package/dist/resolution/class-introspector.js.map +0 -1
- package/dist/resolution/constraints.d.ts.map +0 -1
- package/dist/resolution/constraints.js.map +0 -1
- package/dist/resolution/diagnostics.d.ts.map +0 -1
- package/dist/resolution/diagnostics.js.map +0 -1
- package/dist/resolution/environment.d.ts +0 -94
- package/dist/resolution/environment.d.ts.map +0 -1
- package/dist/resolution/environment.js.map +0 -1
- package/dist/resolution/instantiation-plan.d.ts.map +0 -1
- package/dist/resolution/instantiation-plan.js +0 -183
- package/dist/resolution/instantiation-plan.js.map +0 -1
- package/dist/resolution/lifecycle.d.ts +0 -23
- package/dist/resolution/lifecycle.d.ts.map +0 -1
- package/dist/resolution/lifecycle.js.map +0 -1
- package/dist/resolution/resolution-path.d.ts +0 -98
- package/dist/resolution/resolution-path.d.ts.map +0 -1
- package/dist/resolution/resolution-path.js +0 -98
- package/dist/resolution/resolution-path.js.map +0 -1
- package/dist/resolution/resolve-options.d.ts.map +0 -1
- package/dist/resolution/resolve-options.js +0 -56
- package/dist/resolution/resolve-options.js.map +0 -1
- package/dist/resolution/scope.d.ts +0 -28
- package/dist/resolution/scope.d.ts.map +0 -1
- package/dist/resolution/scope.js +0 -88
- package/dist/resolution/scope.js.map +0 -1
- package/dist/token.d.ts.map +0 -1
- package/dist/token.js +0 -22
- package/dist/token.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/src/registry.ts +0 -282
- package/src/resolution/binding-select.ts +0 -154
- package/src/resolution/class-introspector.ts +0 -74
- package/src/resolution/constraints.ts +0 -121
- package/src/resolution/instantiation-plan.ts +0 -292
- package/src/resolution/resolution-path.ts +0 -156
- package/src/resolution/resolve-options.ts +0 -89
- package/src/resolution/scope.ts +0 -105
- /package/dist/{resolution → core}/binding-scope.js +0 -0
- /package/dist/{constructor-type.js → core/constructor-type.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/dist/{resolution → errors}/diagnostics.js +0 -0
- /package/dist/resolution/{activation-need.js → cache/activation-need.js} +0 -0
- /package/dist/resolution/{binding-lookup-cache.js → cache/binding-lookup-cache.js} +0 -0
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
import type { Binding, BindToBuilder } from "#/binding";
|
|
2
|
-
import { NO_INSTANCE } from "#/binding";
|
|
3
1
|
import type { BindingRegistration } from "#/container/binding-builders";
|
|
4
2
|
import { BindingChain } from "#/container/binding-builders";
|
|
3
|
+
import type { Binding, BindingBuilder, BindToBuilder, ConstantBinding } from "#/core/binding";
|
|
4
|
+
import { NO_INSTANCE } from "#/core/binding";
|
|
5
|
+
import { effectiveBindingScope } from "#/core/binding-scope";
|
|
6
|
+
import { constraintRequirementOf } from "#/core/constraint-requirement";
|
|
7
|
+
import type { AsyncModule, AsyncModuleBuilder, ModuleBuilder, SyncModule } from "#/core/module";
|
|
8
|
+
import { isSyncModule, MODULE_SETUP } from "#/core/module";
|
|
9
|
+
import { BindingRegistry } from "#/core/registry";
|
|
10
|
+
import type { Token } from "#/core/token";
|
|
11
|
+
import { tokenName } from "#/core/token";
|
|
12
|
+
import type {
|
|
13
|
+
ActivationHandler,
|
|
14
|
+
BindingIdentifier,
|
|
15
|
+
BindingScope,
|
|
16
|
+
Constructor,
|
|
17
|
+
DeactivationHandler,
|
|
18
|
+
ResolveOptions,
|
|
19
|
+
} from "#/core/types";
|
|
5
20
|
import type { AutoRegisterRegistry } from "#/decorators/injectable";
|
|
21
|
+
import type { ResolutionDiagnostics } from "#/errors/diagnostics";
|
|
22
|
+
import { RESOLUTION_DIAGNOSTICS } from "#/errors/diagnostics";
|
|
6
23
|
import {
|
|
7
24
|
AsyncModuleLoadError,
|
|
8
25
|
CircularDependencyError,
|
|
@@ -11,39 +28,25 @@ import {
|
|
|
11
28
|
RebindUnboundTokenError,
|
|
12
29
|
ScopeViolationError,
|
|
13
30
|
SyncDisposalNotSupportedError,
|
|
14
|
-
|
|
31
|
+
UnreachableConstraintError,
|
|
32
|
+
UnreachableLifecycleHookError,
|
|
33
|
+
} from "#/errors/errors";
|
|
34
|
+
import type { DependencySlot } from "#/injection/resolve-options";
|
|
35
|
+
import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#/injection/resolve-options";
|
|
15
36
|
import type { ContainerGraphJson, GraphOptions } from "#/introspection/dependency-graph";
|
|
16
37
|
import { buildDependencyGraph } from "#/introspection/dependency-graph";
|
|
17
38
|
import type { BindingSnapshot, ContainerSnapshot } from "#/introspection/inspector";
|
|
18
39
|
import { Inspector } from "#/introspection/inspector";
|
|
40
|
+
import { LifecycleManager } from "#/lifecycle/lifecycle-manager";
|
|
41
|
+
import { ScopeManager } from "#/lifecycle/scope-manager";
|
|
19
42
|
import { MetadataReaderToken } from "#/metadata/metadata-reader-token";
|
|
20
43
|
import type { MetadataReader } from "#/metadata/metadata-types";
|
|
21
44
|
import { defaultMetadataReader } from "#/metadata/symbol-metadata-reader";
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import { isSyncModule, MODULE_SETUP } from "#/module";
|
|
25
|
-
import { BindingRegistry } from "#/registry";
|
|
26
|
-
import { effectiveBindingScope } from "#/resolution/binding-scope";
|
|
27
|
-
import type { ResolutionDiagnostics } from "#/resolution/diagnostics";
|
|
28
|
-
import { RESOLUTION_DIAGNOSTICS } from "#/resolution/diagnostics";
|
|
29
|
-
import { LifecycleManager } from "#/resolution/lifecycle";
|
|
30
|
-
import { ROOT_BRANCH } from "#/resolution/resolution-path";
|
|
31
|
-
import type { DependencySlot } from "#/resolution/resolve-options";
|
|
32
|
-
import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#/resolution/resolve-options";
|
|
45
|
+
import { verifyingMetadataReader } from "#/metadata/verifying-metadata-reader";
|
|
46
|
+
import { ROOT_BRANCH } from "#/resolution/path/resolution-path";
|
|
33
47
|
import { DependencyResolver } from "#/resolution/resolver";
|
|
34
|
-
import { ScopeManager } from "#/resolution/scope";
|
|
35
|
-
import type { Token } from "#/token";
|
|
36
|
-
import { tokenName } from "#/token";
|
|
37
|
-
import type {
|
|
38
|
-
ActivationHandler,
|
|
39
|
-
BindingIdentifier,
|
|
40
|
-
BindingScope,
|
|
41
|
-
Constructor,
|
|
42
|
-
DeactivationHandler,
|
|
43
|
-
ResolveOptions,
|
|
44
|
-
} from "#/types";
|
|
45
48
|
|
|
46
|
-
// ── Container interface
|
|
49
|
+
// ── Container interface ──────────────────────────────────────────────────────────────────────────────────────────────
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
52
|
* @since 0.3.16-canary.0
|
|
@@ -51,12 +54,12 @@ import type {
|
|
|
51
54
|
export interface Container {
|
|
52
55
|
readonly isDisposed: boolean;
|
|
53
56
|
|
|
54
|
-
bind<
|
|
57
|
+
bind<Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value>;
|
|
55
58
|
unbind(tokenOrId: Token<unknown> | Constructor | BindingIdentifier): void;
|
|
56
59
|
unbindAsync(tokenOrId: Token<unknown> | Constructor | BindingIdentifier): Promise<void>;
|
|
57
60
|
unbindAll(): void;
|
|
58
61
|
unbindAllAsync(): Promise<void>;
|
|
59
|
-
rebind<
|
|
62
|
+
rebind<Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value>;
|
|
60
63
|
|
|
61
64
|
load(...modules: Array<SyncModule>): void;
|
|
62
65
|
loadAsync(...modules: Array<SyncModule | AsyncModule>): Promise<void>;
|
|
@@ -64,21 +67,18 @@ export interface Container {
|
|
|
64
67
|
unloadAsync(...modules: Array<SyncModule | AsyncModule>): Promise<void>;
|
|
65
68
|
loadAutoRegistered(registry: AutoRegisterRegistry): number;
|
|
66
69
|
|
|
67
|
-
onActivation<
|
|
68
|
-
onDeactivation<
|
|
70
|
+
onActivation<Value>(token: Token<Value> | Constructor<Value>, handler: ActivationHandler<Value>): void;
|
|
71
|
+
onDeactivation<Value>(token: Token<Value> | Constructor<Value>, handler: DeactivationHandler<Value>): void;
|
|
69
72
|
|
|
70
|
-
resolve<
|
|
71
|
-
resolveAsync<
|
|
72
|
-
resolveOptional<
|
|
73
|
-
resolveOptionalAsync<
|
|
73
|
+
resolve<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value;
|
|
74
|
+
resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value>;
|
|
75
|
+
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined;
|
|
76
|
+
resolveOptionalAsync<Value>(
|
|
74
77
|
token: Token<Value> | Constructor<Value>,
|
|
75
78
|
options?: ResolveOptions,
|
|
76
79
|
): Promise<Value | undefined>;
|
|
77
|
-
resolveAll<
|
|
78
|
-
resolveAllAsync<
|
|
79
|
-
token: Token<Value> | Constructor<Value>,
|
|
80
|
-
options?: ResolveOptions,
|
|
81
|
-
): Promise<Array<Value>>;
|
|
80
|
+
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value>;
|
|
81
|
+
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Array<Value>>;
|
|
82
82
|
|
|
83
83
|
createChild(): Container;
|
|
84
84
|
|
|
@@ -91,24 +91,56 @@ export interface Container {
|
|
|
91
91
|
|
|
92
92
|
has(token: Token<unknown> | Constructor, options?: ResolveOptions): boolean;
|
|
93
93
|
hasOwn(token: Token<unknown> | Constructor, options?: ResolveOptions): boolean;
|
|
94
|
-
lookupBindings<
|
|
94
|
+
lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot>;
|
|
95
95
|
inspect(): ContainerSnapshot;
|
|
96
96
|
generateDependencyGraph(options?: GraphOptions): ContainerGraphJson;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* What a container has to be told before it exists, as opposed to what it can be bound later.
|
|
101
|
+
*
|
|
102
|
+
* @since 0.6.0
|
|
103
|
+
*/
|
|
104
|
+
export interface ContainerOptions {
|
|
105
|
+
/**
|
|
106
|
+
* Reader the resolver consults for class metadata, replacing the decorator reader.
|
|
107
|
+
*
|
|
108
|
+
* @remarks Takes precedence over a {@link MetadataReaderToken} binding, and children inherit it.
|
|
109
|
+
* A resolver is handed its reader when it is built, so this is the only way to give the container
|
|
110
|
+
* you are creating a reader of your own.
|
|
111
|
+
*/
|
|
112
|
+
readonly metadataReader?: MetadataReader | undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
99
115
|
/**
|
|
100
116
|
* @since 0.3.16-canary.0
|
|
101
117
|
*/
|
|
102
118
|
export interface ContainerStatic {
|
|
103
|
-
create(): Container;
|
|
119
|
+
create(options?: ContainerOptions): Container;
|
|
104
120
|
fromModules(...modules: Array<SyncModule>): Container;
|
|
105
121
|
fromModulesAsync(...modules: Array<SyncModule | AsyncModule>): Promise<Container>;
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
//
|
|
124
|
+
// A Record rather than an if-chain, so a new `BindingScope` is a compile error here instead of
|
|
125
|
+
// silently landing in whichever branch happened to be last.
|
|
126
|
+
const APPLY_BINDING_SCOPE: Record<BindingScope, (builder: BindingBuilder<unknown>) => void> = {
|
|
127
|
+
singleton: (builder) => {
|
|
128
|
+
builder.singleton();
|
|
129
|
+
},
|
|
130
|
+
scoped: (builder) => {
|
|
131
|
+
builder.scoped();
|
|
132
|
+
},
|
|
133
|
+
transient: (builder) => {
|
|
134
|
+
builder.transient();
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// ── DefaultContainer ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
109
139
|
|
|
110
140
|
class DefaultContainer implements Container {
|
|
111
141
|
#disposed = false;
|
|
142
|
+
// The one teardown run — every dispose() call returns it once it exists.
|
|
143
|
+
#disposePromise: Promise<void> | undefined;
|
|
112
144
|
readonly #registry: BindingRegistry;
|
|
113
145
|
readonly #scope: ScopeManager;
|
|
114
146
|
readonly #lifecycle: LifecycleManager;
|
|
@@ -124,12 +156,12 @@ class DefaultContainer implements Container {
|
|
|
124
156
|
// One shared registration for every chain this container's own `bind()` creates.
|
|
125
157
|
#registration: BindingRegistration | undefined;
|
|
126
158
|
|
|
127
|
-
constructor(parent?: DefaultContainer) {
|
|
159
|
+
constructor(parent?: DefaultContainer, options?: ContainerOptions) {
|
|
128
160
|
this.#parent = parent;
|
|
129
161
|
this.#registry = new BindingRegistry();
|
|
130
162
|
this.#scope = new ScopeManager(parent !== undefined);
|
|
131
163
|
this.#lifecycle = new LifecycleManager();
|
|
132
|
-
this.#initResolver();
|
|
164
|
+
this.#initResolver(options?.metadataReader);
|
|
133
165
|
}
|
|
134
166
|
|
|
135
167
|
#getInspector(): Inspector {
|
|
@@ -149,21 +181,27 @@ class DefaultContainer implements Container {
|
|
|
149
181
|
if (this.#moduleRefs !== undefined || this.#moduleBindingIds !== undefined) {
|
|
150
182
|
builtSubsystems.push("container.moduleTables");
|
|
151
183
|
}
|
|
152
|
-
if (this.#registry.
|
|
184
|
+
if (this.#registry.isNamedIndexBuilt) {
|
|
153
185
|
builtSubsystems.push("registry.namedIndex");
|
|
154
186
|
}
|
|
155
|
-
if (this.#
|
|
187
|
+
if (this.#registry.isTaggedIndexBuilt) {
|
|
188
|
+
builtSubsystems.push("registry.taggedIndex");
|
|
189
|
+
}
|
|
190
|
+
if (this.#scope.isScopedCacheBuilt) {
|
|
156
191
|
builtSubsystems.push("scope.scoped");
|
|
157
192
|
}
|
|
158
|
-
if (this.#lifecycle.
|
|
193
|
+
if (this.#lifecycle.isActivationTableBuilt) {
|
|
159
194
|
builtSubsystems.push("lifecycle.activationHooks");
|
|
160
195
|
}
|
|
161
196
|
return { ...this.#resolver.describeCaches(), scopedInstanceCount: this.#scope.scopedCount, builtSubsystems };
|
|
162
197
|
}
|
|
163
198
|
|
|
164
|
-
#initResolver(): void {
|
|
165
|
-
const
|
|
166
|
-
const
|
|
199
|
+
#initResolver(configuredReader: MetadataReader | undefined): void {
|
|
200
|
+
const parent = this.#parent;
|
|
201
|
+
const metadataReader = verifyingMetadataReader(
|
|
202
|
+
configuredReader ?? (parent === undefined ? defaultMetadataReader : parent.#readerForChild()),
|
|
203
|
+
);
|
|
204
|
+
const parentResolver = parent === undefined ? undefined : parent.#resolver;
|
|
167
205
|
this.#resolver = new DependencyResolver(
|
|
168
206
|
this.#registry,
|
|
169
207
|
this.#scope,
|
|
@@ -174,47 +212,49 @@ class DefaultContainer implements Container {
|
|
|
174
212
|
);
|
|
175
213
|
}
|
|
176
214
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (metaBindings.length > 0) {
|
|
215
|
+
/** What a container being constructed under this one inherits: a reader bound here, else this one's. */
|
|
216
|
+
#readerForChild(): MetadataReader {
|
|
217
|
+
if (this.#registry.getAll(MetadataReaderToken).length > 0) {
|
|
181
218
|
try {
|
|
182
|
-
return this.#resolver.resolve(MetadataReaderToken, undefined, []
|
|
219
|
+
return this.#resolver.resolve(MetadataReaderToken, undefined, []);
|
|
183
220
|
} catch {
|
|
184
|
-
//
|
|
221
|
+
// An unresolvable reader binding is not worth failing a child over.
|
|
185
222
|
}
|
|
186
223
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
224
|
+
return this.#resolver.metadataReader;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** One reader per container, fixed when its resolver was built — so every path agrees on it. */
|
|
228
|
+
#getMetadataReader(): MetadataReader {
|
|
229
|
+
return this.#resolver.metadataReader;
|
|
191
230
|
}
|
|
192
231
|
|
|
193
232
|
get isDisposed(): boolean {
|
|
194
233
|
return this.#disposed;
|
|
195
234
|
}
|
|
196
235
|
|
|
197
|
-
// ── Binding
|
|
236
|
+
// ── Binding ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
198
237
|
|
|
199
|
-
bind<
|
|
238
|
+
bind<Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value> {
|
|
200
239
|
this.#assertNotDisposed();
|
|
201
240
|
return this.#createBindToBuilder(token);
|
|
202
241
|
}
|
|
203
242
|
|
|
204
243
|
/** The registration every non-module chain shares, so `bind()` allocates only the builder. */
|
|
205
244
|
#ownRegistration(): BindingRegistration {
|
|
206
|
-
return (this.#registration ??= { registry: this.#registry, moduleBindingIds: undefined });
|
|
245
|
+
return (this.#registration ??= { registry: this.#registry, scope: this.#scope, moduleBindingIds: undefined });
|
|
207
246
|
}
|
|
208
247
|
|
|
209
248
|
/** One registration per module load, holding that module's id list directly. */
|
|
210
249
|
#moduleRegistration(moduleRef: object): BindingRegistration {
|
|
211
250
|
return {
|
|
212
251
|
registry: this.#registry,
|
|
252
|
+
scope: this.#scope,
|
|
213
253
|
moduleBindingIds: (this.#moduleBindingIds ??= new Map()).getOrInsert(moduleRef, []),
|
|
214
254
|
};
|
|
215
255
|
}
|
|
216
256
|
|
|
217
|
-
#createBindToBuilder<
|
|
257
|
+
#createBindToBuilder<Value>(
|
|
218
258
|
token: Token<Value> | Constructor<Value>,
|
|
219
259
|
registration: BindingRegistration = this.#ownRegistration(),
|
|
220
260
|
): BindToBuilder<Value> {
|
|
@@ -237,51 +277,88 @@ class DefaultContainer implements Container {
|
|
|
237
277
|
return this.#drainSingletons(this.#registry.removeByToken(tokenOrId));
|
|
238
278
|
}
|
|
239
279
|
|
|
240
|
-
/** Drain scope entries for already-removed bindings
|
|
280
|
+
/** Drain scope entries for already-removed bindings, and pair each one that still owes a deactivation. */
|
|
241
281
|
#drainSingletons(bindings: ReadonlyArray<Binding>): Array<[Binding, unknown]> {
|
|
242
282
|
const pairs: Array<[Binding, unknown]> = [];
|
|
243
283
|
for (const binding of bindings) {
|
|
244
284
|
if (binding.instance !== NO_INSTANCE) {
|
|
245
285
|
pairs.push([binding, binding.instance]);
|
|
246
286
|
this.#scope.deleteSingleton(binding);
|
|
287
|
+
} else if (this.#owesConstantDeactivation(binding)) {
|
|
288
|
+
pairs.push([binding, binding.value]);
|
|
247
289
|
}
|
|
248
290
|
this.#scope.deleteScoped(binding.id);
|
|
249
291
|
}
|
|
250
292
|
return pairs;
|
|
251
293
|
}
|
|
252
294
|
|
|
253
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Whether a constant still owes its deactivation.
|
|
297
|
+
*
|
|
298
|
+
* @remarks A constant's value is handed in at bind time rather than built on demand, so its hook is
|
|
299
|
+
* owed whether or not anything ever resolved it. Callers check `instance` first: a constant that
|
|
300
|
+
* carries one was cached through activation and is deactivated with that value instead.
|
|
301
|
+
*/
|
|
302
|
+
#owesConstantDeactivation(binding: Binding): binding is ConstantBinding<unknown> {
|
|
303
|
+
return (
|
|
304
|
+
binding.kind === "constant" &&
|
|
305
|
+
(binding.onDeactivation !== undefined || this.#lifecycle.hasDeactivationHandlers(binding.token))
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Runs every pair's deactivation even when one throws, then reports what threw. */
|
|
310
|
+
#deactivatePairsSync(pairs: ReadonlyArray<[Binding, unknown]>): void {
|
|
311
|
+
if (pairs.length === 0) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
254
314
|
const reader = this.#getMetadataReader();
|
|
255
|
-
|
|
256
|
-
|
|
315
|
+
const errors: Array<unknown> = [];
|
|
316
|
+
for (const [binding, instance] of pairs) {
|
|
317
|
+
try {
|
|
318
|
+
this.#lifecycle.runDeactivationSync(binding, instance, reader);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
errors.push(error);
|
|
321
|
+
}
|
|
257
322
|
}
|
|
323
|
+
throwCollected(errors, "unbind completed, but deactivation hooks threw");
|
|
258
324
|
}
|
|
259
325
|
|
|
260
|
-
async
|
|
261
|
-
|
|
326
|
+
async #deactivatePairs(pairs: ReadonlyArray<[Binding, unknown]>): Promise<void> {
|
|
327
|
+
if (pairs.length === 0) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
262
330
|
const reader = this.#getMetadataReader();
|
|
263
|
-
|
|
264
|
-
|
|
331
|
+
const errors: Array<unknown> = [];
|
|
332
|
+
for (const [binding, instance] of pairs) {
|
|
333
|
+
try {
|
|
334
|
+
await this.#lifecycle.runDeactivation(binding, instance, reader);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
errors.push(error);
|
|
337
|
+
}
|
|
265
338
|
}
|
|
339
|
+
throwCollected(errors, "unbind completed, but deactivation hooks threw");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
#unbindSync(tokenOrId: Token<unknown> | Constructor | BindingIdentifier): void {
|
|
343
|
+
this.#deactivatePairsSync(this.#collectDeactivationPairs(tokenOrId));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async unbindAsync(tokenOrId: Token<unknown> | Constructor | BindingIdentifier): Promise<void> {
|
|
347
|
+
this.#assertNotDisposed();
|
|
348
|
+
await this.#deactivatePairs(this.#collectDeactivationPairs(tokenOrId));
|
|
266
349
|
}
|
|
267
350
|
|
|
268
351
|
unbindAll(): void {
|
|
269
352
|
this.#assertNotDisposed();
|
|
270
|
-
|
|
271
|
-
for (const [binding, instance] of this.#drainSingletons(this.#registry.clear())) {
|
|
272
|
-
this.#lifecycle.runDeactivationSync(binding, instance, reader);
|
|
273
|
-
}
|
|
353
|
+
this.#deactivatePairsSync(this.#drainSingletons(this.#registry.clear()));
|
|
274
354
|
}
|
|
275
355
|
|
|
276
356
|
async unbindAllAsync(): Promise<void> {
|
|
277
357
|
this.#assertNotDisposed();
|
|
278
|
-
|
|
279
|
-
for (const [binding, instance] of this.#drainSingletons(this.#registry.clear())) {
|
|
280
|
-
await this.#lifecycle.runDeactivation(binding, instance, reader);
|
|
281
|
-
}
|
|
358
|
+
await this.#deactivatePairs(this.#drainSingletons(this.#registry.clear()));
|
|
282
359
|
}
|
|
283
360
|
|
|
284
|
-
rebind<
|
|
361
|
+
rebind<Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value> {
|
|
285
362
|
this.#assertNotDisposed();
|
|
286
363
|
if (!this.#registry.has(token)) {
|
|
287
364
|
throw new RebindUnboundTokenError(tokenName(token));
|
|
@@ -291,7 +368,7 @@ class DefaultContainer implements Container {
|
|
|
291
368
|
return this.#createBindToBuilder(token);
|
|
292
369
|
}
|
|
293
370
|
|
|
294
|
-
// ── Module
|
|
371
|
+
// ── Module ─────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
295
372
|
|
|
296
373
|
load(...modules: Array<SyncModule>): void {
|
|
297
374
|
this.#assertNotDisposed();
|
|
@@ -314,17 +391,43 @@ class DefaultContainer implements Container {
|
|
|
314
391
|
}
|
|
315
392
|
moduleRefs.set(moduleRef, 1);
|
|
316
393
|
const builder = this.#createModuleBuilder(moduleRef);
|
|
317
|
-
|
|
394
|
+
try {
|
|
395
|
+
module[MODULE_SETUP](builder);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
throw this.#rollbackFailedLoadSync(moduleRef, error);
|
|
398
|
+
}
|
|
318
399
|
}
|
|
319
400
|
}
|
|
320
401
|
|
|
321
402
|
async loadAsync(...modules: Array<SyncModule | AsyncModule>): Promise<void> {
|
|
322
403
|
this.#assertNotDisposed();
|
|
323
|
-
|
|
404
|
+
// Deduped by identity like the sync lane, so `loadAsync(m, m)` ref-counts once.
|
|
405
|
+
for (const module of new Set(modules)) {
|
|
324
406
|
await this.#loadOneModuleAsync(module);
|
|
325
407
|
}
|
|
326
408
|
}
|
|
327
409
|
|
|
410
|
+
/** Undo a failed load — bindings registered before the throw and the ref-count both roll back. */
|
|
411
|
+
#rollbackFailedLoadSync(moduleRef: object, cause: unknown): unknown {
|
|
412
|
+
const pairs = this.#removeModuleBindings(moduleRef);
|
|
413
|
+
try {
|
|
414
|
+
this.#deactivatePairsSync(pairs);
|
|
415
|
+
} catch (rollbackError) {
|
|
416
|
+
return new AggregateError([cause, rollbackError], "module load failed, and rolling it back threw too");
|
|
417
|
+
}
|
|
418
|
+
return cause;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async #rollbackFailedLoad(moduleRef: object, cause: unknown): Promise<unknown> {
|
|
422
|
+
const pairs = this.#removeModuleBindings(moduleRef);
|
|
423
|
+
try {
|
|
424
|
+
await this.#deactivatePairs(pairs);
|
|
425
|
+
} catch (rollbackError) {
|
|
426
|
+
return new AggregateError([cause, rollbackError], "module load failed, and rolling it back threw too");
|
|
427
|
+
}
|
|
428
|
+
return cause;
|
|
429
|
+
}
|
|
430
|
+
|
|
328
431
|
async #loadOneModuleAsync(module: SyncModule | AsyncModule): Promise<void> {
|
|
329
432
|
const moduleRef = module as object;
|
|
330
433
|
const moduleRefs = (this.#moduleRefs ??= new Map());
|
|
@@ -337,14 +440,25 @@ class DefaultContainer implements Container {
|
|
|
337
440
|
|
|
338
441
|
if (isSyncModule(module)) {
|
|
339
442
|
const builder = this.#createModuleBuilder(moduleRef);
|
|
340
|
-
|
|
443
|
+
try {
|
|
444
|
+
module[MODULE_SETUP](builder);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
throw this.#rollbackFailedLoadSync(moduleRef, error);
|
|
447
|
+
}
|
|
341
448
|
} else {
|
|
342
449
|
const importPromises: Array<Promise<void>> = [];
|
|
343
450
|
const builder = this.#createAsyncModuleBuilder(moduleRef, importPromises);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
451
|
+
try {
|
|
452
|
+
await module[MODULE_SETUP](builder);
|
|
453
|
+
// Await nested async imports triggered inside the setup callback
|
|
454
|
+
if (importPromises.length > 0) {
|
|
455
|
+
await Promise.all(importPromises);
|
|
456
|
+
}
|
|
457
|
+
} catch (error) {
|
|
458
|
+
// Nested imports may still be registering — let them settle before rolling back, so no
|
|
459
|
+
// registration lands after the rejection and none becomes an unhandled rejection.
|
|
460
|
+
await Promise.allSettled(importPromises);
|
|
461
|
+
throw await this.#rollbackFailedLoad(moduleRef, error);
|
|
348
462
|
}
|
|
349
463
|
}
|
|
350
464
|
}
|
|
@@ -352,7 +466,7 @@ class DefaultContainer implements Container {
|
|
|
352
466
|
#createModuleBuilder(moduleRef: object): ModuleBuilder {
|
|
353
467
|
const registration = this.#moduleRegistration(moduleRef);
|
|
354
468
|
return {
|
|
355
|
-
bind: <
|
|
469
|
+
bind: <Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value> =>
|
|
356
470
|
this.#createBindToBuilder(token, registration),
|
|
357
471
|
import: (...modules: Array<SyncModule>): void => {
|
|
358
472
|
this.#loadSyncModules(modules);
|
|
@@ -363,7 +477,7 @@ class DefaultContainer implements Container {
|
|
|
363
477
|
#createAsyncModuleBuilder(moduleRef: object, importPromises: Array<Promise<void>>): AsyncModuleBuilder {
|
|
364
478
|
const registration = this.#moduleRegistration(moduleRef);
|
|
365
479
|
return {
|
|
366
|
-
bind: <
|
|
480
|
+
bind: <Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value> =>
|
|
367
481
|
this.#createBindToBuilder(token, registration),
|
|
368
482
|
import: (...modules: Array<SyncModule | AsyncModule>): void => {
|
|
369
483
|
for (const module of modules) {
|
|
@@ -385,28 +499,20 @@ class DefaultContainer implements Container {
|
|
|
385
499
|
this.#moduleRefs?.delete(ref);
|
|
386
500
|
const ids = this.#moduleBindingIds?.get(ref) ?? [];
|
|
387
501
|
this.#moduleBindingIds?.delete(ref);
|
|
388
|
-
const
|
|
502
|
+
const removed: Array<Binding> = [];
|
|
389
503
|
for (const id of ids) {
|
|
390
|
-
const binding = this.#registry.
|
|
504
|
+
const binding = this.#registry.removeById(id);
|
|
391
505
|
if (binding !== undefined) {
|
|
392
|
-
|
|
393
|
-
if (binding.instance !== NO_INSTANCE) {
|
|
394
|
-
pairs.push([binding, binding.instance]);
|
|
395
|
-
this.#scope.deleteSingleton(binding);
|
|
396
|
-
}
|
|
397
|
-
this.#scope.deleteScoped(binding.id);
|
|
506
|
+
removed.push(binding);
|
|
398
507
|
}
|
|
399
508
|
}
|
|
400
|
-
return
|
|
509
|
+
return this.#drainSingletons(removed);
|
|
401
510
|
}
|
|
402
511
|
|
|
403
512
|
#unloadModuleSync(ref: object): void {
|
|
404
513
|
const count = this.#moduleRefs?.get(ref) ?? 0;
|
|
405
514
|
if (count <= 1) {
|
|
406
|
-
|
|
407
|
-
for (const [binding, instance] of this.#removeModuleBindings(ref)) {
|
|
408
|
-
this.#lifecycle.runDeactivationSync(binding, instance, reader);
|
|
409
|
-
}
|
|
515
|
+
this.#deactivatePairsSync(this.#removeModuleBindings(ref));
|
|
410
516
|
} else {
|
|
411
517
|
this.#moduleRefs!.set(ref, count - 1);
|
|
412
518
|
}
|
|
@@ -422,10 +528,7 @@ class DefaultContainer implements Container {
|
|
|
422
528
|
async #unloadModuleAsync(ref: object): Promise<void> {
|
|
423
529
|
const count = this.#moduleRefs?.get(ref) ?? 0;
|
|
424
530
|
if (count <= 1) {
|
|
425
|
-
|
|
426
|
-
for (const [binding, instance] of this.#removeModuleBindings(ref)) {
|
|
427
|
-
await this.#lifecycle.runDeactivation(binding, instance, reader);
|
|
428
|
-
}
|
|
531
|
+
await this.#deactivatePairs(this.#removeModuleBindings(ref));
|
|
429
532
|
} else {
|
|
430
533
|
this.#moduleRefs!.set(ref, count - 1);
|
|
431
534
|
}
|
|
@@ -435,112 +538,128 @@ class DefaultContainer implements Container {
|
|
|
435
538
|
this.#assertNotDisposed();
|
|
436
539
|
const entries = registry.entries();
|
|
437
540
|
for (const { target, scope } of entries) {
|
|
438
|
-
|
|
439
|
-
const bindingBuilder = builder.toSelf();
|
|
440
|
-
if (scope === "singleton") {
|
|
441
|
-
bindingBuilder.singleton();
|
|
442
|
-
} else if (scope === "scoped") {
|
|
443
|
-
bindingBuilder.scoped();
|
|
444
|
-
} else {
|
|
445
|
-
bindingBuilder.transient();
|
|
446
|
-
}
|
|
541
|
+
APPLY_BINDING_SCOPE[scope](this.#createBindToBuilder(target).toSelf());
|
|
447
542
|
}
|
|
448
543
|
return entries.length;
|
|
449
544
|
}
|
|
450
545
|
|
|
451
|
-
// ── Lifecycle hooks
|
|
546
|
+
// ── Lifecycle hooks ────────────────────────────────────────────────────────────────────────────────────────────────
|
|
452
547
|
|
|
453
|
-
onActivation<
|
|
548
|
+
onActivation<Value>(token: Token<Value> | Constructor<Value>, handler: ActivationHandler<Value>): void {
|
|
454
549
|
this.#assertNotDisposed();
|
|
455
550
|
this.#lifecycle.registerActivation(token, handler);
|
|
456
551
|
}
|
|
457
552
|
|
|
458
|
-
onDeactivation<
|
|
553
|
+
onDeactivation<Value>(token: Token<Value> | Constructor<Value>, handler: DeactivationHandler<Value>): void {
|
|
459
554
|
this.#assertNotDisposed();
|
|
460
555
|
this.#lifecycle.registerDeactivation(token, handler);
|
|
461
556
|
}
|
|
462
557
|
|
|
463
|
-
// ── Resolution
|
|
558
|
+
// ── Resolution ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
464
559
|
|
|
465
|
-
resolve<
|
|
560
|
+
resolve<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value {
|
|
466
561
|
this.#assertNotDisposed();
|
|
467
562
|
const rootStack = this.#resolver.rootStack;
|
|
468
|
-
// A resolve already holding the shared
|
|
563
|
+
// A resolve already holding the shared stack means this one is nested; it mints its own.
|
|
469
564
|
if (rootStack.length !== 0) {
|
|
470
565
|
return options === undefined
|
|
471
|
-
? this.#resolver.resolveFromContext(token, []
|
|
472
|
-
: this.#resolver.resolve(token, options, []
|
|
566
|
+
? this.#resolver.resolveFromContext(token, [])
|
|
567
|
+
: this.#resolver.resolve(token, options, []);
|
|
473
568
|
}
|
|
474
569
|
if (options === undefined) {
|
|
475
|
-
return this.#resolver.resolveFromContext(token,
|
|
570
|
+
return this.#resolver.resolveFromContext(token, rootStack);
|
|
476
571
|
}
|
|
477
|
-
return this.#resolver.resolve(token, options,
|
|
572
|
+
return this.#resolver.resolve(token, options, rootStack);
|
|
478
573
|
}
|
|
479
574
|
|
|
480
|
-
resolveAsync<
|
|
575
|
+
resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value> {
|
|
481
576
|
this.#assertNotDisposed();
|
|
482
577
|
if (options === undefined) {
|
|
483
578
|
return this.#resolver.resolveAsyncFromRoot(token) as Promise<Value>;
|
|
484
579
|
}
|
|
485
|
-
return this.#resolver.resolveAsync(token, options, [],
|
|
580
|
+
return this.#resolver.resolveAsync(token, options, [], ROOT_BRANCH);
|
|
486
581
|
}
|
|
487
582
|
|
|
488
|
-
resolveOptional<
|
|
583
|
+
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined {
|
|
489
584
|
this.#assertNotDisposed();
|
|
490
585
|
const rootStack = this.#resolver.rootStack;
|
|
491
586
|
return rootStack.length === 0
|
|
492
|
-
? this.#resolver.resolveOptional(token, options,
|
|
493
|
-
: this.#resolver.resolveOptional(token, options, []
|
|
587
|
+
? this.#resolver.resolveOptional(token, options, rootStack)
|
|
588
|
+
: this.#resolver.resolveOptional(token, options, []);
|
|
494
589
|
}
|
|
495
590
|
|
|
496
|
-
resolveOptionalAsync<
|
|
591
|
+
resolveOptionalAsync<Value>(
|
|
497
592
|
token: Token<Value> | Constructor<Value>,
|
|
498
593
|
options?: ResolveOptions,
|
|
499
594
|
): Promise<Value | undefined> {
|
|
500
595
|
this.#assertNotDisposed();
|
|
501
|
-
return this.#resolver.resolveOptionalAsync(token, options, [],
|
|
596
|
+
return this.#resolver.resolveOptionalAsync(token, options, [], ROOT_BRANCH);
|
|
502
597
|
}
|
|
503
598
|
|
|
504
|
-
resolveAll<
|
|
599
|
+
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value> {
|
|
505
600
|
this.#assertNotDisposed();
|
|
506
601
|
const rootStack = this.#resolver.rootStack;
|
|
507
602
|
return rootStack.length === 0
|
|
508
|
-
? this.#resolver.resolveAll(token, options,
|
|
509
|
-
: this.#resolver.resolveAll(token, options, []
|
|
603
|
+
? this.#resolver.resolveAll(token, options, rootStack)
|
|
604
|
+
: this.#resolver.resolveAll(token, options, []);
|
|
510
605
|
}
|
|
511
606
|
|
|
512
|
-
resolveAllAsync<
|
|
513
|
-
token: Token<Value> | Constructor<Value>,
|
|
514
|
-
options?: ResolveOptions,
|
|
515
|
-
): Promise<Array<Value>> {
|
|
607
|
+
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Array<Value>> {
|
|
516
608
|
this.#assertNotDisposed();
|
|
517
|
-
return this.#resolver.resolveAllAsync(token, options, [],
|
|
609
|
+
return this.#resolver.resolveAllAsync(token, options, [], ROOT_BRANCH);
|
|
518
610
|
}
|
|
519
611
|
|
|
520
|
-
// ── Child
|
|
612
|
+
// ── Child ──────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
521
613
|
|
|
522
614
|
createChild(): Container {
|
|
523
615
|
this.#assertNotDisposed();
|
|
524
616
|
return new DefaultContainer(this);
|
|
525
617
|
}
|
|
526
618
|
|
|
527
|
-
// ── Dispose
|
|
619
|
+
// ── Dispose ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
528
620
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
621
|
+
dispose(): Promise<void> {
|
|
622
|
+
// One teardown, shared: a second caller awaits the same run instead of returning early while
|
|
623
|
+
// hooks from the first are still executing.
|
|
624
|
+
return (this.#disposePromise ??= this.#runDispose());
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async #runDispose(): Promise<void> {
|
|
533
628
|
this.#disposed = true;
|
|
629
|
+
// Refuses new materializations immediately; in-flight ones are drained just below.
|
|
630
|
+
this.#scope.markClosed();
|
|
631
|
+
await this.#scope.settleInflight();
|
|
534
632
|
|
|
535
|
-
// Deactivate all singletons in this container (own only)
|
|
536
633
|
const reader = this.#getMetadataReader();
|
|
634
|
+
const errors: Array<unknown> = [];
|
|
537
635
|
// Iterate a copy: a deactivation handler is user code, and the live list is what
|
|
538
|
-
// materializing or dropping a singleton mutates.
|
|
539
|
-
|
|
540
|
-
|
|
636
|
+
// materializing or dropping a singleton mutates. Reverse materialization order, so a
|
|
637
|
+
// dependent tears down before the dependencies it may still reach through.
|
|
638
|
+
const cached = this.#scope.cachedSingletons().slice();
|
|
639
|
+
for (let index = cached.length - 1; index >= 0; index -= 1) {
|
|
640
|
+
const binding = cached[index]!;
|
|
641
|
+
try {
|
|
642
|
+
await this.#lifecycle.runDeactivation(binding, binding.instance, reader);
|
|
643
|
+
} catch (error) {
|
|
644
|
+
errors.push(error);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// A constant never reaches the singleton cache unless activation put it there, so its hook is
|
|
648
|
+
// owed from the registry instead — and only a container that has held one pays for looking.
|
|
649
|
+
if (this.#registry.hasHeldConstantBinding) {
|
|
650
|
+
for (const binding of this.#registry.allBindings()) {
|
|
651
|
+
if (binding.instance === NO_INSTANCE && this.#owesConstantDeactivation(binding)) {
|
|
652
|
+
try {
|
|
653
|
+
await this.#lifecycle.runDeactivation(binding, binding.value, reader);
|
|
654
|
+
} catch (error) {
|
|
655
|
+
errors.push(error);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
541
659
|
}
|
|
542
660
|
|
|
543
661
|
this.#scope.clearAll();
|
|
662
|
+
throwCollected(errors, "dispose() completed, but deactivation hooks threw");
|
|
544
663
|
}
|
|
545
664
|
|
|
546
665
|
[Symbol.asyncDispose](): Promise<void> {
|
|
@@ -551,7 +670,7 @@ class DefaultContainer implements Container {
|
|
|
551
670
|
throw new SyncDisposalNotSupportedError();
|
|
552
671
|
}
|
|
553
672
|
|
|
554
|
-
// ── Initialization
|
|
673
|
+
// ── Initialization ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
555
674
|
|
|
556
675
|
async initializeAsync(): Promise<void> {
|
|
557
676
|
this.#assertNotDisposed();
|
|
@@ -565,17 +684,22 @@ class DefaultContainer implements Container {
|
|
|
565
684
|
if (binding.predicate !== undefined) {
|
|
566
685
|
continue;
|
|
567
686
|
}
|
|
568
|
-
//
|
|
569
|
-
|
|
687
|
+
// A constant is already its own instance, so only an activation hook gives the warm-up
|
|
688
|
+
// something to run — and a container-level hook counts as one just as a per-binding hook does.
|
|
689
|
+
if (
|
|
690
|
+
binding.kind === "constant" &&
|
|
691
|
+
binding.onActivation === undefined &&
|
|
692
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)
|
|
693
|
+
) {
|
|
570
694
|
continue;
|
|
571
695
|
}
|
|
572
696
|
const slotOptions = bindingSlotToResolveOptions(binding.slot);
|
|
573
|
-
await this.resolveAsync(binding.token
|
|
697
|
+
await this.resolveAsync(binding.token, slotOptions);
|
|
574
698
|
}
|
|
575
699
|
}
|
|
576
700
|
}
|
|
577
701
|
|
|
578
|
-
// ── Validate
|
|
702
|
+
// ── Validate ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
579
703
|
|
|
580
704
|
validate(): void {
|
|
581
705
|
this.#assertNotDisposed();
|
|
@@ -588,6 +712,54 @@ class DefaultContainer implements Container {
|
|
|
588
712
|
}
|
|
589
713
|
this.#validateSingletonBindingGraph(binding, reader);
|
|
590
714
|
}
|
|
715
|
+
|
|
716
|
+
for (const [hookToken, phase] of this.#lifecycle.hookedTokens()) {
|
|
717
|
+
if (!this.#isBoundInChain(hookToken)) {
|
|
718
|
+
throw new UnreachableLifecycleHookError(tokenName(hookToken), phase);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
this.#validateConstraintRequirements(allBindings);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/** A constraint waiting on a slot name no binding declares can never hold. */
|
|
726
|
+
#validateConstraintRequirements(allBindings: ReadonlyArray<Binding>): void {
|
|
727
|
+
let declaredSlotNames: Set<string> | undefined;
|
|
728
|
+
|
|
729
|
+
for (const binding of allBindings) {
|
|
730
|
+
const { predicate } = binding;
|
|
731
|
+
if (predicate === undefined) {
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
const requirement = constraintRequirementOf(predicate);
|
|
735
|
+
if (requirement === undefined) {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
declaredSlotNames ??= this.#slotNamesInChain();
|
|
739
|
+
if (!declaredSlotNames.has(requirement.name)) {
|
|
740
|
+
throw new UnreachableConstraintError(tokenName(binding.token), requirement.name, requirement.helperName);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** Every slot name declared anywhere a resolve through this container could reach. */
|
|
746
|
+
#slotNamesInChain(): Set<string> {
|
|
747
|
+
const names = this.#parent === undefined ? new Set<string>() : this.#parent.#slotNamesInChain();
|
|
748
|
+
for (const binding of this.#registry.allBindings()) {
|
|
749
|
+
if (binding.slot.name !== undefined) {
|
|
750
|
+
names.add(binding.slot.name);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return names;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Ancestors count: a parent-owned binding is one this container can still resolve through.
|
|
757
|
+
#isBoundInChain(token: Token<unknown> | Constructor): boolean {
|
|
758
|
+
if (this.#registry.has(token)) {
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
const parent = this.#parent;
|
|
762
|
+
return parent !== undefined && parent.#isBoundInChain(token);
|
|
591
763
|
}
|
|
592
764
|
|
|
593
765
|
#isSingletonStaticAnalyzableBinding(binding: Binding): boolean {
|
|
@@ -599,13 +771,13 @@ class DefaultContainer implements Container {
|
|
|
599
771
|
|
|
600
772
|
/**
|
|
601
773
|
* DFS over explicit constructor / `toResolved*` dependency edges. Follows `toAlias` chains to the
|
|
602
|
-
* terminal binding for scope checks
|
|
774
|
+
* terminal binding for scope checks.
|
|
603
775
|
*
|
|
604
776
|
* @remarks A `toDynamic*` dependency is scope-checked like any other — its declared scope is what
|
|
605
777
|
* makes it captive — but the DFS does not descend into the factory, whose body is opaque.
|
|
606
778
|
*/
|
|
607
779
|
#validateSingletonBindingGraph(root: Binding, reader: MetadataReader): void {
|
|
608
|
-
const rootName = tokenName(root.token
|
|
780
|
+
const rootName = tokenName(root.token);
|
|
609
781
|
|
|
610
782
|
const dfs = (current: Binding, pathNames: Array<string>, pathBindingIds: Set<BindingIdentifier>): void => {
|
|
611
783
|
if (pathBindingIds.has(current.id)) {
|
|
@@ -659,8 +831,8 @@ class DefaultContainer implements Container {
|
|
|
659
831
|
throw new CircularDependencyError(cyclePath);
|
|
660
832
|
}
|
|
661
833
|
seenAliasIds.add(current.id);
|
|
662
|
-
cyclePath.push(tokenName(current.token
|
|
663
|
-
const nextToken = current.target
|
|
834
|
+
cyclePath.push(tokenName(current.token));
|
|
835
|
+
const nextToken = current.target;
|
|
664
836
|
const next = this.#resolver.peekBindingForValidate(nextToken, options);
|
|
665
837
|
if (next === undefined) {
|
|
666
838
|
return undefined;
|
|
@@ -682,7 +854,7 @@ class DefaultContainer implements Container {
|
|
|
682
854
|
/** What a binding declares up front — a class's params, a factory's descriptors, else nothing. */
|
|
683
855
|
#staticDependencies(binding: Binding, reader: MetadataReader): ReadonlyArray<DependencySlot> {
|
|
684
856
|
if (binding.kind === "class") {
|
|
685
|
-
return reader.getConstructorMetadata(binding.target
|
|
857
|
+
return reader.getConstructorMetadata(binding.target)?.params ?? [];
|
|
686
858
|
}
|
|
687
859
|
if (binding.kind === "resolved" || binding.kind === "resolved-async") {
|
|
688
860
|
return binding.deps;
|
|
@@ -705,7 +877,7 @@ class DefaultContainer implements Container {
|
|
|
705
877
|
for (const candidate of this.#peekDependencyCandidates(dep, depOptions)) {
|
|
706
878
|
const terminal = this.#followAliasChainToTerminal(candidate, depOptions);
|
|
707
879
|
if (terminal !== undefined) {
|
|
708
|
-
edges.push({ terminal, depTokenName: tokenName(terminal.token
|
|
880
|
+
edges.push({ terminal, depTokenName: tokenName(terminal.token) });
|
|
709
881
|
}
|
|
710
882
|
}
|
|
711
883
|
}
|
|
@@ -713,7 +885,7 @@ class DefaultContainer implements Container {
|
|
|
713
885
|
return edges;
|
|
714
886
|
}
|
|
715
887
|
|
|
716
|
-
// ── Introspection
|
|
888
|
+
// ── Introspection ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
717
889
|
|
|
718
890
|
has(token: Token<unknown> | Constructor, options?: ResolveOptions): boolean {
|
|
719
891
|
this.#assertNotDisposed();
|
|
@@ -725,7 +897,7 @@ class DefaultContainer implements Container {
|
|
|
725
897
|
return this.#getInspector().hasOwn(token, options);
|
|
726
898
|
}
|
|
727
899
|
|
|
728
|
-
lookupBindings<
|
|
900
|
+
lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot> {
|
|
729
901
|
this.#assertNotDisposed();
|
|
730
902
|
return this.#getInspector().lookupBindings(token);
|
|
731
903
|
}
|
|
@@ -745,7 +917,7 @@ class DefaultContainer implements Container {
|
|
|
745
917
|
);
|
|
746
918
|
}
|
|
747
919
|
|
|
748
|
-
// ── Internal
|
|
920
|
+
// ── Internal ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
749
921
|
|
|
750
922
|
#assertNotDisposed(): void {
|
|
751
923
|
if (this.#disposed) {
|
|
@@ -754,16 +926,29 @@ class DefaultContainer implements Container {
|
|
|
754
926
|
}
|
|
755
927
|
}
|
|
756
928
|
|
|
757
|
-
|
|
929
|
+
/** Reports collected hook failures once teardown finished: the one error as itself, several aggregated. */
|
|
930
|
+
function throwCollected(errors: ReadonlyArray<unknown>, message: string): void {
|
|
931
|
+
if (errors.length === 0) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (errors.length === 1) {
|
|
935
|
+
throw errors[0];
|
|
936
|
+
}
|
|
937
|
+
throw new AggregateError(errors, message);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// ── Container static ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
758
941
|
|
|
759
942
|
/**
|
|
760
943
|
* @since 0.3.16-canary.0
|
|
761
944
|
*/
|
|
762
|
-
export const Container: ContainerStatic
|
|
763
|
-
create(): Container {
|
|
764
|
-
return new DefaultContainer();
|
|
945
|
+
export const Container: ContainerStatic = {
|
|
946
|
+
create(options?: ContainerOptions): Container {
|
|
947
|
+
return new DefaultContainer(undefined, options);
|
|
765
948
|
},
|
|
766
949
|
|
|
950
|
+
// Variadic modules leave no room for an options argument. A container that needs both is
|
|
951
|
+
// `Container.create(options)` followed by `load(...)`, which is what these two do anyway.
|
|
767
952
|
fromModules(...modules: Array<SyncModule>): Container {
|
|
768
953
|
const container = new DefaultContainer();
|
|
769
954
|
container.load(...modules);
|