@codefast/di 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +712 -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 +62 -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 +281 -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 +53 -0
- package/dist/core/constraint-requirement.d.ts.map +1 -0
- package/dist/core/constraint-requirement.js +62 -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 +96 -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} +9 -5
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/{binding-lookup-cache.js → cache/binding-lookup-cache.js} +55 -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} +37 -7
- package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -0
- package/dist/resolution/plan/instantiation-plan.js +419 -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 +581 -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 +79 -44
- package/src/container/container.ts +357 -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 +94 -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 +158 -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} +63 -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 +615 -0
- package/src/resolution/resolver.ts +663 -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
|
@@ -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 { constraintRequirementsOf } 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,56 @@ 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 requirements = constraintRequirementsOf(predicate);
|
|
735
|
+
if (requirements.length === 0) {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
declaredSlotNames ??= this.#slotNamesInChain();
|
|
739
|
+
for (const requirement of requirements) {
|
|
740
|
+
if (!declaredSlotNames.has(requirement.name)) {
|
|
741
|
+
throw new UnreachableConstraintError(tokenName(binding.token), requirement.name, requirement.helperName);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/** Every slot name declared anywhere a resolve through this container could reach. */
|
|
748
|
+
#slotNamesInChain(): Set<string> {
|
|
749
|
+
const names = this.#parent === undefined ? new Set<string>() : this.#parent.#slotNamesInChain();
|
|
750
|
+
for (const binding of this.#registry.allBindings()) {
|
|
751
|
+
if (binding.slot.name !== undefined) {
|
|
752
|
+
names.add(binding.slot.name);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return names;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Ancestors count: a parent-owned binding is one this container can still resolve through.
|
|
759
|
+
#isBoundInChain(token: Token<unknown> | Constructor): boolean {
|
|
760
|
+
if (this.#registry.has(token)) {
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
const parent = this.#parent;
|
|
764
|
+
return parent !== undefined && parent.#isBoundInChain(token);
|
|
591
765
|
}
|
|
592
766
|
|
|
593
767
|
#isSingletonStaticAnalyzableBinding(binding: Binding): boolean {
|
|
@@ -599,13 +773,13 @@ class DefaultContainer implements Container {
|
|
|
599
773
|
|
|
600
774
|
/**
|
|
601
775
|
* DFS over explicit constructor / `toResolved*` dependency edges. Follows `toAlias` chains to the
|
|
602
|
-
* terminal binding for scope checks
|
|
776
|
+
* terminal binding for scope checks.
|
|
603
777
|
*
|
|
604
778
|
* @remarks A `toDynamic*` dependency is scope-checked like any other — its declared scope is what
|
|
605
779
|
* makes it captive — but the DFS does not descend into the factory, whose body is opaque.
|
|
606
780
|
*/
|
|
607
781
|
#validateSingletonBindingGraph(root: Binding, reader: MetadataReader): void {
|
|
608
|
-
const rootName = tokenName(root.token
|
|
782
|
+
const rootName = tokenName(root.token);
|
|
609
783
|
|
|
610
784
|
const dfs = (current: Binding, pathNames: Array<string>, pathBindingIds: Set<BindingIdentifier>): void => {
|
|
611
785
|
if (pathBindingIds.has(current.id)) {
|
|
@@ -659,8 +833,8 @@ class DefaultContainer implements Container {
|
|
|
659
833
|
throw new CircularDependencyError(cyclePath);
|
|
660
834
|
}
|
|
661
835
|
seenAliasIds.add(current.id);
|
|
662
|
-
cyclePath.push(tokenName(current.token
|
|
663
|
-
const nextToken = current.target
|
|
836
|
+
cyclePath.push(tokenName(current.token));
|
|
837
|
+
const nextToken = current.target;
|
|
664
838
|
const next = this.#resolver.peekBindingForValidate(nextToken, options);
|
|
665
839
|
if (next === undefined) {
|
|
666
840
|
return undefined;
|
|
@@ -682,7 +856,7 @@ class DefaultContainer implements Container {
|
|
|
682
856
|
/** What a binding declares up front — a class's params, a factory's descriptors, else nothing. */
|
|
683
857
|
#staticDependencies(binding: Binding, reader: MetadataReader): ReadonlyArray<DependencySlot> {
|
|
684
858
|
if (binding.kind === "class") {
|
|
685
|
-
return reader.getConstructorMetadata(binding.target
|
|
859
|
+
return reader.getConstructorMetadata(binding.target)?.params ?? [];
|
|
686
860
|
}
|
|
687
861
|
if (binding.kind === "resolved" || binding.kind === "resolved-async") {
|
|
688
862
|
return binding.deps;
|
|
@@ -705,7 +879,7 @@ class DefaultContainer implements Container {
|
|
|
705
879
|
for (const candidate of this.#peekDependencyCandidates(dep, depOptions)) {
|
|
706
880
|
const terminal = this.#followAliasChainToTerminal(candidate, depOptions);
|
|
707
881
|
if (terminal !== undefined) {
|
|
708
|
-
edges.push({ terminal, depTokenName: tokenName(terminal.token
|
|
882
|
+
edges.push({ terminal, depTokenName: tokenName(terminal.token) });
|
|
709
883
|
}
|
|
710
884
|
}
|
|
711
885
|
}
|
|
@@ -713,7 +887,7 @@ class DefaultContainer implements Container {
|
|
|
713
887
|
return edges;
|
|
714
888
|
}
|
|
715
889
|
|
|
716
|
-
// ── Introspection
|
|
890
|
+
// ── Introspection ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
717
891
|
|
|
718
892
|
has(token: Token<unknown> | Constructor, options?: ResolveOptions): boolean {
|
|
719
893
|
this.#assertNotDisposed();
|
|
@@ -725,7 +899,7 @@ class DefaultContainer implements Container {
|
|
|
725
899
|
return this.#getInspector().hasOwn(token, options);
|
|
726
900
|
}
|
|
727
901
|
|
|
728
|
-
lookupBindings<
|
|
902
|
+
lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot> {
|
|
729
903
|
this.#assertNotDisposed();
|
|
730
904
|
return this.#getInspector().lookupBindings(token);
|
|
731
905
|
}
|
|
@@ -745,7 +919,7 @@ class DefaultContainer implements Container {
|
|
|
745
919
|
);
|
|
746
920
|
}
|
|
747
921
|
|
|
748
|
-
// ── Internal
|
|
922
|
+
// ── Internal ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
749
923
|
|
|
750
924
|
#assertNotDisposed(): void {
|
|
751
925
|
if (this.#disposed) {
|
|
@@ -754,16 +928,29 @@ class DefaultContainer implements Container {
|
|
|
754
928
|
}
|
|
755
929
|
}
|
|
756
930
|
|
|
757
|
-
|
|
931
|
+
/** Reports collected hook failures once teardown finished: the one error as itself, several aggregated. */
|
|
932
|
+
function throwCollected(errors: ReadonlyArray<unknown>, message: string): void {
|
|
933
|
+
if (errors.length === 0) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (errors.length === 1) {
|
|
937
|
+
throw errors[0];
|
|
938
|
+
}
|
|
939
|
+
throw new AggregateError(errors, message);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// ── Container static ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
758
943
|
|
|
759
944
|
/**
|
|
760
945
|
* @since 0.3.16-canary.0
|
|
761
946
|
*/
|
|
762
|
-
export const Container: ContainerStatic
|
|
763
|
-
create(): Container {
|
|
764
|
-
return new DefaultContainer();
|
|
947
|
+
export const Container: ContainerStatic = {
|
|
948
|
+
create(options?: ContainerOptions): Container {
|
|
949
|
+
return new DefaultContainer(undefined, options);
|
|
765
950
|
},
|
|
766
951
|
|
|
952
|
+
// Variadic modules leave no room for an options argument. A container that needs both is
|
|
953
|
+
// `Container.create(options)` followed by `load(...)`, which is what these two do anyway.
|
|
767
954
|
fromModules(...modules: Array<SyncModule>): Container {
|
|
768
955
|
const container = new DefaultContainer();
|
|
769
956
|
container.load(...modules);
|