@codefast/di 0.5.0-canary.7 → 0.5.0-canary.9
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 +205 -0
- package/README.md +6 -2
- package/dist/binding.d.ts +85 -24
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +55 -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 +144 -192
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +141 -201
- 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 +24 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +30 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/introspection/inspector.d.ts +0 -1
- package/dist/introspection/inspector.d.ts.map +1 -1
- package/dist/introspection/inspector.js +3 -8
- 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 +81 -78
- package/dist/registry.js.map +1 -1
- package/dist/resolution/activation-need.d.ts +27 -0
- package/dist/resolution/activation-need.d.ts.map +1 -0
- package/dist/resolution/activation-need.js +68 -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 +118 -0
- package/dist/resolution/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/binding-scope.d.ts +5 -2
- package/dist/resolution/binding-scope.d.ts.map +1 -1
- package/dist/resolution/binding-scope.js +6 -17
- package/dist/resolution/binding-scope.js.map +1 -1
- package/dist/resolution/binding-select.d.ts +8 -1
- package/dist/resolution/binding-select.d.ts.map +1 -1
- package/dist/resolution/binding-select.js +14 -36
- 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 +48 -1
- package/dist/resolution/environment.d.ts.map +1 -1
- package/dist/resolution/environment.js +134 -5
- 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 +69 -49
- 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 +60 -62
- package/dist/resolution/lifecycle.js.map +1 -1
- package/dist/resolution/resolution-path.d.ts +84 -17
- package/dist/resolution/resolution-path.d.ts.map +1 -1
- package/dist/resolution/resolution-path.js +68 -23
- package/dist/resolution/resolution-path.js.map +1 -1
- package/dist/resolution/resolve-options.d.ts +41 -4
- package/dist/resolution/resolve-options.d.ts.map +1 -1
- package/dist/resolution/resolve-options.js +25 -1
- package/dist/resolution/resolve-options.js.map +1 -1
- package/dist/resolution/resolver.d.ts +33 -10
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +471 -830
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/scope.d.ts +11 -15
- package/dist/resolution/scope.d.ts.map +1 -1
- package/dist/resolution/scope.js +55 -43
- package/dist/resolution/scope.js.map +1 -1
- package/package.json +10 -106
- package/src/binding.ts +146 -24
- package/src/constructor-type.ts +4 -5
- package/src/container/binding-builders.ts +184 -284
- package/src/container/container.ts +161 -221
- package/src/decorators/inject.ts +3 -5
- package/src/errors.ts +38 -0
- package/src/index.ts +4 -1
- package/src/introspection/inspector.ts +3 -9
- package/src/metadata/metadata-keys.ts +3 -6
- package/src/registry.ts +90 -94
- package/src/resolution/activation-need.ts +85 -0
- package/src/resolution/binding-lookup-cache.ts +148 -0
- package/src/resolution/binding-scope.ts +6 -17
- package/src/resolution/binding-select.ts +15 -39
- package/src/resolution/class-introspector.ts +74 -0
- package/src/resolution/diagnostics.ts +43 -0
- package/src/resolution/environment.ts +181 -5
- package/src/resolution/instantiation-plan.ts +116 -64
- package/src/resolution/lifecycle.ts +69 -62
- package/src/resolution/resolution-path.ts +122 -43
- package/src/resolution/resolve-options.ts +51 -4
- package/src/resolution/resolver.ts +649 -1081
- package/src/resolution/scope.ts +58 -47
|
@@ -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,282 @@ import type {
|
|
|
18
16
|
SingletonLifecycleBuilder,
|
|
19
17
|
TransientBindingBuilder,
|
|
20
18
|
} from "#/binding";
|
|
21
|
-
import { DEFAULT_BINDING_SLOT,
|
|
19
|
+
import { bindingSlotEquals, clearBindingFrame, 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, SelfBindingRequiresClassError } 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
|
-
throw new
|
|
145
|
+
throw new SelfBindingRequiresClassError(tokenName(this.#token));
|
|
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", scope: "transient", 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
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
this.predicate,
|
|
245
|
-
this.#commitBinding,
|
|
246
|
-
previousId,
|
|
247
|
-
);
|
|
201
|
+
#withScope(scope: BindingScope): this {
|
|
202
|
+
const binding = this.#registered();
|
|
203
|
+
refinableFields(binding).scope = scope;
|
|
204
|
+
// The frame reports the scope, so a resolve before this call memoized the previous one.
|
|
205
|
+
clearBindingFrame(binding);
|
|
206
|
+
this.#registration.registry.touch();
|
|
207
|
+
return this;
|
|
248
208
|
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// ── ScopeBuilder ─────────────────────────────────────────────────────────────
|
|
252
209
|
|
|
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();
|
|
210
|
+
when(predicate: (ctx: ConstraintContext) => boolean): this {
|
|
211
|
+
return this.#reslot(this.#registered().slot, predicate);
|
|
280
212
|
}
|
|
281
213
|
|
|
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);
|
|
214
|
+
whenNamed(name: string): this {
|
|
215
|
+
return this.#reslot({ ...this.#registered().slot, name }, this.#registered().predicate);
|
|
295
216
|
}
|
|
296
217
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
this.#
|
|
300
|
-
return this;
|
|
218
|
+
whenTagged(tag: string, value: unknown): this {
|
|
219
|
+
const binding = this.#registered();
|
|
220
|
+
return this.#reslot(updateSlotTag(binding.slot, tag, value), binding.predicate);
|
|
301
221
|
}
|
|
302
222
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
223
|
+
whenDefault(): this {
|
|
224
|
+
// The default slot is what a fresh registration already has, so there is nothing to re-slot —
|
|
225
|
+
// but an unregistered chain must fail here exactly as it does in every other refinement.
|
|
226
|
+
this.#registered();
|
|
306
227
|
return this;
|
|
307
228
|
}
|
|
308
229
|
|
|
309
|
-
|
|
310
|
-
return this.#
|
|
230
|
+
singleton(): SingletonBindingBuilder<Value> {
|
|
231
|
+
return this.#withScope("singleton");
|
|
311
232
|
}
|
|
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
233
|
|
|
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();
|
|
234
|
+
transient(): TransientBindingBuilder<Value> {
|
|
235
|
+
return this.#withScope("transient");
|
|
333
236
|
}
|
|
334
237
|
|
|
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);
|
|
238
|
+
scoped(): ScopedBindingBuilder<Value> {
|
|
239
|
+
return this.#withScope("scoped");
|
|
350
240
|
}
|
|
351
241
|
|
|
352
242
|
onActivation(fn: ActivationHandler<Value>): this {
|
|
353
|
-
this.#onActivation = fn;
|
|
354
|
-
this.
|
|
243
|
+
refinableFields(this.#registered()).onActivation = fn;
|
|
244
|
+
this.#registration.registry.touch();
|
|
355
245
|
return this;
|
|
356
246
|
}
|
|
357
247
|
|
|
358
248
|
onDeactivation(fn: DeactivationHandler<Value>): this {
|
|
359
|
-
this.#onDeactivation = fn;
|
|
360
|
-
this.
|
|
249
|
+
refinableFields(this.#registered()).onDeactivation = fn;
|
|
250
|
+
this.#registration.registry.touch();
|
|
361
251
|
return this;
|
|
362
252
|
}
|
|
363
|
-
}
|
|
364
253
|
|
|
365
|
-
|
|
254
|
+
id(): BindingIdentifier {
|
|
255
|
+
return this.#registered().id;
|
|
256
|
+
}
|
|
366
257
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
258
|
+
// ── Registry ───────────────────────────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Register `binding`, first removing `previousId` when the chain is re-slotting.
|
|
262
|
+
*
|
|
263
|
+
* @remarks A `when*()` that follows `to*()` re-slots an already-live binding and can displace one
|
|
264
|
+
* the final shape would never conflict with. Those stay parked in `#displacedByChain` until the
|
|
265
|
+
* chain settles, then get restored.
|
|
266
|
+
*/
|
|
267
|
+
#commit(binding: Binding<Value>, previousId: BindingIdentifier | undefined): void {
|
|
268
|
+
const { registry, moduleBindingIds } = this.#registration;
|
|
269
|
+
// The registry is heterogeneous by design — one instance holds every value type — so the
|
|
270
|
+
// chain's `Binding<Value>` is erased once, here, rather than at each call site.
|
|
271
|
+
const registered = binding as Binding;
|
|
272
|
+
|
|
273
|
+
if (previousId !== undefined) {
|
|
274
|
+
registry.removeById(previousId);
|
|
275
|
+
}
|
|
276
|
+
const displaced = registry.add(registered);
|
|
277
|
+
if (displaced !== undefined) {
|
|
278
|
+
(this.#displacedByChain ??= []).push(displaced);
|
|
279
|
+
}
|
|
280
|
+
if (previousId !== undefined && this.#displacedByChain !== undefined) {
|
|
281
|
+
this.#restoreNonConflicting(registered, this.#displacedByChain);
|
|
282
|
+
}
|
|
283
|
+
if (moduleBindingIds !== undefined) {
|
|
284
|
+
trackBindingForModule(moduleBindingIds, registered.id, previousId);
|
|
285
|
+
}
|
|
382
286
|
}
|
|
383
287
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
predicate: this.predicate,
|
|
393
|
-
target: this.#target,
|
|
394
|
-
};
|
|
395
|
-
this.committedId = this.#commitBinding(binding, previousId);
|
|
288
|
+
#restoreNonConflicting(binding: Binding, displaced: Array<Binding>): void {
|
|
289
|
+
for (let index = displaced.length - 1; index >= 0; index -= 1) {
|
|
290
|
+
const candidate = displaced[index]!;
|
|
291
|
+
if (!displacesRestoredBinding(binding, candidate)) {
|
|
292
|
+
this.#registration.registry.add(candidate);
|
|
293
|
+
displaced.splice(index, 1);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
396
296
|
}
|
|
397
297
|
}
|