@codefast/di 0.7.1 → 0.8.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 +40 -0
- package/README.md +242 -159
- package/dist/ambient/active-container.d.ts +1 -2
- package/dist/ambient/active-container.js +1 -2
- package/dist/container/binding-builders.d.ts +1 -2
- package/dist/container/binding-builders.js +5 -7
- package/dist/container/container.d.ts +1 -2
- package/dist/container/container.js +4 -6
- package/dist/core/binding-scope.d.ts +1 -2
- package/dist/core/binding-scope.js +1 -2
- package/dist/core/binding.d.ts +16 -4
- package/dist/core/binding.js +30 -6
- package/dist/core/constraint-requirement.d.ts +1 -2
- package/dist/core/constraint-requirement.js +1 -2
- package/dist/core/constructor-type.d.ts +1 -2
- package/dist/core/constructor-type.js +1 -2
- package/dist/core/map-upsert.d.ts +1 -2
- package/dist/core/map-upsert.js +1 -2
- package/dist/core/module.d.ts +1 -2
- package/dist/core/module.js +1 -2
- package/dist/core/registry.d.ts +1 -5
- package/dist/core/registry.js +15 -63
- package/dist/core/tag.d.ts +27 -2
- package/dist/core/tag.js +55 -13
- package/dist/core/token.d.ts +1 -2
- package/dist/core/token.js +1 -2
- package/dist/core/types.d.ts +1 -2
- package/dist/core/types.js +1 -2
- package/dist/decorators/inject.d.ts +1 -2
- package/dist/decorators/inject.js +1 -2
- package/dist/decorators/injectable.d.ts +1 -2
- package/dist/decorators/injectable.js +1 -2
- package/dist/decorators/lifecycle-decorators.d.ts +1 -2
- package/dist/decorators/lifecycle-decorators.js +1 -2
- package/dist/errors/diagnostics.d.ts +1 -2
- package/dist/errors/diagnostics.js +1 -2
- package/dist/errors/errors.d.ts +1 -2
- package/dist/errors/errors.js +29 -5
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/injection/descriptor.d.ts +1 -2
- package/dist/injection/descriptor.js +1 -2
- package/dist/injection/resolve-options.d.ts +18 -14
- package/dist/injection/resolve-options.js +70 -17
- package/dist/introspection/dependency-graph.d.ts +1 -2
- package/dist/introspection/dependency-graph.js +23 -11
- package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -2
- package/dist/introspection/graph-adapters/cytoscape.js +1 -2
- package/dist/introspection/graph-adapters/dot.d.ts +1 -2
- package/dist/introspection/graph-adapters/dot.js +1 -2
- package/dist/introspection/graph-adapters/mermaid.d.ts +1 -2
- package/dist/introspection/graph-adapters/mermaid.js +1 -2
- package/dist/introspection/graph-adapters/reactflow.d.ts +1 -2
- package/dist/introspection/graph-adapters/reactflow.js +1 -2
- package/dist/introspection/inspector.d.ts +1 -2
- package/dist/introspection/inspector.js +1 -2
- package/dist/lifecycle/lifecycle-manager.d.ts +1 -2
- package/dist/lifecycle/lifecycle-manager.js +1 -2
- package/dist/lifecycle/scope-manager.d.ts +1 -2
- package/dist/lifecycle/scope-manager.js +1 -2
- package/dist/metadata/metadata-keys.d.ts +1 -2
- package/dist/metadata/metadata-keys.js +1 -2
- package/dist/metadata/metadata-reader-token.d.ts +1 -2
- package/dist/metadata/metadata-reader-token.js +1 -2
- package/dist/metadata/metadata-types.d.ts +1 -2
- package/dist/metadata/metadata-types.js +1 -2
- package/dist/metadata/symbol-metadata-reader.d.ts +1 -2
- package/dist/metadata/symbol-metadata-reader.js +1 -2
- package/dist/metadata/verifying-metadata-reader.d.ts +1 -2
- package/dist/metadata/verifying-metadata-reader.js +1 -2
- package/dist/resolution/cache/activation-need.d.ts +1 -2
- package/dist/resolution/cache/activation-need.js +1 -2
- package/dist/resolution/cache/binding-lookup-cache.d.ts +2 -5
- package/dist/resolution/cache/binding-lookup-cache.js +2 -40
- package/dist/resolution/cache/class-introspector.d.ts +1 -2
- package/dist/resolution/cache/class-introspector.js +1 -2
- package/dist/resolution/context.d.ts +1 -2
- package/dist/resolution/context.js +1 -2
- package/dist/resolution/path/resolution-path.d.ts +1 -2
- package/dist/resolution/path/resolution-path.js +1 -2
- package/dist/resolution/plan/instantiation-plan.d.ts +5 -14
- package/dist/resolution/plan/instantiation-plan.js +10 -29
- package/dist/resolution/resolver.d.ts +13 -7
- package/dist/resolution/resolver.js +107 -143
- package/dist/resolution/select/binding-select.d.ts +5 -7
- package/dist/resolution/select/binding-select.js +19 -22
- package/dist/resolution/select/constraints.d.ts +1 -2
- package/dist/resolution/select/constraints.js +1 -2
- package/package.json +6 -58
- package/dist/ambient/active-container.d.ts.map +0 -1
- package/dist/ambient/active-container.js.map +0 -1
- package/dist/container/binding-builders.d.ts.map +0 -1
- package/dist/container/binding-builders.js.map +0 -1
- package/dist/container/container.d.ts.map +0 -1
- package/dist/container/container.js.map +0 -1
- package/dist/core/binding-scope.d.ts.map +0 -1
- package/dist/core/binding-scope.js.map +0 -1
- package/dist/core/binding.d.ts.map +0 -1
- package/dist/core/binding.js.map +0 -1
- package/dist/core/constraint-requirement.d.ts.map +0 -1
- package/dist/core/constraint-requirement.js.map +0 -1
- package/dist/core/constructor-type.d.ts.map +0 -1
- package/dist/core/constructor-type.js.map +0 -1
- package/dist/core/map-upsert.d.ts.map +0 -1
- package/dist/core/map-upsert.js.map +0 -1
- package/dist/core/module.d.ts.map +0 -1
- package/dist/core/module.js.map +0 -1
- package/dist/core/registry.d.ts.map +0 -1
- package/dist/core/registry.js.map +0 -1
- package/dist/core/tag.d.ts.map +0 -1
- package/dist/core/tag.js.map +0 -1
- package/dist/core/token.d.ts.map +0 -1
- package/dist/core/token.js.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js.map +0 -1
- package/dist/decorators/inject.d.ts.map +0 -1
- package/dist/decorators/inject.js.map +0 -1
- package/dist/decorators/injectable.d.ts.map +0 -1
- package/dist/decorators/injectable.js.map +0 -1
- package/dist/decorators/lifecycle-decorators.d.ts.map +0 -1
- package/dist/decorators/lifecycle-decorators.js.map +0 -1
- package/dist/errors/diagnostics.d.ts.map +0 -1
- package/dist/errors/diagnostics.js.map +0 -1
- package/dist/errors/errors.d.ts.map +0 -1
- package/dist/errors/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/injection/descriptor.d.ts.map +0 -1
- package/dist/injection/descriptor.js.map +0 -1
- package/dist/injection/resolve-options.d.ts.map +0 -1
- package/dist/injection/resolve-options.js.map +0 -1
- package/dist/introspection/dependency-graph.d.ts.map +0 -1
- package/dist/introspection/dependency-graph.js.map +0 -1
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +0 -1
- package/dist/introspection/graph-adapters/cytoscape.js.map +0 -1
- package/dist/introspection/graph-adapters/dot.d.ts.map +0 -1
- package/dist/introspection/graph-adapters/dot.js.map +0 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts.map +0 -1
- package/dist/introspection/graph-adapters/mermaid.js.map +0 -1
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +0 -1
- package/dist/introspection/graph-adapters/reactflow.js.map +0 -1
- package/dist/introspection/inspector.d.ts.map +0 -1
- package/dist/introspection/inspector.js.map +0 -1
- package/dist/lifecycle/lifecycle-manager.d.ts.map +0 -1
- package/dist/lifecycle/lifecycle-manager.js.map +0 -1
- package/dist/lifecycle/scope-manager.d.ts.map +0 -1
- package/dist/lifecycle/scope-manager.js.map +0 -1
- package/dist/metadata/metadata-keys.d.ts.map +0 -1
- package/dist/metadata/metadata-keys.js.map +0 -1
- package/dist/metadata/metadata-reader-token.d.ts.map +0 -1
- package/dist/metadata/metadata-reader-token.js.map +0 -1
- package/dist/metadata/metadata-types.d.ts.map +0 -1
- package/dist/metadata/metadata-types.js.map +0 -1
- package/dist/metadata/symbol-metadata-reader.d.ts.map +0 -1
- package/dist/metadata/symbol-metadata-reader.js.map +0 -1
- package/dist/metadata/verifying-metadata-reader.d.ts.map +0 -1
- package/dist/metadata/verifying-metadata-reader.js.map +0 -1
- package/dist/resolution/cache/activation-need.d.ts.map +0 -1
- package/dist/resolution/cache/activation-need.js.map +0 -1
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +0 -1
- package/dist/resolution/cache/binding-lookup-cache.js.map +0 -1
- package/dist/resolution/cache/class-introspector.d.ts.map +0 -1
- package/dist/resolution/cache/class-introspector.js.map +0 -1
- package/dist/resolution/context.d.ts.map +0 -1
- package/dist/resolution/context.js.map +0 -1
- package/dist/resolution/path/resolution-path.d.ts.map +0 -1
- package/dist/resolution/path/resolution-path.js.map +0 -1
- package/dist/resolution/plan/instantiation-plan.d.ts.map +0 -1
- package/dist/resolution/plan/instantiation-plan.js.map +0 -1
- package/dist/resolution/resolver.d.ts.map +0 -1
- package/dist/resolution/resolver.js.map +0 -1
- package/dist/resolution/select/binding-select.d.ts.map +0 -1
- package/dist/resolution/select/binding-select.js.map +0 -1
- package/dist/resolution/select/constraints.d.ts.map +0 -1
- package/dist/resolution/select/constraints.js.map +0 -1
- package/src/ambient/active-container.ts +0 -69
- package/src/container/binding-builders.ts +0 -332
- package/src/container/container.ts +0 -973
- package/src/core/binding-scope.ts +0 -15
- package/src/core/binding.ts +0 -477
- package/src/core/constraint-requirement.ts +0 -94
- package/src/core/constructor-type.ts +0 -18
- package/src/core/map-upsert.ts +0 -53
- package/src/core/module.ts +0 -117
- package/src/core/registry.ts +0 -371
- package/src/core/tag.ts +0 -158
- package/src/core/token.ts +0 -31
- package/src/core/types.ts +0 -149
- package/src/decorators/inject.ts +0 -101
- package/src/decorators/injectable.ts +0 -134
- package/src/decorators/lifecycle-decorators.ts +0 -43
- package/src/errors/diagnostics.ts +0 -45
- package/src/errors/errors.ts +0 -476
- package/src/index.ts +0 -139
- package/src/injection/descriptor.ts +0 -218
- package/src/injection/resolve-options.ts +0 -144
- package/src/introspection/dependency-graph.ts +0 -282
- package/src/introspection/graph-adapters/cytoscape.ts +0 -78
- package/src/introspection/graph-adapters/dot.ts +0 -32
- package/src/introspection/graph-adapters/mermaid.ts +0 -61
- package/src/introspection/graph-adapters/reactflow.ts +0 -84
- package/src/introspection/inspector.ts +0 -134
- package/src/lifecycle/lifecycle-manager.ts +0 -272
- package/src/lifecycle/scope-manager.ts +0 -158
- package/src/metadata/metadata-keys.ts +0 -28
- package/src/metadata/metadata-reader-token.ts +0 -10
- package/src/metadata/metadata-types.ts +0 -57
- package/src/metadata/symbol-metadata-reader.ts +0 -126
- package/src/metadata/verifying-metadata-reader.ts +0 -41
- package/src/resolution/cache/activation-need.ts +0 -87
- package/src/resolution/cache/binding-lookup-cache.ts +0 -209
- package/src/resolution/cache/class-introspector.ts +0 -256
- package/src/resolution/context.ts +0 -331
- package/src/resolution/path/resolution-path.ts +0 -167
- package/src/resolution/plan/instantiation-plan.ts +0 -617
- package/src/resolution/resolver.ts +0 -1675
- package/src/resolution/select/binding-select.ts +0 -210
- package/src/resolution/select/constraints.ts +0 -146
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { Binding } from "#/core/binding";
|
|
2
|
-
import type { BindingScope } from "#/core/types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The scope a binding resolves under.
|
|
6
|
-
*
|
|
7
|
-
* @remarks Every kind declares one — an alias declares `transient`, since it defers scoping to the
|
|
8
|
-
* binding it points at — so this is a field read, kept as a named function because it is the
|
|
9
|
-
* vocabulary validation and introspection speak.
|
|
10
|
-
*
|
|
11
|
-
* @since 0.3.16-canary.0
|
|
12
|
-
*/
|
|
13
|
-
export function effectiveBindingScope(binding: Binding): BindingScope {
|
|
14
|
-
return binding.scope;
|
|
15
|
-
}
|
package/src/core/binding.ts
DELETED
|
@@ -1,477 +0,0 @@
|
|
|
1
|
-
import type { BindingTag, TagKeyMask } from "#/core/tag";
|
|
2
|
-
import { NO_TAG_KEYS } from "#/core/tag";
|
|
3
|
-
import type { Token } from "#/core/token";
|
|
4
|
-
import type {
|
|
5
|
-
ActivationHandler,
|
|
6
|
-
BindingIdentifier,
|
|
7
|
-
BindingScope,
|
|
8
|
-
Constructor,
|
|
9
|
-
DeactivationHandler,
|
|
10
|
-
ResolutionContext,
|
|
11
|
-
ResolutionFrame,
|
|
12
|
-
BindingConstraint,
|
|
13
|
-
} from "#/core/types";
|
|
14
|
-
import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "#/injection/descriptor";
|
|
15
|
-
|
|
16
|
-
// ── BindingSlot ──────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* The name-plus-tags coordinate a binding registers under and a request matches against.
|
|
20
|
-
*
|
|
21
|
-
* @since 0.3.16-canary.0
|
|
22
|
-
*/
|
|
23
|
-
export interface BindingSlot {
|
|
24
|
-
readonly name: string | undefined;
|
|
25
|
-
readonly tags: ReadonlyArray<BindingTag>;
|
|
26
|
-
/** OR of this slot's tag keys, so the subset test is one word compare. */
|
|
27
|
-
readonly keyMask: TagKeyMask;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Returns whether two slots carry the same name and tag set, in any tag order.
|
|
32
|
-
*
|
|
33
|
-
* @since 0.3.16-canary.0
|
|
34
|
-
*/
|
|
35
|
-
export function bindingSlotEquals(left: BindingSlot, right: BindingSlot): boolean {
|
|
36
|
-
if (left.name !== right.name || left.keyMask !== right.keyMask || left.tags.length !== right.tags.length) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
for (const criterion of left.tags) {
|
|
40
|
-
if (!right.tags.includes(criterion)) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Cached singleton absent — distinguishes "not resolved yet" from a cached `undefined`.
|
|
49
|
-
*
|
|
50
|
-
* @since 0.5.0-canary.8
|
|
51
|
-
*/
|
|
52
|
-
export const NO_INSTANCE: unique symbol = Symbol("di:no-instance");
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The slot every unconstrained binding shares.
|
|
56
|
-
*
|
|
57
|
-
* @remarks Tags are frozen where they are built (here and in the builder's re-tag), never per
|
|
58
|
-
* binding: frames and snapshots alias the array, so a caller's write throws instead of corrupting
|
|
59
|
-
* the registry.
|
|
60
|
-
*
|
|
61
|
-
* @since 0.3.16-canary.0
|
|
62
|
-
*/
|
|
63
|
-
export const DEFAULT_BINDING_SLOT: BindingSlot = { name: undefined, tags: Object.freeze([]), keyMask: NO_TAG_KEYS };
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Formats a slot for diagnostics — `default`, or its `name:`/`tag:` parts.
|
|
67
|
-
*
|
|
68
|
-
* @since 0.3.16-canary.0
|
|
69
|
-
*/
|
|
70
|
-
export function bindingSlotToString(slot: BindingSlot): string {
|
|
71
|
-
if (slot.name === undefined && slot.tags.length === 0) {
|
|
72
|
-
return "default";
|
|
73
|
-
}
|
|
74
|
-
const parts: Array<string> = [];
|
|
75
|
-
if (slot.name !== undefined) {
|
|
76
|
-
parts.push(`name:${slot.name}`);
|
|
77
|
-
}
|
|
78
|
-
for (const criterion of slot.tags) {
|
|
79
|
-
parts.push(`tag:${criterion.key.name}=${String(criterion.value)}`);
|
|
80
|
-
}
|
|
81
|
-
return parts.join(",");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ── BindingBase ──────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
85
|
-
|
|
86
|
-
interface BindingBase<Value> {
|
|
87
|
-
readonly id: BindingIdentifier;
|
|
88
|
-
/**
|
|
89
|
-
* True while this binding's factory is executing on the current synchronous call stack.
|
|
90
|
-
*
|
|
91
|
-
* @remarks Both cycle guards that can use an `O(1)` flag read this — the sync transient-dynamic
|
|
92
|
-
* lane and the async cascade lane — because synchronous code does not interleave, so the flag *is*
|
|
93
|
-
* exact path membership. Not optional: `createBinding` always sets it, and a field that may be
|
|
94
|
-
* absent is a field that can cost the shared hidden class. Resolver-owned; callers never set it.
|
|
95
|
-
*/
|
|
96
|
-
inFlight: boolean;
|
|
97
|
-
/**
|
|
98
|
-
* Memoized resolution frame for this binding. Its contents derive only from immutable binding
|
|
99
|
-
* fields, so it is computed once on first resolve and reused instead of a per-resolver Map
|
|
100
|
-
* lookup on every hop.
|
|
101
|
-
*
|
|
102
|
-
* @remarks Resolver-owned bookkeeping — `registry.add` normalizes it, so callers never set it.
|
|
103
|
-
*/
|
|
104
|
-
frame: ResolutionFrame | undefined;
|
|
105
|
-
/**
|
|
106
|
-
* Cached singleton instance, or {@link NO_INSTANCE}.
|
|
107
|
-
*
|
|
108
|
-
* @remarks A binding belongs to exactly one container, so its singleton slot is per-binding —
|
|
109
|
-
* a field read replaces a keyed lookup on the hottest resolve shape there is.
|
|
110
|
-
*/
|
|
111
|
-
instance: unknown;
|
|
112
|
-
readonly token: Token<Value> | Constructor<Value>;
|
|
113
|
-
readonly slot: BindingSlot;
|
|
114
|
-
readonly predicate?: BindingConstraint | undefined;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
type BindingBaseKeys = keyof BindingBase<unknown>;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* The lifecycle hooks every kind but `alias` may carry.
|
|
121
|
-
*
|
|
122
|
-
* @remarks Declared as **methods**, not function-typed properties, so their parameters compare
|
|
123
|
-
* bivariantly and `Binding<Value>` stays assignable to `Binding`. The engine erases the value type at
|
|
124
|
-
* every lane boundary regardless; the public `ActivationHandler` / `DeactivationHandler` keep strict
|
|
125
|
-
* checking, which is where a user's handler is actually verified. Not `readonly`: a fluent chain
|
|
126
|
-
* refines both in place — see {@link RefinableBindingFields}.
|
|
127
|
-
*/
|
|
128
|
-
interface BindingLifecycleHooks<Value> {
|
|
129
|
-
onActivation?(ctx: ResolutionContext, instance: Value): Value | Promise<Value>;
|
|
130
|
-
onDeactivation?(instance: Value): void | Promise<void>;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ── Binding kinds ────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* A binding that instantiates a constructor.
|
|
137
|
-
*
|
|
138
|
-
* @since 0.3.16-canary.0
|
|
139
|
-
*/
|
|
140
|
-
export interface ClassBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
141
|
-
readonly kind: "class";
|
|
142
|
-
readonly target: Constructor<Value>;
|
|
143
|
-
readonly scope: BindingScope;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* A binding that computes its value with a synchronous factory.
|
|
148
|
-
*
|
|
149
|
-
* @since 0.3.16-canary.0
|
|
150
|
-
*/
|
|
151
|
-
export interface DynamicBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
152
|
-
readonly kind: "dynamic";
|
|
153
|
-
readonly factory: (ctx: ResolutionContext) => Value;
|
|
154
|
-
readonly scope: BindingScope;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* A binding whose factory returns a promise of the value.
|
|
159
|
-
*
|
|
160
|
-
* @since 0.3.16-canary.0
|
|
161
|
-
*/
|
|
162
|
-
export interface DynamicAsyncBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
163
|
-
readonly kind: "dynamic-async";
|
|
164
|
-
readonly factory: (ctx: ResolutionContext) => Promise<Value>;
|
|
165
|
-
readonly scope: BindingScope;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* A binding whose factory is called with its declared dependencies already resolved.
|
|
170
|
-
*
|
|
171
|
-
* @since 0.3.16-canary.0
|
|
172
|
-
*/
|
|
173
|
-
export interface ResolvedBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
174
|
-
readonly kind: "resolved";
|
|
175
|
-
readonly factory: (...args: Array<unknown>) => Value;
|
|
176
|
-
readonly deps: ReadonlyArray<InjectionDescriptor>;
|
|
177
|
-
readonly scope: BindingScope;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* The async form of {@link ResolvedBinding} — the factory returns a promise.
|
|
182
|
-
*
|
|
183
|
-
* @since 0.3.16-canary.0
|
|
184
|
-
*/
|
|
185
|
-
export interface ResolvedAsyncBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
186
|
-
readonly kind: "resolved-async";
|
|
187
|
-
readonly factory: (...args: Array<unknown>) => Promise<Value>;
|
|
188
|
-
readonly deps: ReadonlyArray<InjectionDescriptor>;
|
|
189
|
-
readonly scope: BindingScope;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* A binding that hands out one fixed value, so its scope is always `singleton`.
|
|
194
|
-
*
|
|
195
|
-
* @since 0.3.16-canary.0
|
|
196
|
-
*/
|
|
197
|
-
export interface ConstantBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
198
|
-
readonly kind: "constant";
|
|
199
|
-
readonly value: Value;
|
|
200
|
-
readonly scope: "singleton";
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* A binding that defers to whatever binding its target token selects.
|
|
205
|
-
*
|
|
206
|
-
* @since 0.3.16-canary.0
|
|
207
|
-
*/
|
|
208
|
-
export interface AliasBinding<Value> extends BindingBase<Value> {
|
|
209
|
-
readonly kind: "alias";
|
|
210
|
-
readonly target: Token<Value> | Constructor<Value>;
|
|
211
|
-
/**
|
|
212
|
-
* Always `transient` — an alias defers scoping to the binding it points at.
|
|
213
|
-
*
|
|
214
|
-
* @remarks Declared so `scope` is present on every kind, which is what lets the engine read it
|
|
215
|
-
* as a plain field instead of testing for the one kind that lacks it.
|
|
216
|
-
*/
|
|
217
|
-
readonly scope: "transient";
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Every binding shape the engine resolves, discriminated by `kind`.
|
|
222
|
-
*
|
|
223
|
-
* @since 0.3.16-canary.0
|
|
224
|
-
*/
|
|
225
|
-
export type Binding<Value = unknown> =
|
|
226
|
-
| ClassBinding<Value>
|
|
227
|
-
| DynamicBinding<Value>
|
|
228
|
-
| DynamicAsyncBinding<Value>
|
|
229
|
-
| ResolvedBinding<Value>
|
|
230
|
-
| ResolvedAsyncBinding<Value>
|
|
231
|
-
| ConstantBinding<Value>
|
|
232
|
-
| AliasBinding<Value>;
|
|
233
|
-
|
|
234
|
-
/** `Omit` applied per union member, since a bare `Omit` would collapse the union into one shape. */
|
|
235
|
-
type DistributiveOmit<Union, Keys extends PropertyKey> = Union extends unknown ? Omit<Union, Keys> : never;
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Builder-only payload before `id`, `token`, `slot`, and `predicate` are applied.
|
|
239
|
-
*
|
|
240
|
-
* @remarks Derived rather than listed: a new binding kind joins this the moment it joins
|
|
241
|
-
* {@link Binding}, so the two unions cannot diverge.
|
|
242
|
-
*
|
|
243
|
-
* @since 0.3.16-canary.0
|
|
244
|
-
*/
|
|
245
|
-
export type PartialBinding<Value> = DistributiveOmit<Binding<Value>, BindingBaseKeys>;
|
|
246
|
-
|
|
247
|
-
// ── ID generation ────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
248
|
-
|
|
249
|
-
let bindingIdCounter = 0;
|
|
250
|
-
/**
|
|
251
|
-
* Returns a process-unique identifier for a new binding.
|
|
252
|
-
*
|
|
253
|
-
* @since 0.3.16-canary.0
|
|
254
|
-
*/
|
|
255
|
-
export function generateBindingId(): BindingIdentifier {
|
|
256
|
-
return String(++bindingIdCounter) as BindingIdentifier;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// ── Construction ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
260
|
-
|
|
261
|
-
/** Every key any member declares — a bare `keyof` on a union gives only the shared ones. */
|
|
262
|
-
type KeysOfUnion<Union> = Union extends unknown ? keyof Union : never;
|
|
263
|
-
|
|
264
|
-
// Superset of every kind's fields, so one literal can copy any binding shape.
|
|
265
|
-
type BindingFieldName = KeysOfUnion<Binding<unknown>>;
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Completeness guard for {@link createBinding}'s literal.
|
|
269
|
-
*
|
|
270
|
-
* @remarks The literal is `satisfies` this, so a field added to any binding kind that the literal
|
|
271
|
-
* forgets to write is a compile error rather than a binding silently missing it.
|
|
272
|
-
*/
|
|
273
|
-
type ConstructedBindingFields = Record<BindingFieldName, unknown>;
|
|
274
|
-
|
|
275
|
-
type BindingFieldSuperset = {
|
|
276
|
-
readonly kind: Binding["kind"];
|
|
277
|
-
readonly instance?: unknown;
|
|
278
|
-
readonly scope: BindingScope;
|
|
279
|
-
readonly target?: unknown;
|
|
280
|
-
readonly factory?: unknown;
|
|
281
|
-
readonly deps?: unknown;
|
|
282
|
-
readonly value?: unknown;
|
|
283
|
-
readonly onActivation?: unknown;
|
|
284
|
-
readonly onDeactivation?: unknown;
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* The single construction site for bindings — one literal, one V8 hidden class.
|
|
289
|
-
*
|
|
290
|
-
* @remarks Field order is fixed and this is the only construction site, so every binding shares one
|
|
291
|
-
* hidden class. Reordering the fields, or adding a second site, gives that up.
|
|
292
|
-
*
|
|
293
|
-
* @param source - the kind-specific payload, or an existing binding to re-slot
|
|
294
|
-
* @param token - the key requests resolve the binding by
|
|
295
|
-
* @param slot - the name + tags a request must match to select this binding
|
|
296
|
-
* @param predicate - a custom constraint, or `undefined` for none
|
|
297
|
-
* @param id - reuse a caller's id to keep a fluent chain's `id()` stable across refinements
|
|
298
|
-
*
|
|
299
|
-
* @since 0.5.0-canary.8
|
|
300
|
-
*/
|
|
301
|
-
export function createBinding<Value>(
|
|
302
|
-
source: PartialBinding<Value> | Binding<Value>,
|
|
303
|
-
token: Token<Value> | Constructor<Value>,
|
|
304
|
-
slot: BindingSlot,
|
|
305
|
-
predicate: BindingConstraint | undefined,
|
|
306
|
-
id: BindingIdentifier = generateBindingId(),
|
|
307
|
-
): Binding<Value> {
|
|
308
|
-
const fields = source as BindingFieldSuperset;
|
|
309
|
-
return {
|
|
310
|
-
kind: fields.kind,
|
|
311
|
-
id,
|
|
312
|
-
inFlight: false,
|
|
313
|
-
frame: undefined,
|
|
314
|
-
// An `in` probe, not `??`: a re-slotted singleton may legitimately hold a cached `undefined`.
|
|
315
|
-
instance: "instance" in fields ? fields.instance : NO_INSTANCE,
|
|
316
|
-
token,
|
|
317
|
-
slot,
|
|
318
|
-
predicate,
|
|
319
|
-
scope: fields.scope,
|
|
320
|
-
target: fields.target,
|
|
321
|
-
factory: fields.factory,
|
|
322
|
-
deps: fields.deps,
|
|
323
|
-
value: fields.value,
|
|
324
|
-
onActivation: fields.onActivation,
|
|
325
|
-
onDeactivation: fields.onDeactivation,
|
|
326
|
-
} satisfies ConstructedBindingFields as Binding<Value>;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Writable view of the only fields a fluent chain may refine after registration.
|
|
331
|
-
*
|
|
332
|
-
* @remarks No registry index is keyed on these, so a builder that owns the registered object
|
|
333
|
-
* can write them directly instead of re-registering. `token`, `slot`, `predicate` and `id`
|
|
334
|
-
* are excluded on purpose — changing those means re-indexing.
|
|
335
|
-
*
|
|
336
|
-
* @since 0.5.0-canary.8
|
|
337
|
-
*/
|
|
338
|
-
export interface RefinableBindingFields<Value> {
|
|
339
|
-
onActivation: ActivationHandler<Value> | undefined;
|
|
340
|
-
onDeactivation: DeactivationHandler<Value> | undefined;
|
|
341
|
-
scope: BindingScope;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Narrows a registered binding to the fields a fluent chain may still refine.
|
|
346
|
-
*
|
|
347
|
-
* @since 0.5.0-canary.8
|
|
348
|
-
*/
|
|
349
|
-
export function refinableFields<Value>(binding: Binding<Value>): RefinableBindingFields<Value> {
|
|
350
|
-
return binding as RefinableBindingFields<Value>;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Writable view of the memoized frame, which is a cache rather than part of a binding's identity.
|
|
355
|
-
*
|
|
356
|
-
* @remarks Named for the same reason as {@link RefinableBindingFields}: a write view stated once
|
|
357
|
-
* cannot drift from `Binding`, where an inline cast at each site can.
|
|
358
|
-
*/
|
|
359
|
-
interface MemoizedFrameField {
|
|
360
|
-
frame: ResolutionFrame | undefined;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Drops the memoized resolution frame, for a refinement that changes what the frame reports.
|
|
365
|
-
*
|
|
366
|
-
* @remarks `scope` is the only field a chain writes in place that the frame derives from — a
|
|
367
|
-
* re-slot builds a fresh binding, whose frame starts empty anyway.
|
|
368
|
-
*
|
|
369
|
-
* @since 0.5.0-canary.9
|
|
370
|
-
*/
|
|
371
|
-
export function clearBindingFrame<Value>(binding: Binding<Value>): void {
|
|
372
|
-
(binding as MemoizedFrameField).frame = undefined;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ── Builder interfaces ───────────────────────────────────────────────────────────────────────────────────────────────
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Common slot-constraint + id methods shared by all concrete binding builders.
|
|
379
|
-
*
|
|
380
|
-
* @since 0.3.16-canary.0
|
|
381
|
-
*/
|
|
382
|
-
export interface SlotConstrainedBuilder {
|
|
383
|
-
when(predicate: BindingConstraint): this;
|
|
384
|
-
whenNamed(name: string): this;
|
|
385
|
-
whenTagged(criterion: BindingTag): this;
|
|
386
|
-
whenDefault(): this;
|
|
387
|
-
id(): BindingIdentifier;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* The `to*` step of the fluent chain, choosing what a token resolves to.
|
|
392
|
-
*
|
|
393
|
-
* @since 0.3.16-canary.0
|
|
394
|
-
*/
|
|
395
|
-
export interface BindToBuilder<Value> {
|
|
396
|
-
to(type: Constructor<Value>): BindingBuilder<Value>;
|
|
397
|
-
toSelf(): BindingBuilder<Value>;
|
|
398
|
-
toConstantValue(value: Value): ConstantBindingBuilder<Value>;
|
|
399
|
-
toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value>;
|
|
400
|
-
toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value>;
|
|
401
|
-
toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(
|
|
402
|
-
factory: (...args: { [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]> }) => Value,
|
|
403
|
-
deps: Deps,
|
|
404
|
-
): BindingBuilder<Value>;
|
|
405
|
-
toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(
|
|
406
|
-
factory: (...args: { [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]> }) => Promise<Value>,
|
|
407
|
-
deps: Deps,
|
|
408
|
-
): BindingBuilder<Value>;
|
|
409
|
-
toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* The scope-selection step of the fluent chain.
|
|
414
|
-
*
|
|
415
|
-
* @since 0.3.16-canary.0
|
|
416
|
-
*/
|
|
417
|
-
export interface BindingBuilder<Value> extends SlotConstrainedBuilder {
|
|
418
|
-
singleton(): SingletonBindingBuilder<Value>;
|
|
419
|
-
transient(): TransientBindingBuilder<Value>;
|
|
420
|
-
scoped(): ScopedBindingBuilder<Value>;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* The fluent chain for a constant — lifecycle hooks only, since the scope is fixed.
|
|
425
|
-
*
|
|
426
|
-
* @since 0.3.16-canary.0
|
|
427
|
-
*/
|
|
428
|
-
export interface ConstantBindingBuilder<Value> extends SlotConstrainedBuilder {
|
|
429
|
-
onActivation(fn: ActivationHandler<Value>): SingletonLifecycleBuilder<Value>;
|
|
430
|
-
onDeactivation(fn: DeactivationHandler<Value>): SingletonLifecycleBuilder<Value>;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* The fluent chain for an alias — slot constraints only, since scoping belongs to the target.
|
|
435
|
-
*
|
|
436
|
-
* @since 0.3.16-canary.0
|
|
437
|
-
*/
|
|
438
|
-
export interface AliasBindingBuilder extends SlotConstrainedBuilder {}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* The fluent chain after `singleton()`, where both lifecycle hooks stay available.
|
|
442
|
-
*
|
|
443
|
-
* @since 0.3.16-canary.0
|
|
444
|
-
*/
|
|
445
|
-
export interface SingletonBindingBuilder<Value> {
|
|
446
|
-
onActivation(fn: ActivationHandler<Value>): this;
|
|
447
|
-
onDeactivation(fn: DeactivationHandler<Value>): this;
|
|
448
|
-
id(): BindingIdentifier;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* The fluent chain after `transient()`, where activation is the one lifecycle hook offered.
|
|
453
|
-
*
|
|
454
|
-
* @since 0.3.16-canary.0
|
|
455
|
-
*/
|
|
456
|
-
export interface TransientBindingBuilder<Value> {
|
|
457
|
-
onActivation(fn: ActivationHandler<Value>): this;
|
|
458
|
-
id(): BindingIdentifier;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* The fluent chain after `scoped()`, sharing the `transient()` surface.
|
|
463
|
-
*
|
|
464
|
-
* @since 0.3.16-canary.0
|
|
465
|
-
*/
|
|
466
|
-
export interface ScopedBindingBuilder<Value> extends TransientBindingBuilder<Value> {}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* The fluent chain a constant enters once a lifecycle hook is added.
|
|
470
|
-
*
|
|
471
|
-
* @since 0.3.16-canary.0
|
|
472
|
-
*/
|
|
473
|
-
export interface SingletonLifecycleBuilder<Value> {
|
|
474
|
-
onActivation(fn: ActivationHandler<Value>): this;
|
|
475
|
-
onDeactivation(fn: DeactivationHandler<Value>): this;
|
|
476
|
-
id(): BindingIdentifier;
|
|
477
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/** What a constraint needs to exist before it can ever match, so `validate()` can check for it. */
|
|
2
|
-
import type { BindingConstraint } from "#/core/types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Key the requirement is attached under.
|
|
6
|
-
*
|
|
7
|
-
* @remarks A symbol on the predicate rather than a field in the binding: resolution never reads it,
|
|
8
|
-
* so carrying it costs a resolve nothing.
|
|
9
|
-
*
|
|
10
|
-
* @since 0.6.0
|
|
11
|
-
*/
|
|
12
|
-
export const CONSTRAINT_REQUIREMENT: unique symbol = Symbol("di:constraint-requirement");
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* The slot name a constraint waits for on an ancestor.
|
|
16
|
-
*
|
|
17
|
-
* @remarks Only names are described. A tag criterion is interned, so a typo cannot produce one that
|
|
18
|
-
* looks valid, while a name is a bare string that nothing checks.
|
|
19
|
-
*
|
|
20
|
-
* @since 0.6.0
|
|
21
|
-
*/
|
|
22
|
-
export interface ConstraintRequirement {
|
|
23
|
-
readonly requires: "ancestorSlotName";
|
|
24
|
-
readonly name: string;
|
|
25
|
-
/** The helper that built the predicate, so a report can name what the caller wrote. */
|
|
26
|
-
readonly helperName: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Records what a predicate waits for. Called once, where the predicate is built.
|
|
31
|
-
*
|
|
32
|
-
* @since 0.6.0
|
|
33
|
-
*/
|
|
34
|
-
export function requiringAncestorSlotName(
|
|
35
|
-
predicate: BindingConstraint,
|
|
36
|
-
name: string,
|
|
37
|
-
helperName: string,
|
|
38
|
-
): BindingConstraint {
|
|
39
|
-
const requirement: ConstraintRequirement = { requires: "ancestorSlotName", name, helperName };
|
|
40
|
-
Object.defineProperty(predicate, CONSTRAINT_REQUIREMENT, { value: requirement, enumerable: false });
|
|
41
|
-
return predicate;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The requirement a predicate carries, if it was built by a helper that records one.
|
|
46
|
-
*
|
|
47
|
-
* @remarks A composed predicate may carry several; this answers the first. `validate()` reads
|
|
48
|
-
* {@link constraintRequirementsOf} so no recorded requirement is skipped.
|
|
49
|
-
*
|
|
50
|
-
* @since 0.6.0
|
|
51
|
-
*/
|
|
52
|
-
export function constraintRequirementOf(predicate: BindingConstraint): ConstraintRequirement | undefined {
|
|
53
|
-
return constraintRequirementsOf(predicate)[0];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const NO_REQUIREMENTS: ReadonlyArray<ConstraintRequirement> = [];
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Every requirement a predicate carries — one from a helper, several from a composed chain.
|
|
60
|
-
*
|
|
61
|
-
* @since 0.6.1
|
|
62
|
-
*/
|
|
63
|
-
export function constraintRequirementsOf(predicate: BindingConstraint): ReadonlyArray<ConstraintRequirement> {
|
|
64
|
-
const payload = (
|
|
65
|
-
predicate as { [CONSTRAINT_REQUIREMENT]?: ConstraintRequirement | ReadonlyArray<ConstraintRequirement> }
|
|
66
|
-
)[CONSTRAINT_REQUIREMENT];
|
|
67
|
-
if (payload === undefined) {
|
|
68
|
-
return NO_REQUIREMENTS;
|
|
69
|
-
}
|
|
70
|
-
return Array.isArray(payload)
|
|
71
|
-
? (payload as ReadonlyArray<ConstraintRequirement>)
|
|
72
|
-
: [payload as ConstraintRequirement];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Carries both sides' requirements onto a composed predicate, so chaining does not lose them.
|
|
77
|
-
*
|
|
78
|
-
* @since 0.6.1
|
|
79
|
-
*/
|
|
80
|
-
export function mergingConstraintRequirements(
|
|
81
|
-
composite: BindingConstraint,
|
|
82
|
-
left: BindingConstraint,
|
|
83
|
-
right: BindingConstraint,
|
|
84
|
-
): BindingConstraint {
|
|
85
|
-
const merged = [...constraintRequirementsOf(left), ...constraintRequirementsOf(right)];
|
|
86
|
-
if (merged.length === 0) {
|
|
87
|
-
return composite;
|
|
88
|
-
}
|
|
89
|
-
Object.defineProperty(composite, CONSTRAINT_REQUIREMENT, {
|
|
90
|
-
value: merged.length === 1 ? merged[0] : merged,
|
|
91
|
-
enumerable: false,
|
|
92
|
-
});
|
|
93
|
-
return composite;
|
|
94
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A class token: newable, producing `Value`.
|
|
3
|
-
*
|
|
4
|
-
* @remarks Rest parameters are `never[]` so classes with typed constructors stay assignable under
|
|
5
|
-
* `strictFunctionTypes`. Construction uses the real shape; this alias is the token surface only.
|
|
6
|
-
*
|
|
7
|
-
* @since 0.3.16-canary.0
|
|
8
|
-
*/
|
|
9
|
-
export type Constructor<out Value = unknown> = new (...args: Array<never>) => Value;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Class constructor as invoked by the resolver after metadata-driven
|
|
13
|
-
* resolution of `unknown[]` dependencies — separate from {@link Constructor},
|
|
14
|
-
* which is the public assignable class token.
|
|
15
|
-
*
|
|
16
|
-
* @since 0.3.16-canary.0
|
|
17
|
-
*/
|
|
18
|
-
export type ConstructorInvocation = new (...args: Array<unknown>) => unknown;
|
package/src/core/map-upsert.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/** The two `Map` upsert forms the container indexes on, owned here so the package's Node floor stays put. */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Returns the value stored under a key, inserting the given fallback first when the key is absent.
|
|
5
|
-
*
|
|
6
|
-
* @remarks The caller evaluates the fallback either way, so this form fits an insertion that usually
|
|
7
|
-
* misses. Absence is one `get`, which is why the value type excludes `undefined`.
|
|
8
|
-
*
|
|
9
|
-
* @param map - mutated in place on a miss
|
|
10
|
-
* @param key - looked up by the map's own key equality
|
|
11
|
-
* @param value - stored and returned when the key is absent
|
|
12
|
-
*
|
|
13
|
-
* @since 0.7.0
|
|
14
|
-
*/
|
|
15
|
-
export function getOrInsert<Key, Value extends {} | null>(
|
|
16
|
-
map: Map<Key, Value>,
|
|
17
|
-
key: NoInfer<Key>,
|
|
18
|
-
value: NoInfer<Value>,
|
|
19
|
-
): Value {
|
|
20
|
-
const existing = map.get(key);
|
|
21
|
-
if (existing !== undefined) {
|
|
22
|
-
return existing;
|
|
23
|
-
}
|
|
24
|
-
map.set(key, value);
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Returns the value stored under a key, computing and inserting a fallback only when the key is absent.
|
|
30
|
-
*
|
|
31
|
-
* @remarks Fits a lookup that usually hits, where an eager fallback would allocate per call; hoist
|
|
32
|
-
* the factory to module scope and no closure is allocated either. Absence is decided as in
|
|
33
|
-
* {@link getOrInsert}.
|
|
34
|
-
*
|
|
35
|
-
* @param map - mutated in place on a miss
|
|
36
|
-
* @param key - looked up by the map's own key equality, and handed to the factory
|
|
37
|
-
* @param create - called only on a miss
|
|
38
|
-
*
|
|
39
|
-
* @since 0.7.0
|
|
40
|
-
*/
|
|
41
|
-
export function getOrInsertComputed<Key, Value extends {} | null>(
|
|
42
|
-
map: Map<Key, Value>,
|
|
43
|
-
key: NoInfer<Key>,
|
|
44
|
-
create: (key: NoInfer<Key>) => NoInfer<Value>,
|
|
45
|
-
): Value {
|
|
46
|
-
const existing = map.get(key);
|
|
47
|
-
if (existing !== undefined) {
|
|
48
|
-
return existing;
|
|
49
|
-
}
|
|
50
|
-
const created = create(key);
|
|
51
|
-
map.set(key, created);
|
|
52
|
-
return created;
|
|
53
|
-
}
|