@codefast/di 0.7.0 → 0.8.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 +33 -0
- package/README.md +1 -1
- package/dist/ambient/active-container.d.ts +4 -0
- package/dist/ambient/active-container.d.ts.map +1 -1
- package/dist/ambient/active-container.js +4 -0
- package/dist/ambient/active-container.js.map +1 -1
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +4 -5
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts +6 -0
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +5 -4
- package/dist/container/container.js.map +1 -1
- package/dist/core/binding.d.ts +15 -2
- package/dist/core/binding.d.ts.map +1 -1
- package/dist/core/binding.js +29 -4
- package/dist/core/binding.js.map +1 -1
- package/dist/core/module.d.ts +16 -0
- package/dist/core/module.d.ts.map +1 -1
- package/dist/core/module.js +8 -0
- package/dist/core/module.js.map +1 -1
- package/dist/core/registry.d.ts +2 -3
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +16 -61
- package/dist/core/registry.js.map +1 -1
- package/dist/core/tag.d.ts +26 -0
- package/dist/core/tag.d.ts.map +1 -1
- package/dist/core/tag.js +54 -11
- package/dist/core/tag.js.map +1 -1
- package/dist/core/token.d.ts +6 -0
- package/dist/core/token.d.ts.map +1 -1
- package/dist/core/token.js +4 -0
- package/dist/core/token.js.map +1 -1
- package/dist/core/types.d.ts +20 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/decorators/inject.d.ts +2 -0
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js +2 -0
- package/dist/decorators/inject.js.map +1 -1
- package/dist/decorators/injectable.d.ts +6 -0
- package/dist/decorators/injectable.d.ts.map +1 -1
- package/dist/decorators/injectable.js +4 -0
- package/dist/decorators/injectable.js.map +1 -1
- package/dist/decorators/lifecycle-decorators.d.ts +4 -0
- package/dist/decorators/lifecycle-decorators.d.ts.map +1 -1
- package/dist/decorators/lifecycle-decorators.js +4 -0
- package/dist/decorators/lifecycle-decorators.js.map +1 -1
- package/dist/errors/errors.d.ts +34 -0
- package/dist/errors/errors.d.ts.map +1 -1
- package/dist/errors/errors.js +60 -3
- package/dist/errors/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/injection/descriptor.d.ts +12 -0
- package/dist/injection/descriptor.d.ts.map +1 -1
- package/dist/injection/descriptor.js +8 -0
- package/dist/injection/descriptor.js.map +1 -1
- package/dist/injection/resolve-options.d.ts +17 -12
- package/dist/injection/resolve-options.d.ts.map +1 -1
- package/dist/injection/resolve-options.js +69 -15
- package/dist/injection/resolve-options.js.map +1 -1
- package/dist/introspection/dependency-graph.d.ts +6 -0
- package/dist/introspection/dependency-graph.d.ts.map +1 -1
- package/dist/introspection/dependency-graph.js +24 -9
- package/dist/introspection/dependency-graph.js.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.d.ts +8 -0
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.js +2 -0
- package/dist/introspection/graph-adapters/cytoscape.js.map +1 -1
- package/dist/introspection/graph-adapters/dot.d.ts +2 -0
- package/dist/introspection/graph-adapters/dot.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/dot.js +2 -0
- package/dist/introspection/graph-adapters/dot.js.map +1 -1
- package/dist/introspection/graph-adapters/reactflow.d.ts +8 -0
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/reactflow.js +2 -0
- package/dist/introspection/graph-adapters/reactflow.js.map +1 -1
- package/dist/introspection/inspector.d.ts +6 -0
- package/dist/introspection/inspector.d.ts.map +1 -1
- package/dist/introspection/inspector.js +2 -0
- package/dist/introspection/inspector.js.map +1 -1
- package/dist/lifecycle/lifecycle-manager.d.ts +2 -0
- package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -1
- package/dist/lifecycle/lifecycle-manager.js +2 -0
- package/dist/lifecycle/lifecycle-manager.js.map +1 -1
- package/dist/lifecycle/scope-manager.d.ts +2 -0
- package/dist/lifecycle/scope-manager.d.ts.map +1 -1
- package/dist/lifecycle/scope-manager.js +2 -0
- package/dist/lifecycle/scope-manager.js.map +1 -1
- package/dist/metadata/metadata-keys.d.ts +6 -0
- package/dist/metadata/metadata-keys.d.ts.map +1 -1
- package/dist/metadata/metadata-keys.js +6 -0
- package/dist/metadata/metadata-keys.js.map +1 -1
- package/dist/metadata/metadata-reader-token.d.ts +2 -0
- package/dist/metadata/metadata-reader-token.d.ts.map +1 -1
- package/dist/metadata/metadata-reader-token.js +2 -0
- package/dist/metadata/metadata-reader-token.js.map +1 -1
- package/dist/metadata/metadata-types.d.ts +6 -0
- package/dist/metadata/metadata-types.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.d.ts +4 -0
- package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.js +4 -0
- package/dist/metadata/symbol-metadata-reader.js.map +1 -1
- package/dist/resolution/cache/activation-need.d.ts +2 -0
- package/dist/resolution/cache/activation-need.d.ts.map +1 -1
- package/dist/resolution/cache/activation-need.js +2 -0
- package/dist/resolution/cache/activation-need.js.map +1 -1
- package/dist/resolution/cache/binding-lookup-cache.d.ts +2 -2
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -1
- package/dist/resolution/cache/binding-lookup-cache.js +2 -37
- package/dist/resolution/cache/binding-lookup-cache.js.map +1 -1
- package/dist/resolution/cache/class-introspector.d.ts +2 -0
- package/dist/resolution/cache/class-introspector.d.ts.map +1 -1
- package/dist/resolution/cache/class-introspector.js +2 -0
- package/dist/resolution/cache/class-introspector.js.map +1 -1
- package/dist/resolution/context.d.ts +6 -0
- package/dist/resolution/context.d.ts.map +1 -1
- package/dist/resolution/context.js +4 -0
- package/dist/resolution/context.js.map +1 -1
- package/dist/resolution/plan/instantiation-plan.d.ts +6 -12
- package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -1
- package/dist/resolution/plan/instantiation-plan.js +11 -27
- package/dist/resolution/plan/instantiation-plan.js.map +1 -1
- package/dist/resolution/resolver.d.ts +14 -5
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +108 -141
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/select/binding-select.d.ts +4 -5
- package/dist/resolution/select/binding-select.d.ts.map +1 -1
- package/dist/resolution/select/binding-select.js +18 -20
- package/dist/resolution/select/binding-select.js.map +1 -1
- package/dist/resolution/select/constraints.d.ts +16 -0
- package/dist/resolution/select/constraints.d.ts.map +1 -1
- package/dist/resolution/select/constraints.js +16 -0
- package/dist/resolution/select/constraints.js.map +1 -1
- package/package.json +13 -6
- package/src/ambient/active-container.ts +4 -0
- package/src/container/binding-builders.ts +10 -5
- package/src/container/container.ts +9 -4
- package/src/core/binding.ts +33 -5
- package/src/core/module.ts +16 -0
- package/src/core/registry.ts +16 -66
- package/src/core/tag.ts +71 -17
- package/src/core/token.ts +6 -0
- package/src/core/types.ts +20 -0
- package/src/decorators/inject.ts +2 -0
- package/src/decorators/injectable.ts +8 -0
- package/src/decorators/lifecycle-decorators.ts +4 -0
- package/src/errors/errors.ts +62 -5
- package/src/index.ts +1 -1
- package/src/injection/descriptor.ts +12 -0
- package/src/injection/resolve-options.ts +75 -18
- package/src/introspection/dependency-graph.ts +29 -9
- package/src/introspection/graph-adapters/cytoscape.ts +8 -0
- package/src/introspection/graph-adapters/dot.ts +2 -0
- package/src/introspection/graph-adapters/reactflow.ts +8 -0
- package/src/introspection/inspector.ts +6 -0
- package/src/lifecycle/lifecycle-manager.ts +2 -0
- package/src/lifecycle/scope-manager.ts +2 -0
- package/src/metadata/metadata-keys.ts +6 -0
- package/src/metadata/metadata-reader-token.ts +2 -0
- package/src/metadata/metadata-types.ts +6 -0
- package/src/metadata/symbol-metadata-reader.ts +4 -0
- package/src/resolution/cache/activation-need.ts +2 -0
- package/src/resolution/cache/binding-lookup-cache.ts +2 -40
- package/src/resolution/cache/class-introspector.ts +2 -0
- package/src/resolution/context.ts +6 -0
- package/src/resolution/plan/instantiation-plan.ts +15 -38
- package/src/resolution/resolver.ts +122 -141
- package/src/resolution/select/binding-select.ts +18 -21
- package/src/resolution/select/constraints.ts +16 -0
|
@@ -50,6 +50,8 @@ import { DependencyResolver } from "#/resolution/resolver";
|
|
|
50
50
|
// ── Container interface ──────────────────────────────────────────────────────────────────────────────────────────────
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
+
* The public surface: binding, resolving, modules, lifecycle hooks, child scopes, and disposal.
|
|
54
|
+
*
|
|
53
55
|
* @since 0.3.16-canary.0
|
|
54
56
|
*/
|
|
55
57
|
export interface Container {
|
|
@@ -114,6 +116,8 @@ export interface ContainerOptions {
|
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
/**
|
|
119
|
+
* The factory surface the `Container` companion object exposes.
|
|
120
|
+
*
|
|
117
121
|
* @since 0.3.16-canary.0
|
|
118
122
|
*/
|
|
119
123
|
export interface ContainerStatic {
|
|
@@ -182,9 +186,6 @@ class DefaultContainer implements Container {
|
|
|
182
186
|
if (this.#moduleRefs !== undefined || this.#moduleBindingIds !== undefined) {
|
|
183
187
|
builtSubsystems.push("container.moduleTables");
|
|
184
188
|
}
|
|
185
|
-
if (this.#registry.isNamedIndexBuilt) {
|
|
186
|
-
builtSubsystems.push("registry.namedIndex");
|
|
187
|
-
}
|
|
188
189
|
if (this.#registry.isTaggedIndexBuilt) {
|
|
189
190
|
builtSubsystems.push("registry.taggedIndex");
|
|
190
191
|
}
|
|
@@ -695,8 +696,10 @@ class DefaultContainer implements Container {
|
|
|
695
696
|
) {
|
|
696
697
|
continue;
|
|
697
698
|
}
|
|
699
|
+
// Warm the binding itself — re-selecting by its own criteria could pick a different
|
|
700
|
+
// candidate whose criteria are a subset of them.
|
|
698
701
|
const slotOptions = bindingSlotToResolveOptions(binding.slot);
|
|
699
|
-
await this.
|
|
702
|
+
await this.#resolver.warmBindingAsync(binding, slotOptions);
|
|
700
703
|
}
|
|
701
704
|
}
|
|
702
705
|
}
|
|
@@ -944,6 +947,8 @@ function throwCollected(errors: ReadonlyArray<unknown>, message: string): void {
|
|
|
944
947
|
// ── Container static ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
945
948
|
|
|
946
949
|
/**
|
|
950
|
+
* The companion factory that creates containers, empty or preloaded from modules.
|
|
951
|
+
*
|
|
947
952
|
* @since 0.3.16-canary.0
|
|
948
953
|
*/
|
|
949
954
|
export const Container: ContainerStatic = {
|
package/src/core/binding.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BindingTag, TagKeyMask } from "#/core/tag";
|
|
2
|
-
import { NO_TAG_KEYS } from "#/core/tag";
|
|
2
|
+
import { NO_TAG_KEYS, slotName, tagKeyMaskOf } from "#/core/tag";
|
|
3
3
|
import type { Token } from "#/core/token";
|
|
4
4
|
import type {
|
|
5
5
|
ActivationHandler,
|
|
@@ -16,24 +16,48 @@ import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue
|
|
|
16
16
|
// ── BindingSlot ──────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* The
|
|
19
|
+
* The criterion set a binding registers under and a request matches against.
|
|
20
20
|
*
|
|
21
21
|
* @since 0.3.16-canary.0
|
|
22
22
|
*/
|
|
23
23
|
export interface BindingSlot {
|
|
24
|
+
/** Derived view of the reserved `slotName` criterion — `undefined` when the slot carries none. */
|
|
24
25
|
readonly name: string | undefined;
|
|
26
|
+
/** The whole criterion set, the reserved name criterion included. */
|
|
25
27
|
readonly tags: ReadonlyArray<BindingTag>;
|
|
26
28
|
/** OR of this slot's tag keys, so the subset test is one word compare. */
|
|
27
29
|
readonly keyMask: TagKeyMask;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
|
-
*
|
|
33
|
+
* Builds a slot from its criterion set, deriving the name view and key mask.
|
|
34
|
+
*
|
|
35
|
+
* @remarks The one place the derived `name` is computed — a slot assembled any other way can carry
|
|
36
|
+
* a name criterion the readers of the view never see.
|
|
37
|
+
*
|
|
38
|
+
* @since 0.8.0
|
|
39
|
+
*/
|
|
40
|
+
export function createBindingSlot(tags: ReadonlyArray<BindingTag>): BindingSlot {
|
|
41
|
+
let name: string | undefined;
|
|
42
|
+
for (const criterion of tags) {
|
|
43
|
+
if (criterion.key === slotName) {
|
|
44
|
+
name = criterion.value as string;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Frozen like DEFAULT_BINDING_SLOT's list: frames and snapshots alias a slot's tags.
|
|
49
|
+
return { name, tags: Object.freeze([...tags]), keyMask: tagKeyMaskOf(tags) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns whether two slots carry the same criterion set, in any order.
|
|
54
|
+
*
|
|
55
|
+
* @remarks The derived `name` is not compared — the criterion set alone is the identity.
|
|
32
56
|
*
|
|
33
57
|
* @since 0.3.16-canary.0
|
|
34
58
|
*/
|
|
35
59
|
export function bindingSlotEquals(left: BindingSlot, right: BindingSlot): boolean {
|
|
36
|
-
if (left.
|
|
60
|
+
if (left.keyMask !== right.keyMask || left.tags.length !== right.tags.length) {
|
|
37
61
|
return false;
|
|
38
62
|
}
|
|
39
63
|
for (const criterion of left.tags) {
|
|
@@ -68,7 +92,7 @@ export const DEFAULT_BINDING_SLOT: BindingSlot = { name: undefined, tags: Object
|
|
|
68
92
|
* @since 0.3.16-canary.0
|
|
69
93
|
*/
|
|
70
94
|
export function bindingSlotToString(slot: BindingSlot): string {
|
|
71
|
-
if (slot.
|
|
95
|
+
if (slot.tags.length === 0) {
|
|
72
96
|
return "default";
|
|
73
97
|
}
|
|
74
98
|
const parts: Array<string> = [];
|
|
@@ -76,6 +100,10 @@ export function bindingSlotToString(slot: BindingSlot): string {
|
|
|
76
100
|
parts.push(`name:${slot.name}`);
|
|
77
101
|
}
|
|
78
102
|
for (const criterion of slot.tags) {
|
|
103
|
+
// The reserved criterion already printed as the `name:` part.
|
|
104
|
+
if (criterion.key === slotName) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
79
107
|
parts.push(`tag:${criterion.key.name}=${String(criterion.value)}`);
|
|
80
108
|
}
|
|
81
109
|
return parts.join(",");
|
package/src/core/module.ts
CHANGED
|
@@ -16,6 +16,8 @@ const ASYNC_MODULE_BRAND: unique symbol = Symbol("di:async-module");
|
|
|
16
16
|
export const MODULE_SETUP: unique symbol = Symbol("di:module-setup");
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
+
* A named, reusable group of bindings a container applies synchronously via `load()`.
|
|
20
|
+
*
|
|
19
21
|
* @since 0.3.16-canary.0
|
|
20
22
|
*/
|
|
21
23
|
export interface SyncModule {
|
|
@@ -25,6 +27,8 @@ export interface SyncModule {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
30
|
+
* A named group of bindings whose setup is async, applied via `loadAsync()`.
|
|
31
|
+
*
|
|
28
32
|
* @since 0.3.16-canary.0
|
|
29
33
|
*/
|
|
30
34
|
export interface AsyncModule {
|
|
@@ -36,6 +40,8 @@ export interface AsyncModule {
|
|
|
36
40
|
// ── Builder interfaces ───────────────────────────────────────────────────────────────────────────────────────────────
|
|
37
41
|
|
|
38
42
|
/**
|
|
43
|
+
* The binding surface a sync module's setup callback receives.
|
|
44
|
+
*
|
|
39
45
|
* @since 0.3.16-canary.0
|
|
40
46
|
*/
|
|
41
47
|
export interface ModuleBuilder {
|
|
@@ -44,6 +50,8 @@ export interface ModuleBuilder {
|
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/**
|
|
53
|
+
* The binding surface an async module's setup callback receives; its `import` accepts async modules too.
|
|
54
|
+
*
|
|
47
55
|
* @since 0.3.16-canary.0
|
|
48
56
|
*/
|
|
49
57
|
export interface AsyncModuleBuilder {
|
|
@@ -54,6 +62,8 @@ export interface AsyncModuleBuilder {
|
|
|
54
62
|
// ── Static factories ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
55
63
|
|
|
56
64
|
/**
|
|
65
|
+
* The companion factory that creates `SyncModule` values.
|
|
66
|
+
*
|
|
57
67
|
* @since 0.3.16-canary.0
|
|
58
68
|
*/
|
|
59
69
|
export const SyncModule = {
|
|
@@ -67,6 +77,8 @@ export const SyncModule = {
|
|
|
67
77
|
};
|
|
68
78
|
|
|
69
79
|
/**
|
|
80
|
+
* The companion factory that creates `AsyncModule` values.
|
|
81
|
+
*
|
|
70
82
|
* @since 0.3.16-canary.0
|
|
71
83
|
*/
|
|
72
84
|
export const AsyncModule = {
|
|
@@ -82,6 +94,8 @@ export const AsyncModule = {
|
|
|
82
94
|
// ── Module — unified API ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
83
95
|
|
|
84
96
|
/**
|
|
97
|
+
* The unified module factory — `create` for sync modules, `createAsync` for async ones.
|
|
98
|
+
*
|
|
85
99
|
* @since 0.3.16-canary.0
|
|
86
100
|
*/
|
|
87
101
|
export const Module = {
|
|
@@ -94,6 +108,8 @@ export const Module = {
|
|
|
94
108
|
};
|
|
95
109
|
|
|
96
110
|
/**
|
|
111
|
+
* Narrows a module union to `SyncModule` by checking its brand.
|
|
112
|
+
*
|
|
97
113
|
* @since 0.3.16-canary.0
|
|
98
114
|
*/
|
|
99
115
|
export function isSyncModule(module: SyncModule | AsyncModule): module is SyncModule {
|
package/src/core/registry.ts
CHANGED
|
@@ -6,6 +6,8 @@ import type { Token } from "#/core/token";
|
|
|
6
6
|
import type { BindingIdentifier, Constructor, DependencyKey } from "#/core/types";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
+
* One container's binding store, indexed by token, binding id, and slot for fast lookup.
|
|
10
|
+
*
|
|
9
11
|
* @since 0.3.16-canary.0
|
|
10
12
|
*/
|
|
11
13
|
export class BindingRegistry {
|
|
@@ -15,15 +17,13 @@ export class BindingRegistry {
|
|
|
15
17
|
readonly #bindings = new Map<DependencyKey, Array<Binding>>();
|
|
16
18
|
// Fast lookup by binding ID
|
|
17
19
|
readonly #byId = new Map<BindingIdentifier, Binding>();
|
|
18
|
-
// Fast lookup for slot { name, tags: [] } — unallocated until a named binding is registered.
|
|
19
|
-
#simpleNamed: Map<DependencyKey, Map<string, Binding>> | undefined;
|
|
20
20
|
// Fast path for one default slot binding with no predicate
|
|
21
21
|
readonly #fastDefault = new Map<DependencyKey, Binding>();
|
|
22
|
-
// Fast lookup for a slot carrying exactly one
|
|
23
|
-
// the
|
|
22
|
+
// Fast lookup for a slot carrying exactly one criterion (a lone name folds here too) — keyed by
|
|
23
|
+
// the interned criterion itself, so the pair is one hash. Unallocated until such a slot lands.
|
|
24
24
|
#simpleTagged: Map<DependencyKey, Map<BindingTag, Binding>> | undefined;
|
|
25
|
-
//
|
|
26
|
-
//
|
|
25
|
+
// Slots with two or more criteria, bucketed by their FIRST criterion. A matching slot's every
|
|
26
|
+
// criterion is in the request, so walking the request's buckets finds each candidate exactly once.
|
|
27
27
|
#multiTagged: Map<DependencyKey, Map<BindingTag, Array<Binding>>> | undefined;
|
|
28
28
|
|
|
29
29
|
// Set on the first constant registered and never cleared. Teardown only needs the negative answer
|
|
@@ -77,7 +77,6 @@ export class BindingRegistry {
|
|
|
77
77
|
if (existingIndex !== -1) {
|
|
78
78
|
displacedBinding = bindingsForToken[existingIndex]!;
|
|
79
79
|
this.#byId.delete(displacedBinding.id);
|
|
80
|
-
this.#deindexSimpleNamedBinding(key, displacedBinding);
|
|
81
80
|
this.#deindexSimpleTaggedBinding(key, displacedBinding);
|
|
82
81
|
this.#deindexMultiTaggedBinding(key, displacedBinding);
|
|
83
82
|
}
|
|
@@ -90,7 +89,6 @@ export class BindingRegistry {
|
|
|
90
89
|
|
|
91
90
|
this.#bindings.set(key, nextBindings);
|
|
92
91
|
this.#byId.set(binding.id, binding);
|
|
93
|
-
this.#indexSimpleNamedBinding(key, binding);
|
|
94
92
|
this.#indexSimpleTaggedBinding(key, binding);
|
|
95
93
|
this.#indexMultiTaggedBinding(key, binding);
|
|
96
94
|
this.#refreshFastDefaultForToken(key);
|
|
@@ -103,7 +101,6 @@ export class BindingRegistry {
|
|
|
103
101
|
const key: DependencyKey = token;
|
|
104
102
|
const bindingsForToken = this.#bindings.get(key) ?? [];
|
|
105
103
|
this.#bindings.delete(key);
|
|
106
|
-
this.#simpleNamed?.delete(key);
|
|
107
104
|
this.#simpleTagged?.delete(key);
|
|
108
105
|
this.#multiTagged?.delete(key);
|
|
109
106
|
this.#fastDefault.delete(key);
|
|
@@ -127,12 +124,10 @@ export class BindingRegistry {
|
|
|
127
124
|
const bindingIndex = bindingsForToken.findIndex((candidate) => candidate.id === id);
|
|
128
125
|
// Copy-on-write, like `add`: a walk holding the current array must not lose its place.
|
|
129
126
|
const remaining = bindingIndex === -1 ? bindingsForToken : bindingsForToken.toSpliced(bindingIndex, 1);
|
|
130
|
-
this.#deindexSimpleNamedBinding(key, binding);
|
|
131
127
|
this.#deindexSimpleTaggedBinding(key, binding);
|
|
132
128
|
this.#deindexMultiTaggedBinding(key, binding);
|
|
133
129
|
if (remaining.length === 0) {
|
|
134
130
|
this.#bindings.delete(key);
|
|
135
|
-
this.#simpleNamed?.delete(key);
|
|
136
131
|
this.#simpleTagged?.delete(key);
|
|
137
132
|
this.#multiTagged?.delete(key);
|
|
138
133
|
this.#fastDefault.delete(key);
|
|
@@ -176,7 +171,6 @@ export class BindingRegistry {
|
|
|
176
171
|
const all = this.allBindings();
|
|
177
172
|
this.#bindings.clear();
|
|
178
173
|
this.#byId.clear();
|
|
179
|
-
this.#simpleNamed?.clear();
|
|
180
174
|
this.#simpleTagged?.clear();
|
|
181
175
|
this.#multiTagged?.clear();
|
|
182
176
|
this.#fastDefault.clear();
|
|
@@ -197,10 +191,6 @@ export class BindingRegistry {
|
|
|
197
191
|
);
|
|
198
192
|
}
|
|
199
193
|
|
|
200
|
-
getSimpleNamed(token: Token<unknown> | Constructor, name: string): Binding | undefined {
|
|
201
|
-
return this.#simpleNamed?.get(token)?.get(name);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
194
|
/**
|
|
205
195
|
* The binding indexed under one criterion.
|
|
206
196
|
*
|
|
@@ -285,33 +275,6 @@ export class BindingRegistry {
|
|
|
285
275
|
}
|
|
286
276
|
}
|
|
287
277
|
|
|
288
|
-
#indexSimpleNamedBinding(tokenKey: DependencyKey, binding: Binding): void {
|
|
289
|
-
const name = simpleNameOf(binding);
|
|
290
|
-
if (name === undefined) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
this.#simpleNamed ??= new Map();
|
|
294
|
-
const bindingsByName = getOrInsert(this.#simpleNamed, tokenKey, new Map<string, Binding>());
|
|
295
|
-
bindingsByName.set(name, binding);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
#deindexSimpleNamedBinding(tokenKey: DependencyKey, binding: Binding): void {
|
|
299
|
-
const name = simpleNameOf(binding);
|
|
300
|
-
if (name === undefined) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
const bindingsByName = this.#simpleNamed?.get(tokenKey);
|
|
304
|
-
if (bindingsByName === undefined) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
if (bindingsByName.get(name)?.id === binding.id) {
|
|
308
|
-
bindingsByName.delete(name);
|
|
309
|
-
if (bindingsByName.size === 0) {
|
|
310
|
-
this.#simpleNamed!.delete(tokenKey);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
278
|
#refreshFastDefaultForToken(tokenKey: DependencyKey): void {
|
|
316
279
|
const bindingsForToken = this.#bindings.get(tokenKey);
|
|
317
280
|
const onlyBinding = bindingsForToken?.length === 1 ? bindingsForToken[0]! : undefined;
|
|
@@ -322,48 +285,35 @@ export class BindingRegistry {
|
|
|
322
285
|
this.#fastDefault.delete(tokenKey);
|
|
323
286
|
}
|
|
324
287
|
|
|
325
|
-
/** Whether the deferred named-slot index has had to be built. */
|
|
326
|
-
get isNamedIndexBuilt(): boolean {
|
|
327
|
-
return this.#simpleNamed !== undefined;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
288
|
/** Whether the deferred tagged-slot index has had to be built. */
|
|
331
289
|
get isTaggedIndexBuilt(): boolean {
|
|
332
290
|
return this.#simpleTagged !== undefined;
|
|
333
291
|
}
|
|
334
292
|
}
|
|
335
293
|
|
|
336
|
-
/** The name a binding is indexed under, or `undefined` when its slot is more than a plain name. */
|
|
337
|
-
function simpleNameOf(binding: Binding): string | undefined {
|
|
338
|
-
const { name, tags } = binding.slot;
|
|
339
|
-
return name !== undefined && tags.length === 0 ? name : undefined;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
294
|
/**
|
|
343
|
-
* The
|
|
295
|
+
* The criterion a binding is indexed under, or `undefined` when its slot carries more than one.
|
|
344
296
|
*
|
|
345
|
-
* @remarks Carries predicate-bearing bindings too
|
|
346
|
-
*
|
|
297
|
+
* @remarks Carries predicate-bearing bindings too: every lane that reads this index already
|
|
298
|
+
* re-checks what it finds, so an indexed hit was never unconditional.
|
|
347
299
|
*/
|
|
348
300
|
function simpleTagOf(binding: Binding): BindingTag | undefined {
|
|
349
|
-
const {
|
|
350
|
-
return
|
|
301
|
+
const { tags } = binding.slot;
|
|
302
|
+
return tags.length === 1 ? tags[0] : undefined;
|
|
351
303
|
}
|
|
352
304
|
|
|
353
|
-
/** The first criterion a multi-
|
|
305
|
+
/** The first criterion a multi-criterion slot is bucketed under, or `undefined` for any other shape. */
|
|
354
306
|
function multiTagFirstOf(binding: Binding): BindingTag | undefined {
|
|
355
|
-
const {
|
|
356
|
-
return
|
|
307
|
+
const { tags } = binding.slot;
|
|
308
|
+
return tags.length >= 2 ? tags[0] : undefined;
|
|
357
309
|
}
|
|
358
310
|
|
|
359
311
|
/** A binding nothing has to be matched against: the default slot, no predicate. */
|
|
360
312
|
function isDefaultSlotBinding(binding: Binding): boolean {
|
|
361
|
-
|
|
362
|
-
return name === undefined && tags.length === 0 && binding.predicate === undefined;
|
|
313
|
+
return binding.slot.tags.length === 0 && binding.predicate === undefined;
|
|
363
314
|
}
|
|
364
315
|
|
|
365
316
|
/** A predicate with no slot constraint: last-wins does not apply to it. */
|
|
366
317
|
function isPurePredicateBinding(binding: Binding): boolean {
|
|
367
|
-
|
|
368
|
-
return binding.predicate !== undefined && name === undefined && tags.length === 0;
|
|
318
|
+
return binding.predicate !== undefined && binding.slot.tags.length === 0;
|
|
369
319
|
}
|
package/src/core/tag.ts
CHANGED
|
@@ -58,6 +58,14 @@ export interface TagKey<Value = unknown> {
|
|
|
58
58
|
readonly mask: TagKeyMask;
|
|
59
59
|
/** The criterion for one value, interned: the same value always yields the same object. */
|
|
60
60
|
of(value: Value): BindingTag<Value>;
|
|
61
|
+
/**
|
|
62
|
+
* The interned criterion for a value, or `undefined` when none was ever minted.
|
|
63
|
+
*
|
|
64
|
+
* @remarks Reading without minting is what keeps a request-side value from being retained for
|
|
65
|
+
* the process lifetime: a value no binding ever declared has no criterion, so a lookup can
|
|
66
|
+
* answer "no match" without inserting one.
|
|
67
|
+
*/
|
|
68
|
+
peek(value: Value): BindingTag<Value> | undefined;
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
let tagKeyCounter = -1;
|
|
@@ -101,6 +109,28 @@ export function tag<Value = unknown>(name: string): TagKey<Value> {
|
|
|
101
109
|
let lastValue: Value | undefined;
|
|
102
110
|
let lastPair: BindingTag<Value> | undefined;
|
|
103
111
|
|
|
112
|
+
// The miss paths live outside `of()`/`peek()` so the hot wrappers stay small enough to inline.
|
|
113
|
+
const internPair = (value: Value): BindingTag<Value> => {
|
|
114
|
+
const cacheKey = internKeyFor(value);
|
|
115
|
+
const existing = interned.get(cacheKey);
|
|
116
|
+
|
|
117
|
+
if (existing !== undefined) {
|
|
118
|
+
lastValue = value;
|
|
119
|
+
lastPair = existing;
|
|
120
|
+
|
|
121
|
+
return existing;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const pair = { key, value, mask } as BindingTag<Value>;
|
|
125
|
+
|
|
126
|
+
interned.set(cacheKey, pair);
|
|
127
|
+
lastValue = value;
|
|
128
|
+
lastPair = pair;
|
|
129
|
+
|
|
130
|
+
return pair;
|
|
131
|
+
};
|
|
132
|
+
const peekInterned = (value: Value): BindingTag<Value> | undefined => interned.get(internKeyFor(value));
|
|
133
|
+
|
|
104
134
|
const key: TagKey<Value> = {
|
|
105
135
|
name,
|
|
106
136
|
id,
|
|
@@ -109,30 +139,54 @@ export function tag<Value = unknown>(name: string): TagKey<Value> {
|
|
|
109
139
|
if (lastPair !== undefined && Object.is(value, lastValue)) {
|
|
110
140
|
return lastPair;
|
|
111
141
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
lastValue = value;
|
|
118
|
-
lastPair = existing;
|
|
119
|
-
|
|
120
|
-
return existing;
|
|
142
|
+
return internPair(value);
|
|
143
|
+
},
|
|
144
|
+
peek(value: Value): BindingTag<Value> | undefined {
|
|
145
|
+
if (lastPair !== undefined && Object.is(value, lastValue)) {
|
|
146
|
+
return lastPair;
|
|
121
147
|
}
|
|
122
|
-
|
|
123
|
-
const pair = { key, value, mask } as BindingTag<Value>;
|
|
124
|
-
|
|
125
|
-
interned.set(cacheKey, pair);
|
|
126
|
-
lastValue = value;
|
|
127
|
-
lastPair = pair;
|
|
128
|
-
|
|
129
|
-
return pair;
|
|
148
|
+
return peekInterned(value);
|
|
130
149
|
},
|
|
131
150
|
};
|
|
132
151
|
|
|
133
152
|
return key;
|
|
134
153
|
}
|
|
135
154
|
|
|
155
|
+
/**
|
|
156
|
+
* The reserved key a slot's name is a criterion of.
|
|
157
|
+
*
|
|
158
|
+
* @remarks `whenNamed(n)` and a request's `name` are sugar for `slotName.of(n)`, so one selection
|
|
159
|
+
* model serves both spellings — a name takes part in key masks, indexes and specificity like any
|
|
160
|
+
* criterion. What reserves the key is its identity; diagnostics render its criteria as `name:<value>`.
|
|
161
|
+
*
|
|
162
|
+
* @since 0.8.0
|
|
163
|
+
*/
|
|
164
|
+
export const slotName: TagKey<string> = tag<string>("di:name");
|
|
165
|
+
|
|
166
|
+
// One-entry front for the reserved key's read: a name's criterion never changes once minted, so a
|
|
167
|
+
// hit is sound forever, and a miss is never cached so a later `whenNamed` bind is still seen.
|
|
168
|
+
let lastPeekedName: string | undefined;
|
|
169
|
+
let lastPeekedCriterion: BindingTag<string> | undefined;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The reserved criterion for a name, or `undefined` while no binding has declared it.
|
|
173
|
+
*
|
|
174
|
+
* @remarks Reads without minting, so a request-side name no binding declared is never retained.
|
|
175
|
+
*
|
|
176
|
+
* @since 0.8.0
|
|
177
|
+
*/
|
|
178
|
+
export function slotNameCriterionOf(name: string): BindingTag<string> | undefined {
|
|
179
|
+
if (name === lastPeekedName) {
|
|
180
|
+
return lastPeekedCriterion;
|
|
181
|
+
}
|
|
182
|
+
const criterion = slotName.peek(name);
|
|
183
|
+
if (criterion !== undefined) {
|
|
184
|
+
lastPeekedName = name;
|
|
185
|
+
lastPeekedCriterion = criterion;
|
|
186
|
+
}
|
|
187
|
+
return criterion;
|
|
188
|
+
}
|
|
189
|
+
|
|
136
190
|
/**
|
|
137
191
|
* The key set a list of criteria covers.
|
|
138
192
|
*
|
package/src/core/token.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type { Constructor } from "#/core/constructor-type";
|
|
|
3
3
|
declare const TOKEN_BRAND: unique symbol;
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
+
* A branded identifier carrying the value type its bindings resolve to.
|
|
7
|
+
*
|
|
6
8
|
* @since 0.3.16-canary.0
|
|
7
9
|
*/
|
|
8
10
|
export interface Token<out Value> {
|
|
@@ -11,6 +13,8 @@ export interface Token<out Value> {
|
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
/**
|
|
16
|
+
* Creates a named `Token` for the given value type.
|
|
17
|
+
*
|
|
14
18
|
* @since 0.3.16-canary.0
|
|
15
19
|
*/
|
|
16
20
|
export function token<Value>(name: string): Token<Value> {
|
|
@@ -18,6 +22,8 @@ export function token<Value>(name: string): Token<Value> {
|
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
/**
|
|
25
|
+
* Returns the display name of a token or class used as a dependency key.
|
|
26
|
+
*
|
|
21
27
|
* @since 0.3.16-canary.0
|
|
22
28
|
*/
|
|
23
29
|
export function tokenName(dependency: Token<unknown> | Constructor): string {
|
package/src/core/types.ts
CHANGED
|
@@ -16,6 +16,8 @@ export type DependencyKey = Token<unknown> | Constructor;
|
|
|
16
16
|
// ── BindingScope ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
+
* The lifetime a binding's instances are cached under.
|
|
20
|
+
*
|
|
19
21
|
* @since 0.3.16-canary.0
|
|
20
22
|
*/
|
|
21
23
|
export type BindingScope = "singleton" | "transient" | "scoped";
|
|
@@ -24,6 +26,8 @@ export type BindingScope = "singleton" | "transient" | "scoped";
|
|
|
24
26
|
|
|
25
27
|
declare const BINDING_ID_BRAND: unique symbol;
|
|
26
28
|
/**
|
|
29
|
+
* A branded string that uniquely identifies one binding.
|
|
30
|
+
*
|
|
27
31
|
* @since 0.3.16-canary.0
|
|
28
32
|
*/
|
|
29
33
|
export type BindingIdentifier = string & { readonly [BINDING_ID_BRAND]: true };
|
|
@@ -31,6 +35,8 @@ export type BindingIdentifier = string & { readonly [BINDING_ID_BRAND]: true };
|
|
|
31
35
|
// ── BindingKind ──────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
32
36
|
|
|
33
37
|
/**
|
|
38
|
+
* The strategy a binding produces values with, set by the `to*()` call that created it.
|
|
39
|
+
*
|
|
34
40
|
* @since 0.3.16-canary.0
|
|
35
41
|
*/
|
|
36
42
|
export type BindingKind = "class" | "dynamic" | "dynamic-async" | "resolved" | "resolved-async" | "constant" | "alias";
|
|
@@ -38,11 +44,15 @@ export type BindingKind = "class" | "dynamic" | "dynamic-async" | "resolved" | "
|
|
|
38
44
|
// ── Handlers ─────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
39
45
|
|
|
40
46
|
/**
|
|
47
|
+
* A hook that runs after an instance is created and returns the instance to hand out.
|
|
48
|
+
*
|
|
41
49
|
* @since 0.3.16-canary.0
|
|
42
50
|
*/
|
|
43
51
|
export type ActivationHandler<Value> = (ctx: ResolutionContext, instance: Value) => Value | Promise<Value>;
|
|
44
52
|
|
|
45
53
|
/**
|
|
54
|
+
* A hook that runs when a cached instance is released from its scope.
|
|
55
|
+
*
|
|
46
56
|
* @since 0.3.16-canary.0
|
|
47
57
|
*/
|
|
48
58
|
export type DeactivationHandler<Value> = (instance: Value) => void | Promise<void>;
|
|
@@ -50,6 +60,8 @@ export type DeactivationHandler<Value> = (instance: Value) => void | Promise<voi
|
|
|
50
60
|
// ── ResolveOptions ───────────────────────────────────────────────────────────────────────────────────────────────────
|
|
51
61
|
|
|
52
62
|
/**
|
|
63
|
+
* Slot-selection criteria — a name and tags — that narrow which binding a resolve call matches.
|
|
64
|
+
*
|
|
53
65
|
* @since 0.3.16-canary.0
|
|
54
66
|
*/
|
|
55
67
|
export interface ResolveOptions {
|
|
@@ -67,6 +79,8 @@ export interface ResolveOptions {
|
|
|
67
79
|
// ── ResolutionFrame ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
68
80
|
|
|
69
81
|
/**
|
|
82
|
+
* One entry of the resolution stack: the token, scope, binding, and slot being resolved.
|
|
83
|
+
*
|
|
70
84
|
* @since 0.3.16-canary.0
|
|
71
85
|
*/
|
|
72
86
|
export interface ResolutionFrame {
|
|
@@ -84,6 +98,8 @@ export interface ResolutionFrame {
|
|
|
84
98
|
// ── ConstraintContext ────────────────────────────────────────────────────────────────────────────────────────────────
|
|
85
99
|
|
|
86
100
|
/**
|
|
101
|
+
* The resolution-time view — path, stack, parent, and ancestors — a `BindingConstraint` inspects.
|
|
102
|
+
*
|
|
87
103
|
* @since 0.3.16-canary.0
|
|
88
104
|
*/
|
|
89
105
|
export interface ConstraintContext {
|
|
@@ -105,6 +121,8 @@ export type BindingConstraint = (ctx: ConstraintContext) => boolean;
|
|
|
105
121
|
// ── ResolutionContext ────────────────────────────────────────────────────────────────────────────────────────────────
|
|
106
122
|
|
|
107
123
|
/**
|
|
124
|
+
* The container view a dynamic factory or activation handler resolves further dependencies through.
|
|
125
|
+
*
|
|
108
126
|
* @since 0.3.16-canary.0
|
|
109
127
|
*/
|
|
110
128
|
export interface ResolutionContext {
|
|
@@ -123,6 +141,8 @@ export interface ResolutionContext {
|
|
|
123
141
|
// ── TokenValue ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
124
142
|
|
|
125
143
|
/**
|
|
144
|
+
* The value type a token or class constructor resolves to.
|
|
145
|
+
*
|
|
126
146
|
* @since 0.3.16-canary.0
|
|
127
147
|
*/
|
|
128
148
|
export type TokenValue<Type> =
|
package/src/decorators/inject.ts
CHANGED
|
@@ -35,6 +35,8 @@ type ClassAccessorDecorator<This, Value> = (
|
|
|
35
35
|
) => ClassAccessorDecoratorResult<This, Value> | void;
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
+
* Creates a dual-role value: an injection descriptor that also works as a class accessor decorator.
|
|
39
|
+
*
|
|
38
40
|
* @since 0.3.16-canary.0
|
|
39
41
|
*/
|
|
40
42
|
export function inject<Value>(
|
|
@@ -7,6 +7,8 @@ import type { ParamMetadata } from "#/metadata/metadata-types";
|
|
|
7
7
|
// ── AutoRegisterRegistry ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
+
* The collector `@injectable` registers a class into, for a container to bind later.
|
|
11
|
+
*
|
|
10
12
|
* @since 0.3.16-canary.0
|
|
11
13
|
*/
|
|
12
14
|
export interface AutoRegisterRegistry {
|
|
@@ -15,6 +17,8 @@ export interface AutoRegisterRegistry {
|
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
20
|
+
* Creates an empty auto-register registry.
|
|
21
|
+
*
|
|
18
22
|
* @since 0.3.16-canary.0
|
|
19
23
|
*/
|
|
20
24
|
export function createAutoRegisterRegistry(): AutoRegisterRegistry {
|
|
@@ -32,6 +36,8 @@ export function createAutoRegisterRegistry(): AutoRegisterRegistry {
|
|
|
32
36
|
// ── InjectableOptions ────────────────────────────────────────────────────────────────────────────────────────────────
|
|
33
37
|
|
|
34
38
|
/**
|
|
39
|
+
* Auto-registration options `@injectable` accepts alongside its deps list.
|
|
40
|
+
*
|
|
35
41
|
* @since 0.3.16-canary.0
|
|
36
42
|
*/
|
|
37
43
|
export interface InjectableOptions {
|
|
@@ -83,6 +89,8 @@ export function injectable<const Deps extends ReadonlyArray<InjectableDependency
|
|
|
83
89
|
) => void;
|
|
84
90
|
|
|
85
91
|
/**
|
|
92
|
+
* Writes the declared dependencies into the class's metadata and optionally auto-registers it.
|
|
93
|
+
*
|
|
86
94
|
* @since 0.6.0
|
|
87
95
|
*/
|
|
88
96
|
export function injectable(
|
|
@@ -25,6 +25,8 @@ function recordLifecycleMethod(phase: "postConstruct" | "preDestroy"): MethodDec
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
+
* Marks an instance method to run after the container constructs and wires the instance.
|
|
29
|
+
*
|
|
28
30
|
* @since 0.3.16-canary.0
|
|
29
31
|
*/
|
|
30
32
|
export function postConstruct(): MethodDecorator {
|
|
@@ -32,6 +34,8 @@ export function postConstruct(): MethodDecorator {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
37
|
+
* Marks an instance method to run when the instance's container or scope is disposed.
|
|
38
|
+
*
|
|
35
39
|
* @since 0.3.16-canary.0
|
|
36
40
|
*/
|
|
37
41
|
export function preDestroy(): MethodDecorator {
|