@codefast/di 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +111 -0
- package/README.md +26 -12
- package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +21 -28
- package/dist/container/{binding-builders.js → binding-chain.js} +53 -163
- package/dist/container/container.d.ts +11 -2
- package/dist/container/container.js +107 -64
- package/dist/core/binding-builders.d.ts +114 -0
- package/dist/core/binding-declaration.d.ts +7 -0
- package/dist/core/binding-declaration.js +3 -2
- package/dist/core/binding.d.ts +21 -149
- package/dist/core/binding.js +15 -25
- package/dist/core/module.d.ts +1 -1
- package/dist/core/registry.d.ts +9 -24
- package/dist/core/registry.js +30 -155
- package/dist/core/tag.js +1 -1
- package/dist/decorators/inject.js +5 -5
- package/dist/decorators/injectable.js +2 -2
- package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +3 -3
- package/dist/decorators/{decorator-metadata.js → metadata-record.js} +1 -1
- package/dist/{errors/errors.d.ts → errors.d.ts} +7 -6
- package/dist/{errors/errors.js → errors.js} +8 -7
- package/dist/index.d.ts +12 -10
- package/dist/index.js +6 -6
- package/dist/injection/descriptor.d.ts +1 -1
- package/dist/introspection/dependency-graph.d.ts +1 -1
- package/dist/introspection/dependency-graph.js +3 -4
- package/dist/introspection/explanation.d.ts +101 -0
- package/dist/introspection/explanation.js +151 -0
- package/dist/introspection/inspector.d.ts +9 -2
- package/dist/introspection/inspector.js +25 -20
- package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +9 -2
- package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +45 -28
- package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +3 -5
- package/dist/lifecycle/{scope-manager.js → scopes.js} +8 -19
- package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
- package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
- package/dist/metadata/types.js +1 -0
- package/dist/metadata/verify.d.ts +43 -0
- package/dist/metadata/verify.js +133 -0
- package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +1 -1
- package/dist/metadata/{verifying-metadata-reader.js → verifying-reader.js} +2 -2
- package/dist/resolution/cache/activation-need.d.ts +1 -1
- package/dist/resolution/cache/activation-need.js +5 -1
- package/dist/resolution/cache/class-introspector.d.ts +27 -36
- package/dist/resolution/cache/class-introspector.js +61 -159
- package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -2
- package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +40 -8
- package/dist/resolution/context.d.ts +1 -1
- package/dist/resolution/context.js +1 -1
- package/dist/resolution/{path/resolution-path.js → path.js} +1 -1
- package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +2 -4
- package/dist/resolution/plan/{plan-codegen.js → codegen.js} +2 -4
- package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
- package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +4 -4
- package/dist/resolution/resolver.d.ts +7 -7
- package/dist/resolution/resolver.js +308 -121
- package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
- package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
- package/dist/resolution/select/constraints.js +1 -1
- package/package.json +79 -71
- package/dist/core/binding-scope.d.ts +0 -12
- package/dist/core/binding-scope.js +0 -12
- /package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +0 -0
- /package/dist/{ambient/active-container.js → ambient-container.js} +0 -0
- /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
- /package/dist/decorators/{lifecycle-decorators.d.ts → lifecycle.d.ts} +0 -0
- /package/dist/decorators/{decorator-metadata.d.ts → metadata-record.d.ts} +0 -0
- /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
- /package/dist/injection/{resolve-options.js → dependency-slot.js} +0 -0
- /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
- /package/dist/{errors → introspection}/diagnostics.js +0 -0
- /package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +0 -0
- /package/dist/metadata/{metadata-keys.js → keys.js} +0 -0
- /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
- /package/dist/resolution/{path/resolution-path.d.ts → path.d.ts} +0 -0
package/dist/core/binding.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BindingTag, TagKeyMask } from "#core/tag";
|
|
2
2
|
import type { Token } from "#core/token";
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
3
|
+
import type { BindingConstraint, BindingIdentifier, BindingScope, Constructor, ResolutionContext, ResolutionFrame } from "#core/types";
|
|
4
|
+
import type { InjectionDescriptor } from "#injection/descriptor";
|
|
5
5
|
/**
|
|
6
6
|
* The criterion set a binding registers under and a request matches against.
|
|
7
7
|
*
|
|
@@ -106,13 +106,6 @@ interface BindingBase<Value> {
|
|
|
106
106
|
* container resolving each binding once would build and never read again.
|
|
107
107
|
*/
|
|
108
108
|
activationStamp: number;
|
|
109
|
-
/**
|
|
110
|
-
* Where the binding stands in registration order, or {@link UNREGISTERED_ORDER} before its first add.
|
|
111
|
-
*
|
|
112
|
-
* @remarks Registry-owned: stamped by the first `add` and kept through every re-slot and restore, so a
|
|
113
|
-
* binding taken out and put back returns to its place rather than behind later registrations.
|
|
114
|
-
*/
|
|
115
|
-
registrationOrder: number;
|
|
116
109
|
/**
|
|
117
110
|
* Cached singleton instance, or {@link NO_INSTANCE}.
|
|
118
111
|
*
|
|
@@ -120,6 +113,13 @@ interface BindingBase<Value> {
|
|
|
120
113
|
* a field read replaces a keyed lookup on the hottest resolve shape there is.
|
|
121
114
|
*/
|
|
122
115
|
instance: unknown;
|
|
116
|
+
/**
|
|
117
|
+
* The key every container's scoped cache files this binding's instance under.
|
|
118
|
+
*
|
|
119
|
+
* @remarks Chain-owned. It starts as the binding's id, and a scope verb that changes the scope mints a new one, so
|
|
120
|
+
* a scoped instance any child cached under an earlier scope is never found again. The id itself stays stable.
|
|
121
|
+
*/
|
|
122
|
+
scopedCacheKey: BindingIdentifier;
|
|
123
123
|
readonly token: Token<Value> | Constructor<Value>;
|
|
124
124
|
readonly slot: BindingSlot;
|
|
125
125
|
readonly predicate?: BindingConstraint | undefined;
|
|
@@ -228,40 +228,21 @@ export interface AliasBinding<Value> extends BindingBase<Value> {
|
|
|
228
228
|
*/
|
|
229
229
|
export type Binding<Value = unknown> = ClassBinding<Value> | DynamicBinding<Value> | DynamicAsyncBinding<Value> | ResolvedBinding<Value> | ResolvedAsyncBinding<Value> | ConstantBinding<Value> | AliasBinding<Value>;
|
|
230
230
|
/**
|
|
231
|
-
*
|
|
231
|
+
* The scope a binding resolves under.
|
|
232
232
|
*
|
|
233
|
-
* @since
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Writable view of the one selection field a chain may refine without re-registering: nothing
|
|
238
|
-
* indexes on the predicate, so the registry rewrites it in place and re-homes the binding itself.
|
|
239
|
-
*
|
|
240
|
-
* @since 0.10.0
|
|
241
|
-
*/
|
|
242
|
-
export interface PredicateField {
|
|
243
|
-
predicate: BindingConstraint | undefined;
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Narrows a registered binding to its predicate for the registry to rewrite.
|
|
233
|
+
* @remarks Every kind declares one — an alias declares `transient`, since it defers scoping to the
|
|
234
|
+
* binding it points at — so this is a field read, kept as a named function because it is the
|
|
235
|
+
* vocabulary validation and introspection speak.
|
|
247
236
|
*
|
|
248
|
-
* @since 0.
|
|
237
|
+
* @since 0.3.16-canary.0
|
|
249
238
|
*/
|
|
250
|
-
export declare function
|
|
239
|
+
export declare function effectiveBindingScope(binding: Binding): BindingScope;
|
|
251
240
|
/**
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* @since 0.10.0
|
|
255
|
-
*/
|
|
256
|
-
export interface MembershipField {
|
|
257
|
-
isMany: boolean;
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Narrows a registered binding to its membership flag for the registry to set.
|
|
241
|
+
* Returns a process-unique identifier for a new binding.
|
|
261
242
|
*
|
|
262
|
-
* @since 0.
|
|
243
|
+
* @since 0.3.16-canary.0
|
|
263
244
|
*/
|
|
264
|
-
export declare function
|
|
245
|
+
export declare function generateBindingId(): BindingIdentifier;
|
|
265
246
|
/**
|
|
266
247
|
* The stamp of a binding whose activation need has not been computed under the current versions.
|
|
267
248
|
*
|
|
@@ -269,121 +250,12 @@ export declare function writableMembership(binding: Binding): MembershipField;
|
|
|
269
250
|
*/
|
|
270
251
|
export declare const NO_ACTIVATION_STAMP = -1;
|
|
271
252
|
/**
|
|
272
|
-
*
|
|
253
|
+
* Drops the memoized resolution frame, for a scope change that alters what the frame reports.
|
|
273
254
|
*
|
|
274
|
-
* @
|
|
275
|
-
|
|
276
|
-
export declare const UNREGISTERED_ORDER = -1;
|
|
277
|
-
/**
|
|
278
|
-
* Drops the memoized resolution frame, for a refinement that changes what the frame reports.
|
|
279
|
-
*
|
|
280
|
-
* @remarks The frame derives from `scope` and `slot`, both of which a chain now writes in place on
|
|
281
|
-
* the registered object, so every such refinement clears it.
|
|
255
|
+
* @remarks The frame derives from `scope` and `slot`; the slot is final once the binding registers, so
|
|
256
|
+
* only a scope written in place on the registered object clears it.
|
|
282
257
|
*
|
|
283
258
|
* @since 0.5.0-canary.9
|
|
284
259
|
*/
|
|
285
260
|
export declare function clearBindingFrame<Value>(binding: Binding<Value>): void;
|
|
286
|
-
/**
|
|
287
|
-
* Common slot-constraint + id methods shared by all concrete binding builders.
|
|
288
|
-
*
|
|
289
|
-
* @since 0.3.16-canary.0
|
|
290
|
-
*/
|
|
291
|
-
export interface SlotConstrainedBuilder<Names extends string = string> {
|
|
292
|
-
/** Narrows the binding to requests the predicate accepts, evaluated on every resolve. */
|
|
293
|
-
when(predicate: BindingConstraint): this;
|
|
294
|
-
/** Declares the binding's slot name, one of the names the token declares. */
|
|
295
|
-
whenNamed(name: Names): this;
|
|
296
|
-
/** Declares one criterion of the binding's slot, replacing any earlier criterion of the same key. */
|
|
297
|
-
whenTagged(criterion: BindingTag): this;
|
|
298
|
-
/** Keeps the binding on the default slot, the one an unconstrained request selects. */
|
|
299
|
-
whenDefault(): this;
|
|
300
|
-
/**
|
|
301
|
-
* Makes the binding a collection member: one of several the token's `resolveAll` returns, never
|
|
302
|
-
* what a single `resolve` selects, and outside slot last-wins. It keeps the default slot.
|
|
303
|
-
*/
|
|
304
|
-
many(): this;
|
|
305
|
-
/** The identifier this binding is registered under. */
|
|
306
|
-
id(): BindingIdentifier;
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* The `to*` step of the fluent chain, choosing what a token resolves to.
|
|
310
|
-
*
|
|
311
|
-
* @since 0.3.16-canary.0
|
|
312
|
-
*/
|
|
313
|
-
export interface BindToBuilder<Value, Names extends string = string> {
|
|
314
|
-
to(type: Constructor<Value>): BindingBuilder<Value, Names>;
|
|
315
|
-
toSelf(): BindingBuilder<Value, Names>;
|
|
316
|
-
toConstantValue(value: Value): ConstantBindingBuilder<Value, Names>;
|
|
317
|
-
toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value, Names>;
|
|
318
|
-
toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value, Names>;
|
|
319
|
-
toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
|
|
320
|
-
[K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
|
|
321
|
-
}) => Value, deps: Deps): BindingBuilder<Value, Names>;
|
|
322
|
-
toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
|
|
323
|
-
[K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
|
|
324
|
-
}) => Promise<Value>, deps: Deps): BindingBuilder<Value, Names>;
|
|
325
|
-
toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder<Names>;
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* The scope-selection step of the fluent chain.
|
|
329
|
-
*
|
|
330
|
-
* @since 0.3.16-canary.0
|
|
331
|
-
*/
|
|
332
|
-
export interface BindingBuilder<Value, Names extends string = string> extends SlotConstrainedBuilder<Names> {
|
|
333
|
-
singleton(): SingletonBindingBuilder<Value>;
|
|
334
|
-
transient(): TransientBindingBuilder<Value>;
|
|
335
|
-
scoped(): ScopedBindingBuilder<Value>;
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* The fluent chain for a constant — lifecycle hooks only, since the scope is fixed.
|
|
339
|
-
*
|
|
340
|
-
* @since 0.3.16-canary.0
|
|
341
|
-
*/
|
|
342
|
-
export interface ConstantBindingBuilder<Value, Names extends string = string> extends SlotConstrainedBuilder<Names> {
|
|
343
|
-
onActivation(fn: ActivationHandler<Value>): SingletonLifecycleBuilder<Value>;
|
|
344
|
-
onDeactivation(fn: DeactivationHandler<Value>): SingletonLifecycleBuilder<Value>;
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* The fluent chain for an alias — slot constraints only, since scoping belongs to the target.
|
|
348
|
-
*
|
|
349
|
-
* @since 0.3.16-canary.0
|
|
350
|
-
*/
|
|
351
|
-
export interface AliasBindingBuilder<Names extends string = string> extends SlotConstrainedBuilder<Names> {
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* The fluent chain after `singleton()`, where both lifecycle hooks stay available.
|
|
355
|
-
*
|
|
356
|
-
* @since 0.3.16-canary.0
|
|
357
|
-
*/
|
|
358
|
-
export interface SingletonBindingBuilder<Value> {
|
|
359
|
-
onActivation(fn: ActivationHandler<Value>): this;
|
|
360
|
-
onDeactivation(fn: DeactivationHandler<Value>): this;
|
|
361
|
-
id(): BindingIdentifier;
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* The fluent chain after `transient()`, where activation is the one lifecycle hook offered.
|
|
365
|
-
*
|
|
366
|
-
* @since 0.3.16-canary.0
|
|
367
|
-
*/
|
|
368
|
-
export interface TransientBindingBuilder<Value> {
|
|
369
|
-
onActivation(fn: ActivationHandler<Value>): this;
|
|
370
|
-
id(): BindingIdentifier;
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* The fluent chain after `scoped()`, sharing the `transient()` surface.
|
|
374
|
-
*
|
|
375
|
-
* @since 0.3.16-canary.0
|
|
376
|
-
*/
|
|
377
|
-
export interface ScopedBindingBuilder<Value> extends TransientBindingBuilder<Value> {
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* The fluent chain a constant enters once a lifecycle hook is added.
|
|
381
|
-
*
|
|
382
|
-
* @since 0.3.16-canary.0
|
|
383
|
-
*/
|
|
384
|
-
export interface SingletonLifecycleBuilder<Value> {
|
|
385
|
-
onActivation(fn: ActivationHandler<Value>): this;
|
|
386
|
-
onDeactivation(fn: DeactivationHandler<Value>): this;
|
|
387
|
-
id(): BindingIdentifier;
|
|
388
|
-
}
|
|
389
261
|
export {};
|
package/dist/core/binding.js
CHANGED
|
@@ -108,6 +108,18 @@ export function bindingSlotToString(slot) {
|
|
|
108
108
|
}
|
|
109
109
|
return parts.join(",");
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* The scope a binding resolves under.
|
|
113
|
+
*
|
|
114
|
+
* @remarks Every kind declares one — an alias declares `transient`, since it defers scoping to the
|
|
115
|
+
* binding it points at — so this is a field read, kept as a named function because it is the
|
|
116
|
+
* vocabulary validation and introspection speak.
|
|
117
|
+
*
|
|
118
|
+
* @since 0.3.16-canary.0
|
|
119
|
+
*/
|
|
120
|
+
export function effectiveBindingScope(binding) {
|
|
121
|
+
return binding.scope;
|
|
122
|
+
}
|
|
111
123
|
// ── ID generation ────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
112
124
|
let bindingIdCounter = 0;
|
|
113
125
|
/**
|
|
@@ -119,22 +131,6 @@ export function generateBindingId() {
|
|
|
119
131
|
bindingIdCounter += 1;
|
|
120
132
|
return bindingIdCounter;
|
|
121
133
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Narrows a registered binding to its predicate for the registry to rewrite.
|
|
124
|
-
*
|
|
125
|
-
* @since 0.10.0
|
|
126
|
-
*/
|
|
127
|
-
export function writablePredicate(binding) {
|
|
128
|
-
return binding;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Narrows a registered binding to its membership flag for the registry to set.
|
|
132
|
-
*
|
|
133
|
-
* @since 0.10.0
|
|
134
|
-
*/
|
|
135
|
-
export function writableMembership(binding) {
|
|
136
|
-
return binding;
|
|
137
|
-
}
|
|
138
134
|
/**
|
|
139
135
|
* The stamp of a binding whose activation need has not been computed under the current versions.
|
|
140
136
|
*
|
|
@@ -142,16 +138,10 @@ export function writableMembership(binding) {
|
|
|
142
138
|
*/
|
|
143
139
|
export const NO_ACTIVATION_STAMP = -1;
|
|
144
140
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* @since 0.11.0
|
|
148
|
-
*/
|
|
149
|
-
export const UNREGISTERED_ORDER = -1;
|
|
150
|
-
/**
|
|
151
|
-
* Drops the memoized resolution frame, for a refinement that changes what the frame reports.
|
|
141
|
+
* Drops the memoized resolution frame, for a scope change that alters what the frame reports.
|
|
152
142
|
*
|
|
153
|
-
* @remarks The frame derives from `scope` and `slot
|
|
154
|
-
*
|
|
143
|
+
* @remarks The frame derives from `scope` and `slot`; the slot is final once the binding registers, so
|
|
144
|
+
* only a scope written in place on the registered object clears it.
|
|
155
145
|
*
|
|
156
146
|
* @since 0.5.0-canary.9
|
|
157
147
|
*/
|
package/dist/core/module.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BindToBuilder } from "#core/binding";
|
|
1
|
+
import type { BindToBuilder } from "#core/binding-builders";
|
|
2
2
|
import type { BindingDeclaration, DeclaredBinding } from "#core/binding-declaration";
|
|
3
3
|
import type { Token } from "#core/token";
|
|
4
4
|
import type { Constructor } from "#core/types";
|
package/dist/core/registry.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Binding } from "#core/binding";
|
|
2
2
|
import type { BindingTag } from "#core/tag";
|
|
3
3
|
import type { Token } from "#core/token";
|
|
4
|
-
import type {
|
|
4
|
+
import type { BindingIdentifier, Constructor } from "#core/types";
|
|
5
5
|
/**
|
|
6
6
|
* @since 0.11.0
|
|
7
7
|
*/
|
|
@@ -11,6 +11,8 @@ export declare class BindingRegistry {
|
|
|
11
11
|
get version(): number;
|
|
12
12
|
/** Whether a constant has ever been registered here, and so whether teardown has anything to sweep. */
|
|
13
13
|
get hasHeldConstantBinding(): boolean;
|
|
14
|
+
/** Whether an alias has ever been registered here, and so whether a candidate list can hold one. */
|
|
15
|
+
get hasHeldAlias(): boolean;
|
|
14
16
|
/** Whether the deferred tagged-slot index has had to be built. */
|
|
15
17
|
get isTaggedIndexBuilt(): boolean;
|
|
16
18
|
/** Whether an id-keyed operation has had to build the id index. */
|
|
@@ -55,8 +57,6 @@ export declare class BindingRegistry {
|
|
|
55
57
|
allBindings(): ReadonlyArray<Binding>;
|
|
56
58
|
/** Remove all bindings. Returns all removed. */
|
|
57
59
|
clear(): ReadonlyArray<Binding>;
|
|
58
|
-
/** Whether a slot-based binding currently occupies `binding`'s slot, so adding it would displace. */
|
|
59
|
-
hasSlotOccupant(binding: Binding): boolean;
|
|
60
60
|
/**
|
|
61
61
|
* The binding indexed under one criterion.
|
|
62
62
|
*
|
|
@@ -83,28 +83,13 @@ export declare class BindingRegistry {
|
|
|
83
83
|
hasPredicateCandidate(token: Token<unknown> | Constructor): boolean;
|
|
84
84
|
/** The binding holding a token's default slot — lone or recorded — or `undefined` when none does. */
|
|
85
85
|
getDefaultSlotBinding(token: Token<unknown> | Constructor): Binding | undefined;
|
|
86
|
-
/** A token's lone default-slot binding — the first read of every synchronous resolve. */
|
|
87
|
-
getFastDefault(token: Token<unknown> | Constructor): Binding | undefined;
|
|
88
86
|
/**
|
|
89
|
-
*
|
|
90
|
-
* whether it was live; the caller registers it again with `add`.
|
|
87
|
+
* The token's default-slot alias, which forwards a request's criteria to its target, or `undefined`.
|
|
91
88
|
*
|
|
92
|
-
* @remarks
|
|
93
|
-
*
|
|
94
|
-
* it registered, and a refinement must not resurrect it.
|
|
89
|
+
* @remarks A registry that never held an alias answers without probing, so a request missing every slot of a
|
|
90
|
+
* token pays for forwarding only where forwarding can happen.
|
|
95
91
|
*/
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
* never the token's lone default answer, and nothing else indexes on membership.
|
|
100
|
-
*/
|
|
101
|
-
setMany(binding: Binding): void;
|
|
102
|
-
/**
|
|
103
|
-
* Adds a predicate to a live binding in place.
|
|
104
|
-
*
|
|
105
|
-
* @remarks Only ever narrows — `when()` composes with any existing predicate — so the argument is
|
|
106
|
-
* never absent. Nothing indexes on the predicate, so the binding object and its id stay; a lone
|
|
107
|
-
* binding moves to a record because the lone map holds default-slot bindings with no predicate.
|
|
108
|
-
*/
|
|
109
|
-
setPredicate(binding: Binding, predicate: BindingConstraint): void;
|
|
92
|
+
defaultSlotAlias(token: Token<unknown> | Constructor): Binding | undefined;
|
|
93
|
+
/** A token's lone default-slot binding — the first read of every synchronous resolve. */
|
|
94
|
+
getFastDefault(token: Token<unknown> | Constructor): Binding | undefined;
|
|
110
95
|
}
|
package/dist/core/registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bindingSlotEquals
|
|
1
|
+
import { bindingSlotEquals } from "#core/binding";
|
|
2
2
|
import { getOrInsert } from "#core/map-upsert";
|
|
3
3
|
import { advanceStateEpoch } from "#core/state-epoch";
|
|
4
4
|
const NO_BINDINGS = Object.freeze([]);
|
|
@@ -21,8 +21,8 @@ function createTokenRecord(bindings) {
|
|
|
21
21
|
* that only ever resolves through its parent — every per-request child — allocates no map.
|
|
22
22
|
*/
|
|
23
23
|
const EMPTY_LONE = new Map();
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const HELD_CONSTANT = 1;
|
|
25
|
+
const HELD_ALIAS = 2;
|
|
26
26
|
/**
|
|
27
27
|
* @since 0.11.0
|
|
28
28
|
*/
|
|
@@ -35,18 +35,14 @@ export class BindingRegistry {
|
|
|
35
35
|
// Every other token — several bindings, a tagged slot, a predicate — has a record here, and a
|
|
36
36
|
// token is in exactly one of the two maps. Allocated by the first token that needs a record.
|
|
37
37
|
#records;
|
|
38
|
-
// The binding the last `add` placed and where it landed: the fluent chain refines what it just
|
|
39
|
-
// registered, so a refinement that follows its own add re-slots without a map probe.
|
|
40
|
-
#lastAdded;
|
|
41
|
-
#lastAddedRecord;
|
|
42
38
|
// Built on the first id-keyed read and maintained from then on: a bind-and-resolve container
|
|
43
39
|
// never asks by id, so it never pays for the second map.
|
|
44
40
|
#byId;
|
|
45
41
|
// Set when the first tagged slot lands and never cleared, like the tagged maps it stands for.
|
|
46
42
|
#taggedIndexBuilt = false;
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
#
|
|
43
|
+
// Kinds registered here at least once, never cleared: only the negative answers must be exact, so a
|
|
44
|
+
// container holding no constant skips its teardown sweep and one holding no alias skips its forwarding probe.
|
|
45
|
+
#heldKinds = 0;
|
|
50
46
|
/** Monotonic version — increments on every mutation. */
|
|
51
47
|
get version() {
|
|
52
48
|
return this.#version;
|
|
@@ -59,7 +55,11 @@ export class BindingRegistry {
|
|
|
59
55
|
}
|
|
60
56
|
/** Whether a constant has ever been registered here, and so whether teardown has anything to sweep. */
|
|
61
57
|
get hasHeldConstantBinding() {
|
|
62
|
-
return this.#
|
|
58
|
+
return (this.#heldKinds & HELD_CONSTANT) !== 0;
|
|
59
|
+
}
|
|
60
|
+
/** Whether an alias has ever been registered here, and so whether a candidate list can hold one. */
|
|
61
|
+
get hasHeldAlias() {
|
|
62
|
+
return (this.#heldKinds & HELD_ALIAS) !== 0;
|
|
63
63
|
}
|
|
64
64
|
/** Whether the deferred tagged-slot index has had to be built. */
|
|
65
65
|
get isTaggedIndexBuilt() {
|
|
@@ -88,25 +88,21 @@ export class BindingRegistry {
|
|
|
88
88
|
*/
|
|
89
89
|
add(binding) {
|
|
90
90
|
this.#bump();
|
|
91
|
-
if (binding.registrationOrder === UNREGISTERED_ORDER) {
|
|
92
|
-
registrationCounter += 1;
|
|
93
|
-
binding.registrationOrder = registrationCounter;
|
|
94
|
-
}
|
|
95
91
|
if (binding.kind === "constant") {
|
|
96
|
-
this.#
|
|
92
|
+
this.#heldKinds |= HELD_CONSTANT;
|
|
93
|
+
}
|
|
94
|
+
else if (binding.kind === "alias") {
|
|
95
|
+
this.#heldKinds |= HELD_ALIAS;
|
|
97
96
|
}
|
|
98
97
|
const key = binding.token;
|
|
99
98
|
const records = this.#records;
|
|
100
99
|
if (records !== undefined) {
|
|
101
100
|
const record = records.get(key);
|
|
102
101
|
if (record !== undefined) {
|
|
103
|
-
this.#lastAdded = binding;
|
|
104
|
-
this.#lastAddedRecord = record;
|
|
105
102
|
return this.#addToRecord(key, record, binding);
|
|
106
103
|
}
|
|
107
104
|
}
|
|
108
105
|
const occupant = this.#lone.get(key);
|
|
109
|
-
this.#lastAdded = binding;
|
|
110
106
|
if (occupant === undefined) {
|
|
111
107
|
// The common bind: a fresh token taking the lone seat, one probe and one write.
|
|
112
108
|
if (this.#byId !== undefined) {
|
|
@@ -114,33 +110,30 @@ export class BindingRegistry {
|
|
|
114
110
|
}
|
|
115
111
|
if (isDefaultSlotBinding(binding)) {
|
|
116
112
|
this.#setLone(key, binding);
|
|
117
|
-
this.#lastAddedRecord = undefined;
|
|
118
113
|
}
|
|
119
114
|
else {
|
|
120
|
-
this.#
|
|
115
|
+
this.#createRecord(key, [binding]);
|
|
121
116
|
}
|
|
122
117
|
return undefined;
|
|
123
118
|
}
|
|
124
119
|
// Same slot, last wins: the newcomer takes the lone seat and nothing else moves.
|
|
125
120
|
if (isDefaultSlotBinding(binding)) {
|
|
126
121
|
this.#setLone(key, binding);
|
|
127
|
-
this.#lastAddedRecord = undefined;
|
|
128
122
|
if (this.#byId !== undefined) {
|
|
129
123
|
this.#byId.delete(occupant.identifier);
|
|
130
124
|
this.#byId.set(binding.identifier, binding);
|
|
131
125
|
}
|
|
132
126
|
return occupant;
|
|
133
127
|
}
|
|
134
|
-
// A second shape joins the token, which is what a record is for.
|
|
128
|
+
// A second shape joins the token, which is what a record is for; every add is the newest, so it goes last.
|
|
135
129
|
this.#deleteLone(key);
|
|
136
130
|
this.#byId?.set(binding.identifier, binding);
|
|
137
|
-
this.#
|
|
131
|
+
this.#createRecord(key, [occupant, binding]);
|
|
138
132
|
return undefined;
|
|
139
133
|
}
|
|
140
134
|
/** Remove all bindings for a token. Returns removed bindings. */
|
|
141
135
|
removeByToken(token) {
|
|
142
136
|
this.#bump();
|
|
143
|
-
this.#lastAdded = undefined;
|
|
144
137
|
const lone = this.#lone.get(token);
|
|
145
138
|
if (lone !== undefined) {
|
|
146
139
|
this.#deleteLone(token);
|
|
@@ -168,7 +161,6 @@ export class BindingRegistry {
|
|
|
168
161
|
return undefined;
|
|
169
162
|
}
|
|
170
163
|
this.#bump();
|
|
171
|
-
this.#lastAdded = undefined;
|
|
172
164
|
byId.delete(id);
|
|
173
165
|
const key = binding.token;
|
|
174
166
|
if (this.#lone.get(key)?.identifier === id) {
|
|
@@ -236,28 +228,12 @@ export class BindingRegistry {
|
|
|
236
228
|
/** Remove all bindings. Returns all removed. */
|
|
237
229
|
clear() {
|
|
238
230
|
this.#bump();
|
|
239
|
-
this.#lastAdded = undefined;
|
|
240
231
|
const all = this.allBindings();
|
|
241
232
|
this.#clearLone();
|
|
242
233
|
this.#records?.clear();
|
|
243
234
|
this.#byId?.clear();
|
|
244
235
|
return all;
|
|
245
236
|
}
|
|
246
|
-
/** Whether a slot-based binding currently occupies `binding`'s slot, so adding it would displace. */
|
|
247
|
-
hasSlotOccupant(binding) {
|
|
248
|
-
if (isPurePredicateBinding(binding)) {
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
if (this.#lone.has(binding.token)) {
|
|
252
|
-
// The lone seat is the default slot, so only a default-slot newcomer collides with it.
|
|
253
|
-
return binding.slot.tags.length === 0;
|
|
254
|
-
}
|
|
255
|
-
const record = this.#records?.get(binding.token);
|
|
256
|
-
if (record === undefined) {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
return this.#slotOccupant(record, binding.slot) !== undefined;
|
|
260
|
-
}
|
|
261
237
|
/**
|
|
262
238
|
* The binding indexed under one criterion.
|
|
263
239
|
*
|
|
@@ -304,109 +280,22 @@ export class BindingRegistry {
|
|
|
304
280
|
getDefaultSlotBinding(token) {
|
|
305
281
|
return this.#lone.get(token) ?? this.#records?.get(token)?.defaultOccupant;
|
|
306
282
|
}
|
|
307
|
-
/** A token's lone default-slot binding — the first read of every synchronous resolve. */
|
|
308
|
-
getFastDefault(token) {
|
|
309
|
-
return this.#lone.get(token);
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Takes a live binding out of every index, lets `rewrite` change its slot or predicate, and reports
|
|
313
|
-
* whether it was live; the caller registers it again with `add`.
|
|
314
|
-
*
|
|
315
|
-
* @remarks The binding keeps its object and its id, so the id index needs no touch and nothing
|
|
316
|
-
* that holds the object has to be told. `false` means the binding was unbound or displaced since
|
|
317
|
-
* it registered, and a refinement must not resurrect it.
|
|
318
|
-
*/
|
|
319
|
-
reslot(binding, rewrite) {
|
|
320
|
-
const key = binding.token;
|
|
321
|
-
if (this.#lone.get(key) === binding) {
|
|
322
|
-
this.#bump();
|
|
323
|
-
this.#lastAdded = undefined;
|
|
324
|
-
this.#deleteLone(key);
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
const record = this.#records?.get(key);
|
|
328
|
-
const index = record === undefined ? -1 : record.bindings.indexOf(binding);
|
|
329
|
-
if (record === undefined || index === -1) {
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
this.#bump();
|
|
333
|
-
this.#lastAdded = undefined;
|
|
334
|
-
// Replaced, never spliced: a walk holding the current array must not lose its place.
|
|
335
|
-
record.bindings = record.bindings.toSpliced(index, 1);
|
|
336
|
-
this.#deindexSlot(record, binding);
|
|
337
|
-
this.#settle(key, record);
|
|
338
|
-
}
|
|
339
|
-
rewrite();
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Marks a live binding as a collection member in place, moving it out of the lone map: a member is
|
|
344
|
-
* never the token's lone default answer, and nothing else indexes on membership.
|
|
345
|
-
*/
|
|
346
|
-
setMany(binding) {
|
|
347
|
-
this.#bump();
|
|
348
|
-
// Set before any (re)indexing so `#indexSlot` sees a member and leaves it out of every slot.
|
|
349
|
-
writableMembership(binding).isMany = true;
|
|
350
|
-
if (binding === this.#lastAdded) {
|
|
351
|
-
// The chain refining what it just added: where the binding sits is known without a probe.
|
|
352
|
-
const record = this.#lastAddedRecord;
|
|
353
|
-
if (record === undefined) {
|
|
354
|
-
this.#deleteLone(binding.token);
|
|
355
|
-
this.#lastAddedRecord = this.#createRecord(binding.token, [binding]);
|
|
356
|
-
}
|
|
357
|
-
else if (record.defaultOccupant === binding) {
|
|
358
|
-
record.defaultOccupant = undefined;
|
|
359
|
-
}
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
if (this.#promoteLoneToRecord(binding.token, binding)) {
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
// A default occupant that becomes a member frees its slot, so drop the stale index entry. A
|
|
366
|
-
// member cannot collapse a record back to lone, so this path does not settle.
|
|
367
|
-
this.#clearDefaultOccupant(binding.token, binding);
|
|
368
|
-
}
|
|
369
283
|
/**
|
|
370
|
-
*
|
|
284
|
+
* The token's default-slot alias, which forwards a request's criteria to its target, or `undefined`.
|
|
371
285
|
*
|
|
372
|
-
* @remarks
|
|
373
|
-
*
|
|
374
|
-
* binding moves to a record because the lone map holds default-slot bindings with no predicate.
|
|
286
|
+
* @remarks A registry that never held an alias answers without probing, so a request missing every slot of a
|
|
287
|
+
* token pays for forwarding only where forwarding can happen.
|
|
375
288
|
*/
|
|
376
|
-
|
|
377
|
-
this.#
|
|
378
|
-
|
|
379
|
-
writablePredicate(binding).predicate = predicate;
|
|
380
|
-
if (this.#promoteLoneToRecord(binding.token, binding)) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
// The binding may have vacated the default slot, and a narrowed record can collapse back to lone.
|
|
384
|
-
const record = this.#clearDefaultOccupant(binding.token, binding);
|
|
385
|
-
if (record !== undefined) {
|
|
386
|
-
this.#settle(binding.token, record);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Moves a binding still holding the lone seat into a fresh one-binding record, returning whether it did.
|
|
391
|
-
*
|
|
392
|
-
* @remarks Its field (`isMany` or `predicate`) is written before this call, so the founding
|
|
393
|
-
* `#indexSlot` files it under the slot it now holds.
|
|
394
|
-
*/
|
|
395
|
-
#promoteLoneToRecord(key, binding) {
|
|
396
|
-
if (this.#lone.get(key) !== binding) {
|
|
397
|
-
return false;
|
|
289
|
+
defaultSlotAlias(token) {
|
|
290
|
+
if ((this.#heldKinds & HELD_ALIAS) === 0) {
|
|
291
|
+
return undefined;
|
|
398
292
|
}
|
|
399
|
-
this
|
|
400
|
-
|
|
401
|
-
return true;
|
|
293
|
+
const occupant = this.getDefaultSlotBinding(token);
|
|
294
|
+
return occupant !== undefined && occupant.kind === "alias" ? occupant : undefined;
|
|
402
295
|
}
|
|
403
|
-
/**
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (record?.defaultOccupant === binding) {
|
|
407
|
-
record.defaultOccupant = undefined;
|
|
408
|
-
}
|
|
409
|
-
return record;
|
|
296
|
+
/** A token's lone default-slot binding — the first read of every synchronous resolve. */
|
|
297
|
+
getFastDefault(token) {
|
|
298
|
+
return this.#lone.get(token);
|
|
410
299
|
}
|
|
411
300
|
#createRecord(key, bindings) {
|
|
412
301
|
const record = createTokenRecord(bindings);
|
|
@@ -445,7 +334,7 @@ export class BindingRegistry {
|
|
|
445
334
|
if (displacedBinding !== undefined) {
|
|
446
335
|
record.bindings = record.bindings.filter((candidate) => candidate !== displacedBinding);
|
|
447
336
|
}
|
|
448
|
-
|
|
337
|
+
record.bindings.push(binding);
|
|
449
338
|
this.#byId?.set(binding.identifier, binding);
|
|
450
339
|
this.#indexSlot(record, binding);
|
|
451
340
|
this.#settle(key, record);
|
|
@@ -544,20 +433,6 @@ export class BindingRegistry {
|
|
|
544
433
|
* Places a binding in its record by registration order: a fresh registration appends, and a binding a
|
|
545
434
|
* chain took out and put back returns to its place.
|
|
546
435
|
*/
|
|
547
|
-
function insertInRegistrationOrder(record, binding) {
|
|
548
|
-
const { bindings } = record;
|
|
549
|
-
const order = binding.registrationOrder;
|
|
550
|
-
if (bindings.length === 0 || bindings.at(-1).registrationOrder < order) {
|
|
551
|
-
bindings.push(binding);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
let index = bindings.length - 1;
|
|
555
|
-
while (index > 0 && bindings[index - 1].registrationOrder > order) {
|
|
556
|
-
index -= 1;
|
|
557
|
-
}
|
|
558
|
-
// Replaced, never spliced: a walk holding the current array must not lose its place.
|
|
559
|
-
record.bindings = bindings.toSpliced(index, 0, binding);
|
|
560
|
-
}
|
|
561
436
|
/** A binding nothing has to be matched against: the default slot, no predicate, not a collection member. */
|
|
562
437
|
function isDefaultSlotBinding(binding) {
|
|
563
438
|
return binding.slot.tags.length === 0 && binding.predicate === undefined && !binding.isMany;
|
package/dist/core/tag.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Tag keys and the interned pairs they mint — the one way a slot criterion is built. */
|
|
2
2
|
/**
|
|
3
|
-
* The bits a tag-key mask holds: the width of a 32-bit integer
|
|
3
|
+
* The bits a tag-key mask holds: the width of a 32-bit integer.
|
|
4
4
|
*
|
|
5
5
|
* @remarks Ids past this wrap, so two keys can share a bit. The mask is a prefilter only — a shared
|
|
6
6
|
* bit costs a false positive that the identity comparison then rejects, never a false negative.
|