@codefast/di 0.10.0 → 0.11.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 +293 -0
- package/README.md +71 -7
- package/dist/ambient/active-container.d.ts +13 -11
- package/dist/ambient/active-container.js +8 -6
- package/dist/container/binding-builders.d.ts +25 -7
- package/dist/container/binding-builders.js +110 -42
- package/dist/container/container.d.ts +8 -8
- package/dist/container/container.js +244 -67
- package/dist/core/binding-declaration.d.ts +120 -0
- package/dist/core/binding-declaration.js +186 -0
- package/dist/core/binding-scope.d.ts +2 -2
- package/dist/core/binding.d.ts +61 -9
- package/dist/core/binding.js +49 -2
- package/dist/core/constraint-requirement.d.ts +1 -1
- package/dist/core/module.d.ts +10 -7
- package/dist/core/module.js +17 -3
- package/dist/core/registry.d.ts +17 -13
- package/dist/core/registry.js +155 -59
- package/dist/core/state-epoch.d.ts +18 -1
- package/dist/core/state-epoch.js +17 -0
- package/dist/core/tag.js +1 -1
- package/dist/core/token.d.ts +1 -1
- package/dist/core/types.d.ts +5 -5
- package/dist/decorators/decorator-metadata.d.ts +9 -0
- package/dist/decorators/decorator-metadata.js +20 -0
- package/dist/decorators/inject.d.ts +3 -3
- package/dist/decorators/inject.js +7 -6
- package/dist/decorators/injectable.d.ts +2 -2
- package/dist/decorators/injectable.js +5 -3
- package/dist/decorators/lifecycle-decorators.js +9 -3
- package/dist/errors/errors.d.ts +85 -14
- package/dist/errors/errors.js +100 -20
- package/dist/index.d.ts +37 -35
- package/dist/index.js +20 -19
- package/dist/injection/descriptor.d.ts +3 -3
- package/dist/injection/descriptor.js +3 -7
- package/dist/injection/resolve-options.d.ts +3 -3
- package/dist/injection/resolve-options.js +7 -5
- package/dist/introspection/dependency-graph.d.ts +10 -5
- package/dist/introspection/dependency-graph.js +51 -28
- package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -1
- package/dist/introspection/graph-adapters/dot.d.ts +1 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts +1 -1
- package/dist/introspection/graph-adapters/reactflow.d.ts +1 -1
- package/dist/introspection/graph-adapters/reactflow.js +6 -4
- package/dist/introspection/inspector.d.ts +4 -4
- package/dist/introspection/inspector.js +9 -12
- package/dist/lifecycle/lifecycle-manager.d.ts +4 -4
- package/dist/lifecycle/lifecycle-manager.js +18 -6
- package/dist/lifecycle/scope-manager.d.ts +2 -2
- package/dist/lifecycle/scope-manager.js +31 -13
- package/dist/metadata/metadata-reader-token.d.ts +2 -2
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/metadata/metadata-types.d.ts +3 -3
- package/dist/metadata/symbol-metadata-reader.d.ts +3 -3
- package/dist/metadata/symbol-metadata-reader.js +1 -1
- package/dist/metadata/verifying-metadata-reader.d.ts +5 -4
- package/dist/metadata/verifying-metadata-reader.js +30 -8
- package/dist/resolution/async-fan-out.d.ts +12 -0
- package/dist/resolution/async-fan-out.js +26 -0
- package/dist/resolution/cache/activation-need.d.ts +4 -5
- package/dist/resolution/cache/activation-need.js +11 -18
- package/dist/resolution/cache/binding-lookup-cache.d.ts +5 -12
- package/dist/resolution/cache/binding-lookup-cache.js +32 -19
- package/dist/resolution/cache/class-introspector.d.ts +16 -5
- package/dist/resolution/cache/class-introspector.js +73 -56
- package/dist/resolution/context.d.ts +17 -25
- package/dist/resolution/context.js +47 -56
- package/dist/resolution/path/resolution-path.d.ts +48 -13
- package/dist/resolution/path/resolution-path.js +90 -39
- package/dist/resolution/plan/instantiation-plan.d.ts +5 -5
- package/dist/resolution/plan/instantiation-plan.js +66 -26
- package/dist/resolution/plan/plan-codegen.d.ts +10 -7
- package/dist/resolution/plan/plan-codegen.js +62 -34
- package/dist/resolution/resolver.d.ts +16 -17
- package/dist/resolution/resolver.js +316 -273
- package/dist/resolution/select/binding-select.d.ts +6 -5
- package/dist/resolution/select/binding-select.js +5 -4
- package/dist/resolution/select/constraints.d.ts +3 -3
- package/dist/resolution/select/constraints.js +4 -4
- package/package.json +14 -2
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A binding written as data: `binding()` checks a definition once and normalises it into the
|
|
3
|
+
* declaration a declared module files when it loads.
|
|
4
|
+
*/
|
|
5
|
+
import type { BindingSlot } from "#core/binding";
|
|
6
|
+
import type { BindingTag } from "#core/tag";
|
|
7
|
+
import type { Token } from "#core/token";
|
|
8
|
+
import type { ActivationHandler, BindingConstraint, BindingKind, BindingScope, Constructor, DeactivationHandler, ResolutionContext } from "#core/types";
|
|
9
|
+
import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "#injection/descriptor";
|
|
10
|
+
type Never<Keys extends PropertyKey> = {
|
|
11
|
+
readonly [Key in Keys]?: never;
|
|
12
|
+
};
|
|
13
|
+
type StrategyKey = "to" | "toSelf" | "toConstantValue" | "toDynamic" | "toDynamicAsync" | "toResolved" | "toResolvedAsync" | "toAlias" | "deps";
|
|
14
|
+
type ResolvedParameters<Deps extends ReadonlyArray<InjectableDependency>> = {
|
|
15
|
+
[Index in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[Index]>;
|
|
16
|
+
};
|
|
17
|
+
/** Where the binding sits: a name and criteria, or a collection member, which has no slot to take. */
|
|
18
|
+
type SlotDefinition<Names extends string> = ({
|
|
19
|
+
readonly whenNamed?: Names | undefined;
|
|
20
|
+
readonly whenTagged?: BindingTag | ReadonlyArray<BindingTag> | undefined;
|
|
21
|
+
} & Never<"many">) | ({
|
|
22
|
+
readonly many: true;
|
|
23
|
+
} & Never<"whenNamed" | "whenTagged">);
|
|
24
|
+
/** How long an instance lives, and the hooks that lifetime allows. */
|
|
25
|
+
type LifetimeDefinition<Value> = {
|
|
26
|
+
readonly scope: "singleton";
|
|
27
|
+
readonly onActivation?: ActivationHandler<Value> | undefined;
|
|
28
|
+
readonly onDeactivation?: DeactivationHandler<Value> | undefined;
|
|
29
|
+
} | ({
|
|
30
|
+
readonly scope?: "transient" | "scoped" | undefined;
|
|
31
|
+
readonly onActivation?: ActivationHandler<Value> | undefined;
|
|
32
|
+
} & Never<"onDeactivation">);
|
|
33
|
+
type BuiltDefinition<Value, Deps extends ReadonlyArray<InjectableDependency>> = ({
|
|
34
|
+
readonly to: Constructor<Value>;
|
|
35
|
+
} & Never<Exclude<StrategyKey, "to">>) | ({
|
|
36
|
+
readonly toDynamic: (ctx: ResolutionContext) => Value;
|
|
37
|
+
} & Never<Exclude<StrategyKey, "toDynamic">>) | ({
|
|
38
|
+
readonly toDynamicAsync: (ctx: ResolutionContext) => Promise<Value>;
|
|
39
|
+
} & Never<Exclude<StrategyKey, "toDynamicAsync">>) | ({
|
|
40
|
+
readonly toResolved: (...args: ResolvedParameters<Deps>) => Value;
|
|
41
|
+
readonly deps: Deps;
|
|
42
|
+
} & Never<Exclude<StrategyKey, "toResolved" | "deps">>) | ({
|
|
43
|
+
readonly toResolvedAsync: (...args: ResolvedParameters<Deps>) => Promise<Value>;
|
|
44
|
+
readonly deps: Deps;
|
|
45
|
+
} & Never<Exclude<StrategyKey, "toResolvedAsync" | "deps">>);
|
|
46
|
+
type ConstantDefinition<Value> = {
|
|
47
|
+
readonly toConstantValue: Value;
|
|
48
|
+
readonly onActivation?: ActivationHandler<Value> | undefined;
|
|
49
|
+
readonly onDeactivation?: DeactivationHandler<Value> | undefined;
|
|
50
|
+
} & Never<Exclude<StrategyKey, "toConstantValue"> | "scope">;
|
|
51
|
+
type AliasDefinition<Value> = {
|
|
52
|
+
readonly toAlias: Token<Value> | Constructor<Value>;
|
|
53
|
+
} & Never<Exclude<StrategyKey, "toAlias"> | "scope" | "onActivation" | "onDeactivation">;
|
|
54
|
+
type SelfDefinition<Value> = {
|
|
55
|
+
readonly toSelf: true;
|
|
56
|
+
} & Never<Exclude<StrategyKey, "toSelf">> & LifetimeDefinition<Value> & SlotDefinition<string> & {
|
|
57
|
+
readonly when?: BindingConstraint | undefined;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* One binding spelled with the fluent chain's vocabulary: a single strategy key, then the slot,
|
|
61
|
+
* `when`, `many`, `scope` and hooks that strategy allows.
|
|
62
|
+
*
|
|
63
|
+
* @typeParam Names - The slot names `whenNamed` accepts, the token's own.
|
|
64
|
+
* @typeParam Deps - The dependencies `toResolved` and `toResolvedAsync` infer their parameters from.
|
|
65
|
+
*
|
|
66
|
+
* @since 0.11.0
|
|
67
|
+
*/
|
|
68
|
+
export type BindingDefinition<Value, Names extends string = string, Deps extends ReadonlyArray<InjectableDependency> = ReadonlyArray<InjectableDependency>> = ((BuiltDefinition<Value, Deps> & LifetimeDefinition<Value>) | ConstantDefinition<Value> | AliasDefinition<Value>) & SlotDefinition<Names> & {
|
|
69
|
+
readonly when?: BindingConstraint | undefined;
|
|
70
|
+
};
|
|
71
|
+
declare const declarationBrand: unique symbol;
|
|
72
|
+
/**
|
|
73
|
+
* A checked, normalised binding that `Module.fromBindings` groups into a declared module.
|
|
74
|
+
*
|
|
75
|
+
* @remarks Opaque: only `binding()` makes one, so a declared module that was built can always load.
|
|
76
|
+
*
|
|
77
|
+
* @since 0.11.0
|
|
78
|
+
*/
|
|
79
|
+
export interface BindingDeclaration {
|
|
80
|
+
readonly [declarationBrand]: true;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The runtime form of a `BindingDeclaration`: the binding fields a container copies into each binding it registers.
|
|
84
|
+
*
|
|
85
|
+
* @remarks Built at one site in `binding()`, so every declaration shares a hidden class.
|
|
86
|
+
*
|
|
87
|
+
* @since 0.11.0
|
|
88
|
+
*/
|
|
89
|
+
export interface DeclaredBinding extends BindingDeclaration {
|
|
90
|
+
readonly token: Token<unknown> | Constructor;
|
|
91
|
+
readonly kind: BindingKind;
|
|
92
|
+
readonly scope: BindingScope;
|
|
93
|
+
readonly target: unknown;
|
|
94
|
+
readonly factory: unknown;
|
|
95
|
+
readonly value: unknown;
|
|
96
|
+
readonly deps: ReadonlyArray<InjectionDescriptor> | undefined;
|
|
97
|
+
readonly slot: BindingSlot;
|
|
98
|
+
readonly predicate: BindingConstraint | undefined;
|
|
99
|
+
readonly isMany: boolean;
|
|
100
|
+
readonly activationHook: ActivationHandler<unknown> | undefined;
|
|
101
|
+
readonly deactivationHook: DeactivationHandler<unknown> | undefined;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Narrows a list entry to the declaration it must be, or throws for anything `binding()` did not make.
|
|
105
|
+
*
|
|
106
|
+
* @since 0.11.0
|
|
107
|
+
*/
|
|
108
|
+
export declare function asDeclaredBinding(entry: BindingDeclaration, moduleName: string, index: number): DeclaredBinding;
|
|
109
|
+
/**
|
|
110
|
+
* Declares one binding for `Module.fromBindings`, checked against the fluent chain's rules where it is written.
|
|
111
|
+
*
|
|
112
|
+
* @remarks The key alone decides the value type and the slot names; the definition never widens them.
|
|
113
|
+
*
|
|
114
|
+
* @throws `InvalidBindingDeclarationError` for no strategy, several, or a key the strategy does not allow.
|
|
115
|
+
* @throws `ManyBindingSlotError` for a slot on a collection member.
|
|
116
|
+
* @throws `SelfBindingRequiresClassError` for `toSelf` on a key that is not a class.
|
|
117
|
+
*/
|
|
118
|
+
export declare function binding<Value, Names extends string, const Deps extends ReadonlyArray<InjectableDependency> = []>(key: Token<Value, Names>, definition: BindingDefinition<NoInfer<Value>, NoInfer<Names>, Deps>): BindingDeclaration;
|
|
119
|
+
export declare function binding<Value, const Deps extends ReadonlyArray<InjectableDependency> = []>(key: Constructor<Value>, definition: BindingDefinition<NoInfer<Value>, string, Deps> | SelfDefinition<NoInfer<Value>>): BindingDeclaration;
|
|
120
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { DEFAULT_BINDING_SLOT, withSlotCriterion } from "#core/binding";
|
|
2
|
+
import { slotName } from "#core/tag";
|
|
3
|
+
import { tokenName } from "#core/token";
|
|
4
|
+
import { InvalidBindingDeclarationError, ManyBindingSlotError, SelfBindingRequiresClassError } from "#errors/errors";
|
|
5
|
+
import { normalizeToDescriptor } from "#injection/descriptor";
|
|
6
|
+
// Only this module can mint the symbol, so an entry carrying it was made by `binding()`.
|
|
7
|
+
const declarationBrand = Symbol("di:binding-declaration");
|
|
8
|
+
/** Whether an entry carries the brand `binding()` alone sets, and so every field a declaration holds. */
|
|
9
|
+
function isDeclaredBinding(entry) {
|
|
10
|
+
// Plain JavaScript can hand over anything, `null` included.
|
|
11
|
+
return typeof entry === "object" && entry !== null && entry[declarationBrand] === true;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Narrows a list entry to the declaration it must be, or throws for anything `binding()` did not make.
|
|
15
|
+
*
|
|
16
|
+
* @since 0.11.0
|
|
17
|
+
*/
|
|
18
|
+
export function asDeclaredBinding(entry, moduleName, index) {
|
|
19
|
+
if (!isDeclaredBinding(entry)) {
|
|
20
|
+
throw new InvalidBindingDeclarationError(`${moduleName}[${String(index)}]`, "the entry is not a declaration made by binding()");
|
|
21
|
+
}
|
|
22
|
+
return entry;
|
|
23
|
+
}
|
|
24
|
+
const STRATEGIES = new Set([
|
|
25
|
+
"to",
|
|
26
|
+
"toSelf",
|
|
27
|
+
"toConstantValue",
|
|
28
|
+
"toDynamic",
|
|
29
|
+
"toDynamicAsync",
|
|
30
|
+
"toResolved",
|
|
31
|
+
"toResolvedAsync",
|
|
32
|
+
"toAlias",
|
|
33
|
+
]);
|
|
34
|
+
const DEFINITION_KEYS = new Set([
|
|
35
|
+
...STRATEGIES,
|
|
36
|
+
"deps",
|
|
37
|
+
"whenNamed",
|
|
38
|
+
"whenTagged",
|
|
39
|
+
"when",
|
|
40
|
+
"many",
|
|
41
|
+
"scope",
|
|
42
|
+
"onActivation",
|
|
43
|
+
"onDeactivation",
|
|
44
|
+
]);
|
|
45
|
+
const SCOPES = new Set(["singleton", "transient", "scoped"]);
|
|
46
|
+
function isStrategy(key) {
|
|
47
|
+
return STRATEGIES.has(key);
|
|
48
|
+
}
|
|
49
|
+
/** `Array.isArray` for a read-only list, which the built-in guard does not narrow. */
|
|
50
|
+
function isReadonlyList(value) {
|
|
51
|
+
return Array.isArray(value);
|
|
52
|
+
}
|
|
53
|
+
/** The strategy key a definition names, when it names exactly one. */
|
|
54
|
+
function strategyOf(name, definition) {
|
|
55
|
+
let strategy;
|
|
56
|
+
for (const key of Object.keys(definition)) {
|
|
57
|
+
if (!DEFINITION_KEYS.has(key)) {
|
|
58
|
+
throw new InvalidBindingDeclarationError(name, `\`${key}\` is not a definition key`);
|
|
59
|
+
}
|
|
60
|
+
if (isStrategy(key)) {
|
|
61
|
+
if (strategy !== undefined) {
|
|
62
|
+
throw new InvalidBindingDeclarationError(name, `it names two strategies, \`${strategy}\` and \`${key}\``);
|
|
63
|
+
}
|
|
64
|
+
strategy = key;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (strategy === undefined) {
|
|
68
|
+
throw new InvalidBindingDeclarationError(name, "it names no strategy");
|
|
69
|
+
}
|
|
70
|
+
return strategy;
|
|
71
|
+
}
|
|
72
|
+
/** The slot `whenNamed` then each `whenTagged` criterion builds, one chain step at a time. */
|
|
73
|
+
function slotOf(definition) {
|
|
74
|
+
let slot = DEFAULT_BINDING_SLOT;
|
|
75
|
+
if (definition.whenNamed !== undefined) {
|
|
76
|
+
slot = withSlotCriterion(slot, slotName.of(definition.whenNamed));
|
|
77
|
+
}
|
|
78
|
+
const tagged = definition.whenTagged;
|
|
79
|
+
if (tagged !== undefined) {
|
|
80
|
+
for (const criterion of isReadonlyList(tagged) ? tagged : [tagged]) {
|
|
81
|
+
slot = withSlotCriterion(slot, criterion);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return slot;
|
|
85
|
+
}
|
|
86
|
+
// The value type is erased here, once: the overloads above are what a caller is checked against.
|
|
87
|
+
/**
|
|
88
|
+
* @since 0.11.0
|
|
89
|
+
*/
|
|
90
|
+
export function binding(key, definition) {
|
|
91
|
+
const name = tokenName(key);
|
|
92
|
+
const strategy = strategyOf(name, definition);
|
|
93
|
+
let kind;
|
|
94
|
+
let target;
|
|
95
|
+
let factory;
|
|
96
|
+
let value;
|
|
97
|
+
let deps;
|
|
98
|
+
switch (strategy) {
|
|
99
|
+
case "to":
|
|
100
|
+
kind = "class";
|
|
101
|
+
target = definition.to;
|
|
102
|
+
break;
|
|
103
|
+
case "toSelf":
|
|
104
|
+
// Plain JavaScript can pass `toSelf: false`; the type admits `true` alone.
|
|
105
|
+
if (definition.toSelf !== true) {
|
|
106
|
+
throw new InvalidBindingDeclarationError(name, "`toSelf` takes `true`");
|
|
107
|
+
}
|
|
108
|
+
if (typeof key !== "function") {
|
|
109
|
+
throw new SelfBindingRequiresClassError(name);
|
|
110
|
+
}
|
|
111
|
+
kind = "class";
|
|
112
|
+
target = key;
|
|
113
|
+
break;
|
|
114
|
+
case "toConstantValue":
|
|
115
|
+
kind = "constant";
|
|
116
|
+
value = definition.toConstantValue;
|
|
117
|
+
break;
|
|
118
|
+
case "toDynamic":
|
|
119
|
+
kind = "dynamic";
|
|
120
|
+
factory = definition.toDynamic;
|
|
121
|
+
break;
|
|
122
|
+
case "toDynamicAsync":
|
|
123
|
+
kind = "dynamic-async";
|
|
124
|
+
factory = definition.toDynamicAsync;
|
|
125
|
+
break;
|
|
126
|
+
case "toResolved":
|
|
127
|
+
case "toResolvedAsync": {
|
|
128
|
+
const declaredDeps = definition.deps;
|
|
129
|
+
if (!isReadonlyList(declaredDeps)) {
|
|
130
|
+
throw new InvalidBindingDeclarationError(name, `\`${strategy}\` needs \`deps\`, an array`);
|
|
131
|
+
}
|
|
132
|
+
kind = strategy === "toResolved" ? "resolved" : "resolved-async";
|
|
133
|
+
factory = strategy === "toResolved" ? definition.toResolved : definition.toResolvedAsync;
|
|
134
|
+
deps = declaredDeps.map((dependency) => normalizeToDescriptor(dependency));
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case "toAlias":
|
|
138
|
+
kind = "alias";
|
|
139
|
+
target = definition.toAlias;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
if (deps === undefined && definition.deps !== undefined) {
|
|
143
|
+
throw new InvalidBindingDeclarationError(name, "`deps` belongs to `toResolved` or `toResolvedAsync`");
|
|
144
|
+
}
|
|
145
|
+
let scope = kind === "constant" ? "singleton" : "transient";
|
|
146
|
+
if (definition.scope !== undefined) {
|
|
147
|
+
if (kind === "constant" || kind === "alias") {
|
|
148
|
+
throw new InvalidBindingDeclarationError(name, `\`${strategy}\` takes no \`scope\``);
|
|
149
|
+
}
|
|
150
|
+
if (!SCOPES.has(definition.scope)) {
|
|
151
|
+
throw new InvalidBindingDeclarationError(name, "`scope` is not one of singleton, transient or scoped");
|
|
152
|
+
}
|
|
153
|
+
scope = definition.scope;
|
|
154
|
+
}
|
|
155
|
+
const { onActivation, onDeactivation } = definition;
|
|
156
|
+
if (kind === "alias" && (onActivation !== undefined || onDeactivation !== undefined)) {
|
|
157
|
+
throw new InvalidBindingDeclarationError(name, "`toAlias` takes no lifecycle hook");
|
|
158
|
+
}
|
|
159
|
+
if (onDeactivation !== undefined && kind !== "constant" && scope !== "singleton") {
|
|
160
|
+
throw new InvalidBindingDeclarationError(name, '`onDeactivation` needs `scope: "singleton"`');
|
|
161
|
+
}
|
|
162
|
+
if (definition.many !== undefined && definition.many !== true) {
|
|
163
|
+
throw new InvalidBindingDeclarationError(name, "`many` takes `true`");
|
|
164
|
+
}
|
|
165
|
+
const isMany = definition.many === true;
|
|
166
|
+
const slot = slotOf(definition);
|
|
167
|
+
if (isMany && slot.tags.length !== 0) {
|
|
168
|
+
throw new ManyBindingSlotError(name);
|
|
169
|
+
}
|
|
170
|
+
const declared = {
|
|
171
|
+
[declarationBrand]: true,
|
|
172
|
+
token: key,
|
|
173
|
+
kind,
|
|
174
|
+
scope,
|
|
175
|
+
target,
|
|
176
|
+
factory,
|
|
177
|
+
value,
|
|
178
|
+
deps,
|
|
179
|
+
slot,
|
|
180
|
+
predicate: definition.when,
|
|
181
|
+
isMany,
|
|
182
|
+
activationHook: onActivation,
|
|
183
|
+
deactivationHook: onDeactivation,
|
|
184
|
+
};
|
|
185
|
+
return declared;
|
|
186
|
+
}
|
package/dist/core/binding.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { BindingTag, TagKeyMask } from "
|
|
2
|
-
import type { Token } from "
|
|
3
|
-
import type { ActivationHandler, BindingIdentifier, BindingScope, Constructor, DeactivationHandler, ResolutionContext, ResolutionFrame, BindingConstraint } from "
|
|
4
|
-
import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "
|
|
1
|
+
import type { BindingTag, TagKeyMask } from "#core/tag";
|
|
2
|
+
import type { Token } from "#core/token";
|
|
3
|
+
import type { ActivationHandler, BindingIdentifier, BindingScope, Constructor, DeactivationHandler, ResolutionContext, ResolutionFrame, BindingConstraint } from "#core/types";
|
|
4
|
+
import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "#injection/descriptor";
|
|
5
5
|
/**
|
|
6
6
|
* The criterion set a binding registers under and a request matches against.
|
|
7
7
|
*
|
|
@@ -32,6 +32,14 @@ export declare function createBindingSlot(tags: ReadonlyArray<BindingTag>): Bind
|
|
|
32
32
|
* @since 0.3.16-canary.0
|
|
33
33
|
*/
|
|
34
34
|
export declare function bindingSlotEquals(left: BindingSlot, right: BindingSlot): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Returns the slot with one criterion added, replacing any earlier criterion of the same key.
|
|
37
|
+
*
|
|
38
|
+
* @remarks One criterion per key: re-tagging a key replaces it rather than asking for both values.
|
|
39
|
+
*
|
|
40
|
+
* @since 0.11.0
|
|
41
|
+
*/
|
|
42
|
+
export declare function withSlotCriterion(slot: BindingSlot, criterion: BindingTag): BindingSlot;
|
|
35
43
|
/**
|
|
36
44
|
* Cached singleton absent — distinguishes "not resolved yet" from a cached `undefined`.
|
|
37
45
|
*
|
|
@@ -48,6 +56,15 @@ export declare const NO_INSTANCE: unique symbol;
|
|
|
48
56
|
* @since 0.3.16-canary.0
|
|
49
57
|
*/
|
|
50
58
|
export declare const DEFAULT_BINDING_SLOT: BindingSlot;
|
|
59
|
+
/**
|
|
60
|
+
* Renders a tag value for a diagnostic, never throwing.
|
|
61
|
+
*
|
|
62
|
+
* @remarks A tag value is caller data — a bigint, a null-prototype object, a throwing `toString` —
|
|
63
|
+
* so stringifying it must not become the error that masks the real one.
|
|
64
|
+
*
|
|
65
|
+
* @since 0.11.0
|
|
66
|
+
*/
|
|
67
|
+
export declare function stringifyTagValue(value: unknown): string;
|
|
51
68
|
/**
|
|
52
69
|
* Formats a slot for diagnostics — `default`, or its `name:`/`tag:` parts.
|
|
53
70
|
*
|
|
@@ -57,12 +74,13 @@ export declare function bindingSlotToString(slot: BindingSlot): string;
|
|
|
57
74
|
interface BindingBase<Value> {
|
|
58
75
|
readonly identifier: BindingIdentifier;
|
|
59
76
|
/**
|
|
60
|
-
* True while this binding
|
|
77
|
+
* True while this binding is being resolved on the current synchronous call stack.
|
|
61
78
|
*
|
|
62
|
-
* @remarks
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
79
|
+
* @remarks Every synchronous cycle guard reads this flag and nothing else: synchronous code does not
|
|
80
|
+
* interleave, so the flag *is* exact path membership at any depth. The async lanes hold it only for a
|
|
81
|
+
* factory's synchronous prefix, or for the seeded path a synchronous call from an async level runs
|
|
82
|
+
* over. Not optional: the binding builder always initializes it, and a field that may be absent is a
|
|
83
|
+
* field that can cost the shared hidden class. Resolver-owned; callers never set it.
|
|
66
84
|
*/
|
|
67
85
|
inFlight: boolean;
|
|
68
86
|
/**
|
|
@@ -73,6 +91,28 @@ interface BindingBase<Value> {
|
|
|
73
91
|
* @remarks Resolver-owned bookkeeping — `registry.add` normalizes it, so callers never set it.
|
|
74
92
|
*/
|
|
75
93
|
frame: ResolutionFrame | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* The context a transient factory root is handed, built once per binding: the root's path is its own frame
|
|
96
|
+
* alone, so every resolve of the root reads the same one.
|
|
97
|
+
*
|
|
98
|
+
* @remarks Resolver-owned bookkeeping, cleared with the frame it is built over.
|
|
99
|
+
*/
|
|
100
|
+
rootContext: ResolutionContext | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* The activation need last computed for this binding, stamped with the versions it was computed
|
|
103
|
+
* under, or {@link NO_ACTIVATION_STAMP}.
|
|
104
|
+
*
|
|
105
|
+
* @remarks Resolver-owned bookkeeping: a field the level reads beats a per-resolver map that a
|
|
106
|
+
* container resolving each binding once would build and never read again.
|
|
107
|
+
*/
|
|
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;
|
|
76
116
|
/**
|
|
77
117
|
* Cached singleton instance, or {@link NO_INSTANCE}.
|
|
78
118
|
*
|
|
@@ -222,6 +262,18 @@ export interface MembershipField {
|
|
|
222
262
|
* @since 0.10.0
|
|
223
263
|
*/
|
|
224
264
|
export declare function writableMembership(binding: Binding): MembershipField;
|
|
265
|
+
/**
|
|
266
|
+
* The stamp of a binding whose activation need has not been computed under the current versions.
|
|
267
|
+
*
|
|
268
|
+
* @since 0.11.0
|
|
269
|
+
*/
|
|
270
|
+
export declare const NO_ACTIVATION_STAMP = -1;
|
|
271
|
+
/**
|
|
272
|
+
* The registration order of a binding no registry has added yet.
|
|
273
|
+
*
|
|
274
|
+
* @since 0.11.0
|
|
275
|
+
*/
|
|
276
|
+
export declare const UNREGISTERED_ORDER = -1;
|
|
225
277
|
/**
|
|
226
278
|
* Drops the memoized resolution frame, for a refinement that changes what the frame reports.
|
|
227
279
|
*
|
package/dist/core/binding.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NO_TAG_KEYS, slotName, tagKeyMaskOf } from "
|
|
1
|
+
import { NO_TAG_KEYS, slotName, tagKeyMaskOf } from "#core/tag";
|
|
2
2
|
/**
|
|
3
3
|
* Builds a slot from its criterion set, deriving the name view and key mask.
|
|
4
4
|
*
|
|
@@ -36,6 +36,24 @@ export function bindingSlotEquals(left, right) {
|
|
|
36
36
|
}
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns the slot with one criterion added, replacing any earlier criterion of the same key.
|
|
41
|
+
*
|
|
42
|
+
* @remarks One criterion per key: re-tagging a key replaces it rather than asking for both values.
|
|
43
|
+
*
|
|
44
|
+
* @since 0.11.0
|
|
45
|
+
*/
|
|
46
|
+
export function withSlotCriterion(slot, criterion) {
|
|
47
|
+
const tags = [...slot.tags];
|
|
48
|
+
const existingIndex = tags.findIndex((existing) => existing.key === criterion.key);
|
|
49
|
+
if (existingIndex === -1) {
|
|
50
|
+
tags.push(criterion);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
tags[existingIndex] = criterion;
|
|
54
|
+
}
|
|
55
|
+
return createBindingSlot(tags);
|
|
56
|
+
}
|
|
39
57
|
/**
|
|
40
58
|
* Cached singleton absent — distinguishes "not resolved yet" from a cached `undefined`.
|
|
41
59
|
*
|
|
@@ -52,6 +70,22 @@ export const NO_INSTANCE = Symbol("di:no-instance");
|
|
|
52
70
|
* @since 0.3.16-canary.0
|
|
53
71
|
*/
|
|
54
72
|
export const DEFAULT_BINDING_SLOT = { name: undefined, tags: Object.freeze([]), keyMask: NO_TAG_KEYS };
|
|
73
|
+
/**
|
|
74
|
+
* Renders a tag value for a diagnostic, never throwing.
|
|
75
|
+
*
|
|
76
|
+
* @remarks A tag value is caller data — a bigint, a null-prototype object, a throwing `toString` —
|
|
77
|
+
* so stringifying it must not become the error that masks the real one.
|
|
78
|
+
*
|
|
79
|
+
* @since 0.11.0
|
|
80
|
+
*/
|
|
81
|
+
export function stringifyTagValue(value) {
|
|
82
|
+
try {
|
|
83
|
+
return String(value);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return "<unprintable>";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
55
89
|
/**
|
|
56
90
|
* Formats a slot for diagnostics — `default`, or its `name:`/`tag:` parts.
|
|
57
91
|
*
|
|
@@ -70,7 +104,7 @@ export function bindingSlotToString(slot) {
|
|
|
70
104
|
if (criterion.key === slotName) {
|
|
71
105
|
continue;
|
|
72
106
|
}
|
|
73
|
-
parts.push(`tag:${criterion.key.name}=${
|
|
107
|
+
parts.push(`tag:${criterion.key.name}=${stringifyTagValue(criterion.value)}`);
|
|
74
108
|
}
|
|
75
109
|
return parts.join(",");
|
|
76
110
|
}
|
|
@@ -101,6 +135,18 @@ export function writablePredicate(binding) {
|
|
|
101
135
|
export function writableMembership(binding) {
|
|
102
136
|
return binding;
|
|
103
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* The stamp of a binding whose activation need has not been computed under the current versions.
|
|
140
|
+
*
|
|
141
|
+
* @since 0.11.0
|
|
142
|
+
*/
|
|
143
|
+
export const NO_ACTIVATION_STAMP = -1;
|
|
144
|
+
/**
|
|
145
|
+
* The registration order of a binding no registry has added yet.
|
|
146
|
+
*
|
|
147
|
+
* @since 0.11.0
|
|
148
|
+
*/
|
|
149
|
+
export const UNREGISTERED_ORDER = -1;
|
|
104
150
|
/**
|
|
105
151
|
* Drops the memoized resolution frame, for a refinement that changes what the frame reports.
|
|
106
152
|
*
|
|
@@ -111,4 +157,5 @@ export function writableMembership(binding) {
|
|
|
111
157
|
*/
|
|
112
158
|
export function clearBindingFrame(binding) {
|
|
113
159
|
binding.frame = undefined;
|
|
160
|
+
binding.rootContext = undefined;
|
|
114
161
|
}
|
package/dist/core/module.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { BindToBuilder } from "
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
1
|
+
import type { BindToBuilder } from "#core/binding";
|
|
2
|
+
import type { BindingDeclaration, DeclaredBinding } from "#core/binding-declaration";
|
|
3
|
+
import type { Token } from "#core/token";
|
|
4
|
+
import type { Constructor } from "#core/types";
|
|
4
5
|
declare const SYNC_MODULE_BRAND: unique symbol;
|
|
5
6
|
declare const ASYNC_MODULE_BRAND: unique symbol;
|
|
6
7
|
/**
|
|
7
|
-
* Key for
|
|
8
|
-
*
|
|
8
|
+
* Key for what a module applies when it loads: its setup callback, or a declared module's
|
|
9
|
+
* declarations. A symbol (not exported from the package root) keeps it out of autocomplete.
|
|
9
10
|
*
|
|
10
11
|
* @since 0.5.0-canary.7
|
|
11
12
|
*/
|
|
@@ -18,7 +19,7 @@ export declare const MODULE_SETUP: unique symbol;
|
|
|
18
19
|
export interface SyncModule {
|
|
19
20
|
readonly name: string;
|
|
20
21
|
readonly [SYNC_MODULE_BRAND]: true;
|
|
21
|
-
readonly [MODULE_SETUP]: (builder: ModuleBuilder) => void
|
|
22
|
+
readonly [MODULE_SETUP]: ((builder: ModuleBuilder) => void) | ReadonlyArray<DeclaredBinding>;
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* A named group of bindings whose setup is async, applied via `loadAsync()`.
|
|
@@ -55,6 +56,7 @@ export interface AsyncModuleBuilder {
|
|
|
55
56
|
*/
|
|
56
57
|
export declare const SyncModule: {
|
|
57
58
|
create(name: string, setup: (builder: ModuleBuilder) => void): SyncModule;
|
|
59
|
+
fromBindings(name: string, declarations: ReadonlyArray<BindingDeclaration>): SyncModule;
|
|
58
60
|
};
|
|
59
61
|
/**
|
|
60
62
|
* The companion factory that creates `AsyncModule` values.
|
|
@@ -65,12 +67,13 @@ export declare const AsyncModule: {
|
|
|
65
67
|
create(name: string, setup: (builder: AsyncModuleBuilder) => Promise<void>): AsyncModule;
|
|
66
68
|
};
|
|
67
69
|
/**
|
|
68
|
-
* The unified module factory — `create` for sync modules, `createAsync` for async ones.
|
|
70
|
+
* The unified module factory — `create` and `fromBindings` for sync modules, `createAsync` for async ones.
|
|
69
71
|
*
|
|
70
72
|
* @since 0.3.16-canary.0
|
|
71
73
|
*/
|
|
72
74
|
export declare const Module: {
|
|
73
75
|
create(name: string, setup: (builder: ModuleBuilder) => void): SyncModule;
|
|
76
|
+
fromBindings(name: string, declarations: ReadonlyArray<BindingDeclaration>): SyncModule;
|
|
74
77
|
createAsync(name: string, setup: (builder: AsyncModuleBuilder) => Promise<void>): AsyncModule;
|
|
75
78
|
};
|
|
76
79
|
/**
|
package/dist/core/module.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { asDeclaredBinding } from "#core/binding-declaration";
|
|
1
2
|
// ── Branded types (runtime symbols for branding) ─────────────────────────────────────────────────────────────────────
|
|
2
3
|
const SYNC_MODULE_BRAND = Symbol("di:sync-module");
|
|
3
4
|
const ASYNC_MODULE_BRAND = Symbol("di:async-module");
|
|
4
5
|
/**
|
|
5
|
-
* Key for
|
|
6
|
-
*
|
|
6
|
+
* Key for what a module applies when it loads: its setup callback, or a declared module's
|
|
7
|
+
* declarations. A symbol (not exported from the package root) keeps it out of autocomplete.
|
|
7
8
|
*
|
|
8
9
|
* @since 0.5.0-canary.7
|
|
9
10
|
*/
|
|
@@ -22,6 +23,16 @@ export const SyncModule = {
|
|
|
22
23
|
[MODULE_SETUP]: setup,
|
|
23
24
|
};
|
|
24
25
|
},
|
|
26
|
+
fromBindings(name, declarations) {
|
|
27
|
+
// Copied, so a later write to the caller's list cannot change what the module loads. Not frozen:
|
|
28
|
+
// every load walks this list, and V8 reads a frozen array's elements on a slower path.
|
|
29
|
+
const declared = declarations.map((declaration, index) => asDeclaredBinding(declaration, name, index));
|
|
30
|
+
return {
|
|
31
|
+
name,
|
|
32
|
+
[SYNC_MODULE_BRAND]: true,
|
|
33
|
+
[MODULE_SETUP]: declared,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
25
36
|
};
|
|
26
37
|
/**
|
|
27
38
|
* The companion factory that creates `AsyncModule` values.
|
|
@@ -39,7 +50,7 @@ export const AsyncModule = {
|
|
|
39
50
|
};
|
|
40
51
|
// ── Module — unified API ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
41
52
|
/**
|
|
42
|
-
* The unified module factory — `create` for sync modules, `createAsync` for async ones.
|
|
53
|
+
* The unified module factory — `create` and `fromBindings` for sync modules, `createAsync` for async ones.
|
|
43
54
|
*
|
|
44
55
|
* @since 0.3.16-canary.0
|
|
45
56
|
*/
|
|
@@ -47,6 +58,9 @@ export const Module = {
|
|
|
47
58
|
create(name, setup) {
|
|
48
59
|
return SyncModule.create(name, setup);
|
|
49
60
|
},
|
|
61
|
+
fromBindings(name, declarations) {
|
|
62
|
+
return SyncModule.fromBindings(name, declarations);
|
|
63
|
+
},
|
|
50
64
|
createAsync(name, setup) {
|
|
51
65
|
return AsyncModule.create(name, setup);
|
|
52
66
|
},
|