@codefast/di 0.10.1 → 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.
Files changed (88) hide show
  1. package/CHANGELOG.md +376 -0
  2. package/README.md +94 -17
  3. package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +7 -2
  4. package/dist/{ambient/active-container.js → ambient-container.js} +6 -1
  5. package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +27 -16
  6. package/dist/container/binding-chain.js +254 -0
  7. package/dist/container/container.d.ts +11 -2
  8. package/dist/container/container.js +293 -73
  9. package/dist/core/binding-builders.d.ts +114 -0
  10. package/dist/core/binding-declaration.d.ts +127 -0
  11. package/dist/core/binding-declaration.js +187 -0
  12. package/dist/core/binding.d.ts +62 -138
  13. package/dist/core/binding.js +54 -17
  14. package/dist/core/module.d.ts +8 -5
  15. package/dist/core/module.js +17 -3
  16. package/dist/core/registry.d.ts +17 -28
  17. package/dist/core/registry.js +92 -133
  18. package/dist/core/state-epoch.d.ts +18 -1
  19. package/dist/core/state-epoch.js +17 -0
  20. package/dist/core/tag.js +1 -1
  21. package/dist/decorators/inject.js +6 -5
  22. package/dist/decorators/injectable.js +4 -2
  23. package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +9 -3
  24. package/dist/decorators/metadata-record.d.ts +9 -0
  25. package/dist/decorators/metadata-record.js +20 -0
  26. package/dist/{errors/errors.d.ts → errors.d.ts} +84 -9
  27. package/dist/{errors/errors.js → errors.js} +101 -17
  28. package/dist/index.d.ts +14 -10
  29. package/dist/index.js +7 -6
  30. package/dist/injection/{resolve-options.js → dependency-slot.js} +6 -4
  31. package/dist/injection/descriptor.d.ts +1 -1
  32. package/dist/injection/descriptor.js +3 -7
  33. package/dist/introspection/dependency-graph.d.ts +8 -3
  34. package/dist/introspection/dependency-graph.js +48 -26
  35. package/dist/introspection/explanation.d.ts +101 -0
  36. package/dist/introspection/explanation.js +151 -0
  37. package/dist/introspection/graph-adapters/reactflow.js +6 -4
  38. package/dist/introspection/inspector.d.ts +9 -2
  39. package/dist/introspection/inspector.js +30 -28
  40. package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +9 -2
  41. package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +59 -30
  42. package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +3 -5
  43. package/dist/lifecycle/{scope-manager.js → scopes.js} +36 -29
  44. package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
  45. package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
  46. package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
  47. package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
  48. package/dist/metadata/types.js +1 -0
  49. package/dist/metadata/verify.d.ts +43 -0
  50. package/dist/metadata/verify.js +133 -0
  51. package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +5 -4
  52. package/dist/metadata/verifying-reader.js +53 -0
  53. package/dist/resolution/async-fan-out.d.ts +12 -0
  54. package/dist/resolution/async-fan-out.js +26 -0
  55. package/dist/resolution/cache/activation-need.d.ts +1 -2
  56. package/dist/resolution/cache/activation-need.js +16 -19
  57. package/dist/resolution/cache/class-introspector.d.ts +38 -36
  58. package/dist/resolution/cache/class-introspector.js +79 -159
  59. package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -9
  60. package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +70 -25
  61. package/dist/resolution/context.d.ts +16 -24
  62. package/dist/resolution/context.js +47 -56
  63. package/dist/resolution/path.d.ts +128 -0
  64. package/dist/resolution/path.js +167 -0
  65. package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +5 -4
  66. package/dist/resolution/plan/{plan-codegen.js → codegen.js} +58 -32
  67. package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
  68. package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +64 -24
  69. package/dist/resolution/resolver.d.ts +11 -12
  70. package/dist/resolution/resolver.js +573 -356
  71. package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
  72. package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
  73. package/dist/resolution/select/constraints.js +1 -1
  74. package/package.json +87 -67
  75. package/dist/container/binding-builders.js +0 -305
  76. package/dist/core/binding-scope.d.ts +0 -12
  77. package/dist/core/binding-scope.js +0 -12
  78. package/dist/metadata/verifying-metadata-reader.js +0 -31
  79. package/dist/resolution/path/resolution-path.d.ts +0 -93
  80. package/dist/resolution/path/resolution-path.js +0 -116
  81. /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
  82. /package/dist/decorators/{lifecycle-decorators.d.ts → lifecycle.d.ts} +0 -0
  83. /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
  84. /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
  85. /package/dist/{errors → introspection}/diagnostics.js +0 -0
  86. /package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +0 -0
  87. /package/dist/metadata/{metadata-keys.js → keys.js} +0 -0
  88. /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The fluent chain's contract: the slot steps `bind()` offers before `to*()`, and the builder each `to*()` returns.
3
+ */
4
+ import type { BindingTag } from "#core/tag";
5
+ import type { Token } from "#core/token";
6
+ import type { ActivationHandler, BindingConstraint, BindingIdentifier, Constructor, DeactivationHandler, ResolutionContext } from "#core/types";
7
+ import type { InjectableDependency, ResolvedDependencyValue } from "#injection/descriptor";
8
+ /**
9
+ * The slot steps of the fluent chain, taken before `to*()` so the binding registers once, in its final shape.
10
+ *
11
+ * @since 0.3.16-canary.0
12
+ */
13
+ export interface SlotConstrainedBuilder<Names extends string = string> {
14
+ /** Narrows the binding to requests the predicate accepts, evaluated on every resolve. */
15
+ when(predicate: BindingConstraint): this;
16
+ /** Declares the binding's slot name, one of the names the token declares. */
17
+ whenNamed(name: Names): this;
18
+ /** Declares one criterion of the binding's slot, replacing any earlier criterion of the same key. */
19
+ whenTagged(criterion: BindingTag): this;
20
+ /** Keeps the binding on the default slot, the one an unconstrained request selects. */
21
+ whenDefault(): this;
22
+ /**
23
+ * Makes the binding a collection member: one of several the token's `resolveAll` returns, never
24
+ * what a single `resolve` selects, and outside slot last-wins. It keeps the default slot.
25
+ */
26
+ many(): this;
27
+ }
28
+ /**
29
+ * The first step of the fluent chain: the slot, then the `to*` strategy that registers the binding.
30
+ *
31
+ * @since 0.3.16-canary.0
32
+ */
33
+ export interface BindToBuilder<Value, Names extends string = string> extends SlotConstrainedBuilder<Names> {
34
+ to(type: Constructor<Value>): BindingBuilder<Value>;
35
+ toSelf(): BindingBuilder<Value>;
36
+ toConstantValue(value: Value): ConstantBindingBuilder<Value>;
37
+ toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value>;
38
+ toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value>;
39
+ toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
40
+ [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
41
+ }) => Value, deps: Deps): BindingBuilder<Value>;
42
+ toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
43
+ [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
44
+ }) => Promise<Value>, deps: Deps): BindingBuilder<Value>;
45
+ toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder;
46
+ }
47
+ /**
48
+ * The scope-selection step of the fluent chain, which the binding enters once registered.
49
+ *
50
+ * @since 0.3.16-canary.0
51
+ */
52
+ export interface BindingBuilder<Value> {
53
+ singleton(): SingletonBindingBuilder<Value>;
54
+ transient(): TransientBindingBuilder<Value>;
55
+ scoped(): ScopedBindingBuilder<Value>;
56
+ /** The identifier this binding is registered under. */
57
+ id(): BindingIdentifier;
58
+ }
59
+ /**
60
+ * The fluent chain for a constant — lifecycle hooks only, since the scope is fixed.
61
+ *
62
+ * @since 0.3.16-canary.0
63
+ */
64
+ export interface ConstantBindingBuilder<Value> {
65
+ onActivation(fn: ActivationHandler<Value>): SingletonLifecycleBuilder<Value>;
66
+ onDeactivation(fn: DeactivationHandler<Value>): SingletonLifecycleBuilder<Value>;
67
+ /** The identifier this binding is registered under. */
68
+ id(): BindingIdentifier;
69
+ }
70
+ /**
71
+ * The fluent chain for an alias — nothing to add, since scoping belongs to the target.
72
+ *
73
+ * @since 0.3.16-canary.0
74
+ */
75
+ export interface AliasBindingBuilder {
76
+ /** The identifier this binding is registered under. */
77
+ id(): BindingIdentifier;
78
+ }
79
+ /**
80
+ * The fluent chain after `singleton()`, where both lifecycle hooks stay available.
81
+ *
82
+ * @since 0.3.16-canary.0
83
+ */
84
+ export interface SingletonBindingBuilder<Value> {
85
+ onActivation(fn: ActivationHandler<Value>): this;
86
+ onDeactivation(fn: DeactivationHandler<Value>): this;
87
+ id(): BindingIdentifier;
88
+ }
89
+ /**
90
+ * The fluent chain after `transient()`, where activation is the one lifecycle hook offered.
91
+ *
92
+ * @since 0.3.16-canary.0
93
+ */
94
+ export interface TransientBindingBuilder<Value> {
95
+ onActivation(fn: ActivationHandler<Value>): this;
96
+ id(): BindingIdentifier;
97
+ }
98
+ /**
99
+ * The fluent chain after `scoped()`, sharing the `transient()` surface.
100
+ *
101
+ * @since 0.3.16-canary.0
102
+ */
103
+ export interface ScopedBindingBuilder<Value> extends TransientBindingBuilder<Value> {
104
+ }
105
+ /**
106
+ * The fluent chain a constant enters once a lifecycle hook is added.
107
+ *
108
+ * @since 0.3.16-canary.0
109
+ */
110
+ export interface SingletonLifecycleBuilder<Value> {
111
+ onActivation(fn: ActivationHandler<Value>): this;
112
+ onDeactivation(fn: DeactivationHandler<Value>): this;
113
+ id(): BindingIdentifier;
114
+ }
@@ -0,0 +1,127 @@
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
+ * @since 0.11.0
119
+ */
120
+ 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;
121
+ /**
122
+ * Declares one binding keyed by a class, the one kind of key `toSelf` accepts.
123
+ *
124
+ * @since 0.11.0
125
+ */
126
+ export declare function binding<Value, const Deps extends ReadonlyArray<InjectableDependency> = []>(key: Constructor<Value>, definition: BindingDefinition<NoInfer<Value>, string, Deps> | SelfDefinition<NoInfer<Value>>): BindingDeclaration;
127
+ export {};
@@ -0,0 +1,187 @@
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";
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
+ /**
87
+ * Implements both overloads with the value type erased once, since a caller is checked against the overloads above.
88
+ *
89
+ * @since 0.11.0
90
+ */
91
+ export function binding(key, definition) {
92
+ const name = tokenName(key);
93
+ const strategy = strategyOf(name, definition);
94
+ let kind;
95
+ let target;
96
+ let factory;
97
+ let value;
98
+ let deps;
99
+ switch (strategy) {
100
+ case "to":
101
+ kind = "class";
102
+ target = definition.to;
103
+ break;
104
+ case "toSelf":
105
+ // Plain JavaScript can pass `toSelf: false`; the type admits `true` alone.
106
+ if (definition.toSelf !== true) {
107
+ throw new InvalidBindingDeclarationError(name, "`toSelf` takes `true`");
108
+ }
109
+ if (typeof key !== "function") {
110
+ throw new SelfBindingRequiresClassError(name);
111
+ }
112
+ kind = "class";
113
+ target = key;
114
+ break;
115
+ case "toConstantValue":
116
+ kind = "constant";
117
+ value = definition.toConstantValue;
118
+ break;
119
+ case "toDynamic":
120
+ kind = "dynamic";
121
+ factory = definition.toDynamic;
122
+ break;
123
+ case "toDynamicAsync":
124
+ kind = "dynamic-async";
125
+ factory = definition.toDynamicAsync;
126
+ break;
127
+ case "toResolved":
128
+ case "toResolvedAsync": {
129
+ const declaredDeps = definition.deps;
130
+ if (!isReadonlyList(declaredDeps)) {
131
+ throw new InvalidBindingDeclarationError(name, `\`${strategy}\` needs \`deps\`, an array`);
132
+ }
133
+ kind = strategy === "toResolved" ? "resolved" : "resolved-async";
134
+ factory = strategy === "toResolved" ? definition.toResolved : definition.toResolvedAsync;
135
+ deps = declaredDeps.map((dependency) => normalizeToDescriptor(dependency));
136
+ break;
137
+ }
138
+ case "toAlias":
139
+ kind = "alias";
140
+ target = definition.toAlias;
141
+ break;
142
+ }
143
+ if (deps === undefined && definition.deps !== undefined) {
144
+ throw new InvalidBindingDeclarationError(name, "`deps` belongs to `toResolved` or `toResolvedAsync`");
145
+ }
146
+ let scope = kind === "constant" ? "singleton" : "transient";
147
+ if (definition.scope !== undefined) {
148
+ if (kind === "constant" || kind === "alias") {
149
+ throw new InvalidBindingDeclarationError(name, `\`${strategy}\` takes no \`scope\``);
150
+ }
151
+ if (!SCOPES.has(definition.scope)) {
152
+ throw new InvalidBindingDeclarationError(name, "`scope` is not one of singleton, transient or scoped");
153
+ }
154
+ scope = definition.scope;
155
+ }
156
+ const { onActivation, onDeactivation } = definition;
157
+ if (kind === "alias" && (onActivation !== undefined || onDeactivation !== undefined)) {
158
+ throw new InvalidBindingDeclarationError(name, "`toAlias` takes no lifecycle hook");
159
+ }
160
+ if (onDeactivation !== undefined && kind !== "constant" && scope !== "singleton") {
161
+ throw new InvalidBindingDeclarationError(name, '`onDeactivation` needs `scope: "singleton"`');
162
+ }
163
+ if (definition.many !== undefined && definition.many !== true) {
164
+ throw new InvalidBindingDeclarationError(name, "`many` takes `true`");
165
+ }
166
+ const isMany = definition.many === true;
167
+ const slot = slotOf(definition);
168
+ if (isMany && slot.tags.length !== 0) {
169
+ throw new ManyBindingSlotError(name);
170
+ }
171
+ const declared = {
172
+ [declarationBrand]: true,
173
+ token: key,
174
+ kind,
175
+ scope,
176
+ target,
177
+ factory,
178
+ value,
179
+ deps,
180
+ slot,
181
+ predicate: definition.when,
182
+ isMany,
183
+ activationHook: onActivation,
184
+ deactivationHook: onDeactivation,
185
+ };
186
+ return declared;
187
+ }