@codefast/di 0.5.0-canary.7 → 0.5.0-canary.8
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 +68 -0
- package/README.md +3 -1
- package/dist/binding.d.ts +47 -5
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +44 -0
- package/dist/binding.js.map +1 -1
- package/dist/constructor-type.d.ts +4 -5
- package/dist/constructor-type.d.ts.map +1 -1
- package/dist/container/binding-builders.d.ts +32 -11
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +140 -191
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +75 -92
- package/dist/container/container.js.map +1 -1
- package/dist/decorators/inject.d.ts +2 -4
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js.map +1 -1
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +17 -0
- package/dist/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/introspection/inspector.js +1 -1
- package/dist/introspection/inspector.js.map +1 -1
- package/dist/metadata/metadata-keys.d.ts +3 -6
- package/dist/metadata/metadata-keys.d.ts.map +1 -1
- package/dist/metadata/metadata-keys.js +3 -6
- package/dist/metadata/metadata-keys.js.map +1 -1
- package/dist/registry.d.ts +14 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +35 -45
- package/dist/registry.js.map +1 -1
- package/dist/resolution/activation-need.d.ts +25 -0
- package/dist/resolution/activation-need.d.ts.map +1 -0
- package/dist/resolution/activation-need.js +64 -0
- package/dist/resolution/activation-need.js.map +1 -0
- package/dist/resolution/binding-lookup-cache.d.ts +41 -0
- package/dist/resolution/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/binding-lookup-cache.js +102 -0
- package/dist/resolution/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/binding-select.js +1 -4
- package/dist/resolution/binding-select.js.map +1 -1
- package/dist/resolution/class-introspector.d.ts +27 -0
- package/dist/resolution/class-introspector.d.ts.map +1 -0
- package/dist/resolution/class-introspector.js +60 -0
- package/dist/resolution/class-introspector.js.map +1 -0
- package/dist/resolution/diagnostics.d.ts +41 -0
- package/dist/resolution/diagnostics.d.ts.map +1 -0
- package/dist/resolution/diagnostics.js +18 -0
- package/dist/resolution/diagnostics.js.map +1 -0
- package/dist/resolution/environment.d.ts +22 -1
- package/dist/resolution/environment.d.ts.map +1 -1
- package/dist/resolution/environment.js +27 -1
- package/dist/resolution/environment.js.map +1 -1
- package/dist/resolution/instantiation-plan.d.ts +15 -15
- package/dist/resolution/instantiation-plan.d.ts.map +1 -1
- package/dist/resolution/instantiation-plan.js +68 -48
- package/dist/resolution/instantiation-plan.js.map +1 -1
- package/dist/resolution/lifecycle.d.ts +2 -0
- package/dist/resolution/lifecycle.d.ts.map +1 -1
- package/dist/resolution/lifecycle.js +16 -11
- package/dist/resolution/lifecycle.js.map +1 -1
- package/dist/resolution/resolution-path.d.ts +4 -13
- package/dist/resolution/resolution-path.d.ts.map +1 -1
- package/dist/resolution/resolution-path.js +3 -16
- package/dist/resolution/resolution-path.js.map +1 -1
- package/dist/resolution/resolver.d.ts +7 -2
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +116 -328
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/scope.d.ts +7 -15
- package/dist/resolution/scope.d.ts.map +1 -1
- package/dist/resolution/scope.js +48 -44
- package/dist/resolution/scope.js.map +1 -1
- package/package.json +7 -97
- package/src/binding.ts +104 -5
- package/src/constructor-type.ts +4 -5
- package/src/container/binding-builders.ts +180 -283
- package/src/container/container.ts +90 -103
- package/src/decorators/inject.ts +3 -5
- package/src/errors.ts +21 -0
- package/src/index.ts +1 -0
- package/src/introspection/inspector.ts +1 -1
- package/src/metadata/metadata-keys.ts +3 -6
- package/src/registry.ts +38 -59
- package/src/resolution/activation-need.ts +81 -0
- package/src/resolution/binding-lookup-cache.ts +132 -0
- package/src/resolution/binding-select.ts +1 -4
- package/src/resolution/class-introspector.ts +74 -0
- package/src/resolution/diagnostics.ts +43 -0
- package/src/resolution/environment.ts +31 -1
- package/src/resolution/instantiation-plan.ts +113 -61
- package/src/resolution/lifecycle.ts +16 -11
- package/src/resolution/resolution-path.ts +5 -20
- package/src/resolution/resolver.ts +142 -371
- package/src/resolution/scope.ts +50 -49
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The fluent chain `bind()` returns: it registers the binding and refines it in place.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* previous binding by id) whenever a later fluent call refines the binding —
|
|
6
|
-
* so a chain left half-finished is still a valid registration.
|
|
4
|
+
* @see `ARCHITECTURE.md` — why one object plays every role, and what pins the ordering.
|
|
7
5
|
*/
|
|
8
6
|
import type {
|
|
9
7
|
AliasBindingBuilder,
|
|
@@ -18,380 +16,279 @@ import type {
|
|
|
18
16
|
SingletonLifecycleBuilder,
|
|
19
17
|
TransientBindingBuilder,
|
|
20
18
|
} from "#/binding";
|
|
21
|
-
import { DEFAULT_BINDING_SLOT,
|
|
19
|
+
import { bindingSlotEquals, createBinding, DEFAULT_BINDING_SLOT, refinableFields } from "#/binding";
|
|
22
20
|
import type { InjectableDependency, ResolvedDependencyValue } from "#/decorators/inject";
|
|
23
21
|
import { normalizeToDescriptor } from "#/decorators/inject";
|
|
22
|
+
import { ChainNotRegisteredError } from "#/errors";
|
|
23
|
+
import type { BindingRegistry } from "#/registry";
|
|
24
24
|
import type { Token } from "#/token";
|
|
25
|
+
import { tokenName } from "#/token";
|
|
25
26
|
import type {
|
|
26
27
|
ActivationHandler,
|
|
27
28
|
BindingIdentifier,
|
|
29
|
+
BindingScope,
|
|
28
30
|
ConstraintContext,
|
|
29
31
|
Constructor,
|
|
30
32
|
DeactivationHandler,
|
|
31
33
|
ResolutionContext,
|
|
32
34
|
} from "#/types";
|
|
33
35
|
|
|
34
|
-
// ── Shared builder helpers ────────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @since 0.5.0-canary.7
|
|
38
|
-
*/
|
|
39
|
-
export type CommitFn = <Value>(binding: Binding<Value>, previousId?: BindingIdentifier) => BindingIdentifier;
|
|
40
|
-
|
|
41
|
-
// Builder payloads that still accept a scope — alias/constant bindings never flow through ConstraintBuilder/ScopeBuilder.
|
|
42
|
-
type ScopedPartialBinding<Value> = Exclude<PartialBinding<Value>, { kind: "alias" } | { kind: "constant" }>;
|
|
43
|
-
|
|
44
36
|
function updateSlotTag(slot: BindingSlot, tag: string, value: unknown): BindingSlot {
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
|
|
37
|
+
const tags = [...slot.tags];
|
|
38
|
+
const existingIndex = tags.findIndex(([key]) => key === tag);
|
|
39
|
+
if (existingIndex === -1) {
|
|
40
|
+
tags.push([tag, value]);
|
|
49
41
|
} else {
|
|
50
|
-
|
|
42
|
+
tags[existingIndex] = [tag, value];
|
|
51
43
|
}
|
|
52
|
-
return { ...slot, tags
|
|
44
|
+
return { ...slot, tags };
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
protected abstract commit(): void;
|
|
63
|
-
|
|
64
|
-
when(predicate: (ctx: ConstraintContext) => boolean): this {
|
|
65
|
-
this.predicate = predicate;
|
|
66
|
-
this.commit();
|
|
67
|
-
return this;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
whenNamed(name: string): this {
|
|
71
|
-
this.slot = { ...this.slot, name };
|
|
72
|
-
this.commit();
|
|
73
|
-
return this;
|
|
47
|
+
// True when re-adding `restored` would immediately be displaced again by `current`
|
|
48
|
+
// (both slot-based with equal slots) — in that case the replacement was legitimate.
|
|
49
|
+
function displacesRestoredBinding(current: Binding, restored: Binding): boolean {
|
|
50
|
+
const currentIsPurePredicate =
|
|
51
|
+
current.predicate !== undefined && current.slot.name === undefined && current.slot.tags.length === 0;
|
|
52
|
+
if (currentIsPurePredicate) {
|
|
53
|
+
return false;
|
|
74
54
|
}
|
|
55
|
+
return bindingSlotEquals(current.slot, restored.slot);
|
|
56
|
+
}
|
|
75
57
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
58
|
+
/** Record a module's binding id, dropping the id the chain re-slotted away from. */
|
|
59
|
+
function trackBindingForModule(
|
|
60
|
+
ids: Array<BindingIdentifier>,
|
|
61
|
+
id: BindingIdentifier,
|
|
62
|
+
previousId: BindingIdentifier | undefined,
|
|
63
|
+
): void {
|
|
64
|
+
if (previousId !== undefined) {
|
|
65
|
+
const previousIndex = ids.indexOf(previousId);
|
|
66
|
+
if (previousIndex !== -1) {
|
|
67
|
+
ids.splice(previousIndex, 1);
|
|
68
|
+
}
|
|
80
69
|
}
|
|
70
|
+
ids.push(id);
|
|
71
|
+
}
|
|
81
72
|
|
|
82
|
-
|
|
83
|
-
return this;
|
|
84
|
-
}
|
|
73
|
+
// ── Registration target ───────────────────────────────────────────────────────
|
|
85
74
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Where a chain registers, and on whose behalf.
|
|
77
|
+
*
|
|
78
|
+
* @remarks Built once per container and shared by every chain it creates. `moduleBindingIds` is
|
|
79
|
+
* present exactly when the chain belongs to a module load.
|
|
80
|
+
*
|
|
81
|
+
* @since 0.5.0-canary.8
|
|
82
|
+
*/
|
|
83
|
+
export interface BindingRegistration {
|
|
84
|
+
readonly registry: BindingRegistry;
|
|
85
|
+
readonly moduleBindingIds: Array<BindingIdentifier> | undefined;
|
|
89
86
|
}
|
|
90
87
|
|
|
91
|
-
// ──
|
|
88
|
+
// ── BindingChain ──────────────────────────────────────────────────────────────
|
|
92
89
|
|
|
93
90
|
/**
|
|
94
|
-
*
|
|
91
|
+
* The one builder behind `bind()` and every `to*()` return type. Each interface exposes only the
|
|
92
|
+
* calls that are legal at that point in the chain; the runtime object is shared because every
|
|
93
|
+
* refinement is the same operation — narrow the registered binding, keep its id.
|
|
94
|
+
*
|
|
95
|
+
* @since 0.5.0-canary.8
|
|
95
96
|
*/
|
|
96
|
-
export class
|
|
97
|
+
export class BindingChain<Value>
|
|
98
|
+
implements
|
|
99
|
+
AliasBindingBuilder,
|
|
100
|
+
BindingBuilder<Value>,
|
|
101
|
+
BindToBuilder<Value>,
|
|
102
|
+
ConstantBindingBuilder<Value>,
|
|
103
|
+
ScopedBindingBuilder<Value>,
|
|
104
|
+
SingletonBindingBuilder<Value>,
|
|
105
|
+
SingletonLifecycleBuilder<Value>,
|
|
106
|
+
TransientBindingBuilder<Value>
|
|
107
|
+
{
|
|
108
|
+
// Undefined until a `to*()` call registers the binding.
|
|
109
|
+
#binding: Binding<Value> | undefined;
|
|
110
|
+
// Allocated only by a chain that actually displaces something — most never do.
|
|
111
|
+
#displacedByChain: Array<Binding> | undefined;
|
|
97
112
|
readonly #token: Token<Value> | Constructor<Value>;
|
|
98
|
-
readonly #
|
|
113
|
+
readonly #registration: BindingRegistration;
|
|
99
114
|
|
|
100
|
-
constructor(token: Token<Value> | Constructor<Value>,
|
|
115
|
+
constructor(token: Token<Value> | Constructor<Value>, registration: BindingRegistration) {
|
|
101
116
|
this.#token = token;
|
|
102
|
-
this.#
|
|
117
|
+
this.#registration = registration;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The registered binding, or a loud failure if no `to*()` has run yet. */
|
|
121
|
+
#registered(): Binding<Value> {
|
|
122
|
+
if (this.#binding === undefined) {
|
|
123
|
+
throw new ChainNotRegisteredError(tokenName(this.#token));
|
|
124
|
+
}
|
|
125
|
+
return this.#binding;
|
|
103
126
|
}
|
|
104
127
|
|
|
128
|
+
#register(partial: PartialBinding<Value>): this {
|
|
129
|
+
// Each `to*()` starts its own registration, so anything a previous one displaced is not this
|
|
130
|
+
// registration's to restore.
|
|
131
|
+
this.#displacedByChain = undefined;
|
|
132
|
+
this.#binding = createBinding(partial, this.#token, DEFAULT_BINDING_SLOT, undefined);
|
|
133
|
+
this.#commit(this.#binding, undefined);
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── Registration ───────────────────────────────────────────────────────────
|
|
138
|
+
|
|
105
139
|
to(type: Constructor<Value>): BindingBuilder<Value> {
|
|
106
|
-
return
|
|
107
|
-
this.#token,
|
|
108
|
-
{ kind: "class", target: type, scope: "transient" },
|
|
109
|
-
this.#commitBinding,
|
|
110
|
-
);
|
|
140
|
+
return this.#register({ kind: "class", target: type, scope: "transient" });
|
|
111
141
|
}
|
|
112
142
|
|
|
113
143
|
toSelf(): BindingBuilder<Value> {
|
|
114
144
|
if (typeof this.#token !== "function") {
|
|
115
145
|
throw new Error("toSelf() requires token to be a Constructor");
|
|
116
146
|
}
|
|
117
|
-
return
|
|
118
|
-
this.#token,
|
|
119
|
-
{ kind: "class", target: this.#token, scope: "transient" },
|
|
120
|
-
this.#commitBinding,
|
|
121
|
-
);
|
|
147
|
+
return this.#register({ kind: "class", target: this.#token, scope: "transient" });
|
|
122
148
|
}
|
|
123
149
|
|
|
124
150
|
toConstantValue(value: Value): ConstantBindingBuilder<Value> {
|
|
125
|
-
return
|
|
151
|
+
return this.#register({ kind: "constant", scope: "singleton", value });
|
|
126
152
|
}
|
|
127
153
|
|
|
128
154
|
toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value> {
|
|
129
|
-
return
|
|
130
|
-
this.#token,
|
|
131
|
-
{ kind: "dynamic", factory, scope: "transient" },
|
|
132
|
-
this.#commitBinding,
|
|
133
|
-
);
|
|
155
|
+
return this.#register({ kind: "dynamic", factory, scope: "transient" });
|
|
134
156
|
}
|
|
135
157
|
|
|
136
158
|
toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value> {
|
|
137
|
-
return
|
|
138
|
-
this.#token,
|
|
139
|
-
{ kind: "dynamic-async", factory, scope: "transient" },
|
|
140
|
-
this.#commitBinding,
|
|
141
|
-
);
|
|
159
|
+
return this.#register({ kind: "dynamic-async", factory, scope: "transient" });
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(
|
|
145
163
|
factory: (...args: { [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]> }) => Value,
|
|
146
164
|
deps: Deps,
|
|
147
165
|
): BindingBuilder<Value> {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
deps: normalizedDeps,
|
|
155
|
-
scope: "transient",
|
|
156
|
-
},
|
|
157
|
-
this.#commitBinding,
|
|
158
|
-
);
|
|
166
|
+
return this.#register({
|
|
167
|
+
kind: "resolved",
|
|
168
|
+
deps: deps.map((dependency) => normalizeToDescriptor(dependency)),
|
|
169
|
+
factory: factory as (...args: Array<unknown>) => Value,
|
|
170
|
+
scope: "transient",
|
|
171
|
+
});
|
|
159
172
|
}
|
|
160
173
|
|
|
161
174
|
toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(
|
|
162
175
|
factory: (...args: { [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]> }) => Promise<Value>,
|
|
163
176
|
deps: Deps,
|
|
164
177
|
): BindingBuilder<Value> {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
deps: normalizedDeps,
|
|
172
|
-
scope: "transient",
|
|
173
|
-
},
|
|
174
|
-
this.#commitBinding,
|
|
175
|
-
);
|
|
178
|
+
return this.#register({
|
|
179
|
+
kind: "resolved-async",
|
|
180
|
+
deps: deps.map((dependency) => normalizeToDescriptor(dependency)),
|
|
181
|
+
factory: factory as (...args: Array<unknown>) => Promise<Value>,
|
|
182
|
+
scope: "transient",
|
|
183
|
+
});
|
|
176
184
|
}
|
|
177
185
|
|
|
178
186
|
toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder {
|
|
179
|
-
return
|
|
187
|
+
return this.#register({ kind: "alias", target });
|
|
180
188
|
}
|
|
181
|
-
}
|
|
182
189
|
|
|
183
|
-
// ──
|
|
184
|
-
|
|
185
|
-
class ConstraintBuilder<Value> extends SlotBuilder implements BindingBuilder<Value> {
|
|
186
|
-
readonly #token: Token<Value> | Constructor<Value>;
|
|
187
|
-
readonly #partial: ScopedPartialBinding<Value>;
|
|
188
|
-
readonly #commitBinding: CommitFn;
|
|
189
|
-
|
|
190
|
-
constructor(token: Token<Value> | Constructor<Value>, partial: ScopedPartialBinding<Value>, commitBinding: CommitFn) {
|
|
191
|
-
super();
|
|
192
|
-
this.#token = token;
|
|
193
|
-
this.#partial = partial;
|
|
194
|
-
this.#commitBinding = commitBinding;
|
|
195
|
-
this.commit();
|
|
196
|
-
}
|
|
190
|
+
// ── Refinement ─────────────────────────────────────────────────────────────
|
|
197
191
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
slot: this.slot,
|
|
206
|
-
predicate: this.predicate,
|
|
207
|
-
};
|
|
208
|
-
this.committedId = this.#commitBinding(binding, previousId);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
singleton(): SingletonBindingBuilder<Value> {
|
|
212
|
-
const previousId = this.committedId;
|
|
213
|
-
this.committedId = undefined;
|
|
214
|
-
return new ScopeBuilder<Value>(
|
|
215
|
-
this.#token,
|
|
216
|
-
{ ...this.#partial, scope: "singleton" },
|
|
217
|
-
this.slot,
|
|
218
|
-
this.predicate,
|
|
219
|
-
this.#commitBinding,
|
|
220
|
-
previousId,
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
transient(): TransientBindingBuilder<Value> {
|
|
225
|
-
const previousId = this.committedId;
|
|
226
|
-
this.committedId = undefined;
|
|
227
|
-
return new ScopeBuilder<Value>(
|
|
228
|
-
this.#token,
|
|
229
|
-
{ ...this.#partial, scope: "transient" },
|
|
230
|
-
this.slot,
|
|
231
|
-
this.predicate,
|
|
232
|
-
this.#commitBinding,
|
|
233
|
-
previousId,
|
|
234
|
-
);
|
|
192
|
+
// Slot and predicate are what the registry indexes on, so a re-slot rebuilds the binding and
|
|
193
|
+
// re-registers it — under the original id, keeping `id()` stable for the whole chain.
|
|
194
|
+
#reslot(slot: BindingSlot, predicate: ((ctx: ConstraintContext) => boolean) | undefined): this {
|
|
195
|
+
const previous = this.#registered();
|
|
196
|
+
this.#binding = createBinding(previous, previous.token, slot, predicate, previous.id);
|
|
197
|
+
this.#commit(this.#binding, previous.id);
|
|
198
|
+
return this;
|
|
235
199
|
}
|
|
236
200
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.
|
|
240
|
-
return
|
|
241
|
-
this.#token,
|
|
242
|
-
{ ...this.#partial, scope: "scoped" },
|
|
243
|
-
this.slot,
|
|
244
|
-
this.predicate,
|
|
245
|
-
this.#commitBinding,
|
|
246
|
-
previousId,
|
|
247
|
-
);
|
|
201
|
+
#withScope(scope: BindingScope): this {
|
|
202
|
+
refinableFields(this.#registered()).scope = scope;
|
|
203
|
+
this.#registration.registry.touch();
|
|
204
|
+
return this;
|
|
248
205
|
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// ── ScopeBuilder ─────────────────────────────────────────────────────────────
|
|
252
206
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
#onDeactivation: DeactivationHandler<Value> | undefined;
|
|
256
|
-
#committedId: BindingIdentifier | undefined;
|
|
257
|
-
|
|
258
|
-
readonly #token: Token<Value> | Constructor<Value>;
|
|
259
|
-
readonly #partial: ScopedPartialBinding<Value>;
|
|
260
|
-
readonly #slot: BindingSlot;
|
|
261
|
-
readonly #predicate: ((ctx: ConstraintContext) => boolean) | undefined;
|
|
262
|
-
readonly #commitBinding: CommitFn;
|
|
263
|
-
readonly #initialPreviousId: BindingIdentifier | undefined;
|
|
264
|
-
|
|
265
|
-
constructor(
|
|
266
|
-
token: Token<Value> | Constructor<Value>,
|
|
267
|
-
partial: ScopedPartialBinding<Value>,
|
|
268
|
-
slot: BindingSlot,
|
|
269
|
-
predicate: ((ctx: ConstraintContext) => boolean) | undefined,
|
|
270
|
-
commitBinding: CommitFn,
|
|
271
|
-
initialPreviousId?: BindingIdentifier,
|
|
272
|
-
) {
|
|
273
|
-
this.#token = token;
|
|
274
|
-
this.#partial = partial;
|
|
275
|
-
this.#slot = slot;
|
|
276
|
-
this.#predicate = predicate;
|
|
277
|
-
this.#commitBinding = commitBinding;
|
|
278
|
-
this.#initialPreviousId = initialPreviousId;
|
|
279
|
-
this.#commit();
|
|
207
|
+
when(predicate: (ctx: ConstraintContext) => boolean): this {
|
|
208
|
+
return this.#reslot(this.#registered().slot, predicate);
|
|
280
209
|
}
|
|
281
210
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const binding: Binding<Value> = {
|
|
285
|
-
...this.#partial,
|
|
286
|
-
id: generateBindingId(),
|
|
287
|
-
inFlight: false,
|
|
288
|
-
token: this.#token,
|
|
289
|
-
slot: this.#slot,
|
|
290
|
-
predicate: this.#predicate,
|
|
291
|
-
onActivation: this.#onActivation,
|
|
292
|
-
onDeactivation: this.#onDeactivation,
|
|
293
|
-
};
|
|
294
|
-
this.#committedId = this.#commitBinding(binding, previousId);
|
|
211
|
+
whenNamed(name: string): this {
|
|
212
|
+
return this.#reslot({ ...this.#registered().slot, name }, this.#registered().predicate);
|
|
295
213
|
}
|
|
296
214
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
this.#
|
|
300
|
-
return this;
|
|
215
|
+
whenTagged(tag: string, value: unknown): this {
|
|
216
|
+
const binding = this.#registered();
|
|
217
|
+
return this.#reslot(updateSlotTag(binding.slot, tag, value), binding.predicate);
|
|
301
218
|
}
|
|
302
219
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
220
|
+
whenDefault(): this {
|
|
221
|
+
// The default slot is what a fresh registration already has, so there is nothing to re-slot —
|
|
222
|
+
// but an unregistered chain must fail here exactly as it does in every other refinement.
|
|
223
|
+
this.#registered();
|
|
306
224
|
return this;
|
|
307
225
|
}
|
|
308
226
|
|
|
309
|
-
|
|
310
|
-
return this.#
|
|
227
|
+
singleton(): SingletonBindingBuilder<Value> {
|
|
228
|
+
return this.#withScope("singleton");
|
|
311
229
|
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// ── ConstantBuilder ───────────────────────────────────────────────────────────
|
|
315
|
-
|
|
316
|
-
class ConstantBuilder<Value>
|
|
317
|
-
extends SlotBuilder
|
|
318
|
-
implements ConstantBindingBuilder<Value>, SingletonLifecycleBuilder<Value>
|
|
319
|
-
{
|
|
320
|
-
#onActivation: ActivationHandler<Value> | undefined;
|
|
321
|
-
#onDeactivation: DeactivationHandler<Value> | undefined;
|
|
322
230
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
readonly #commitBinding: CommitFn;
|
|
326
|
-
|
|
327
|
-
constructor(token: Token<Value> | Constructor<Value>, value: Value, commitBinding: CommitFn) {
|
|
328
|
-
super();
|
|
329
|
-
this.#token = token;
|
|
330
|
-
this.#value = value;
|
|
331
|
-
this.#commitBinding = commitBinding;
|
|
332
|
-
this.commit();
|
|
231
|
+
transient(): TransientBindingBuilder<Value> {
|
|
232
|
+
return this.#withScope("transient");
|
|
333
233
|
}
|
|
334
234
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const binding: Binding<Value> = {
|
|
338
|
-
kind: "constant",
|
|
339
|
-
id: generateBindingId(),
|
|
340
|
-
inFlight: false,
|
|
341
|
-
token: this.#token,
|
|
342
|
-
slot: this.slot,
|
|
343
|
-
predicate: this.predicate,
|
|
344
|
-
value: this.#value,
|
|
345
|
-
scope: "singleton",
|
|
346
|
-
onActivation: this.#onActivation,
|
|
347
|
-
onDeactivation: this.#onDeactivation,
|
|
348
|
-
};
|
|
349
|
-
this.committedId = this.#commitBinding(binding, previousId);
|
|
235
|
+
scoped(): ScopedBindingBuilder<Value> {
|
|
236
|
+
return this.#withScope("scoped");
|
|
350
237
|
}
|
|
351
238
|
|
|
352
239
|
onActivation(fn: ActivationHandler<Value>): this {
|
|
353
|
-
this.#onActivation = fn;
|
|
354
|
-
this.
|
|
240
|
+
refinableFields(this.#registered()).onActivation = fn;
|
|
241
|
+
this.#registration.registry.touch();
|
|
355
242
|
return this;
|
|
356
243
|
}
|
|
357
244
|
|
|
358
245
|
onDeactivation(fn: DeactivationHandler<Value>): this {
|
|
359
|
-
this.#onDeactivation = fn;
|
|
360
|
-
this.
|
|
246
|
+
refinableFields(this.#registered()).onDeactivation = fn;
|
|
247
|
+
this.#registration.registry.touch();
|
|
361
248
|
return this;
|
|
362
249
|
}
|
|
363
|
-
}
|
|
364
250
|
|
|
365
|
-
|
|
251
|
+
id(): BindingIdentifier {
|
|
252
|
+
return this.#registered().id;
|
|
253
|
+
}
|
|
366
254
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
255
|
+
// ── Registry ───────────────────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Register `binding`, first removing `previousId` when the chain is re-slotting.
|
|
259
|
+
*
|
|
260
|
+
* @remarks A `when*()` that follows `to*()` re-slots an already-live binding and can displace one
|
|
261
|
+
* the final shape would never conflict with. Those stay parked in `#displacedByChain` until the
|
|
262
|
+
* chain settles, then get restored.
|
|
263
|
+
*/
|
|
264
|
+
#commit(binding: Binding<Value>, previousId: BindingIdentifier | undefined): void {
|
|
265
|
+
const { registry, moduleBindingIds } = this.#registration;
|
|
266
|
+
// The registry is heterogeneous by design — one instance holds every value type — so the
|
|
267
|
+
// chain's `Binding<Value>` is erased once, here, rather than at each call site.
|
|
268
|
+
const registered = binding as Binding;
|
|
269
|
+
|
|
270
|
+
if (previousId !== undefined) {
|
|
271
|
+
registry.removeById(previousId);
|
|
272
|
+
}
|
|
273
|
+
const displaced = registry.add(registered);
|
|
274
|
+
if (displaced !== undefined) {
|
|
275
|
+
(this.#displacedByChain ??= []).push(displaced);
|
|
276
|
+
}
|
|
277
|
+
if (previousId !== undefined && this.#displacedByChain !== undefined) {
|
|
278
|
+
this.#restoreNonConflicting(registered, this.#displacedByChain);
|
|
279
|
+
}
|
|
280
|
+
if (moduleBindingIds !== undefined) {
|
|
281
|
+
trackBindingForModule(moduleBindingIds, registered.id, previousId);
|
|
282
|
+
}
|
|
382
283
|
}
|
|
383
284
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
predicate: this.predicate,
|
|
393
|
-
target: this.#target,
|
|
394
|
-
};
|
|
395
|
-
this.committedId = this.#commitBinding(binding, previousId);
|
|
285
|
+
#restoreNonConflicting(binding: Binding, displaced: Array<Binding>): void {
|
|
286
|
+
for (let index = displaced.length - 1; index >= 0; index -= 1) {
|
|
287
|
+
const candidate = displaced[index]!;
|
|
288
|
+
if (!displacesRestoredBinding(binding, candidate)) {
|
|
289
|
+
this.#registration.registry.add(candidate);
|
|
290
|
+
displaced.splice(index, 1);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
396
293
|
}
|
|
397
294
|
}
|