ts-ioc-container 72.2.0 → 72.3.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 (58) hide show
  1. package/README.md +169 -0
  2. package/cjm/container/Container.js +3 -0
  3. package/cjm/container/EmptyContainer.js +3 -0
  4. package/cjm/feature/IFeatureFlags.js +6 -0
  5. package/cjm/index.js +11 -2
  6. package/cjm/token/BindableToken.js +63 -0
  7. package/cjm/token/GroupAliasToken.js +5 -50
  8. package/cjm/token/MultiVariantFeatureToken.js +84 -0
  9. package/cjm/token/SingleAliasToken.js +5 -50
  10. package/cjm/token/SingleToken.js +5 -50
  11. package/cjm/token/ToggleFeatureToken.js +10 -0
  12. package/esm/container/Container.js +3 -0
  13. package/esm/container/EmptyContainer.js +3 -0
  14. package/esm/feature/IFeatureFlags.js +3 -0
  15. package/esm/index.js +4 -0
  16. package/esm/token/BindableToken.js +59 -0
  17. package/esm/token/GroupAliasToken.js +5 -50
  18. package/esm/token/MultiVariantFeatureToken.js +80 -0
  19. package/esm/token/SingleAliasToken.js +5 -50
  20. package/esm/token/SingleToken.js +5 -50
  21. package/esm/token/ToggleFeatureToken.js +6 -0
  22. package/lib/container/Container.ts +4 -0
  23. package/lib/container/EmptyContainer.ts +4 -0
  24. package/lib/container/IContainer.ts +3 -0
  25. package/lib/feature/IFeatureFlags.ts +44 -0
  26. package/lib/index.ts +16 -0
  27. package/lib/registration/IRegistration.ts +12 -5
  28. package/lib/token/BindableToken.ts +137 -0
  29. package/lib/token/GroupAliasToken.ts +10 -69
  30. package/lib/token/MultiVariantFeatureToken.ts +160 -0
  31. package/lib/token/SingleAliasToken.ts +11 -67
  32. package/lib/token/SingleToken.ts +9 -67
  33. package/lib/token/ToggleFeatureToken.ts +27 -0
  34. package/package.json +2 -2
  35. package/typings/container/Container.d.ts +1 -0
  36. package/typings/container/Container.d.ts.map +1 -1
  37. package/typings/container/EmptyContainer.d.ts +1 -0
  38. package/typings/container/EmptyContainer.d.ts.map +1 -1
  39. package/typings/container/IContainer.d.ts +2 -0
  40. package/typings/container/IContainer.d.ts.map +1 -1
  41. package/typings/feature/IFeatureFlags.d.ts +42 -0
  42. package/typings/feature/IFeatureFlags.d.ts.map +1 -0
  43. package/typings/index.d.ts +4 -0
  44. package/typings/index.d.ts.map +1 -1
  45. package/typings/registration/IRegistration.d.ts +12 -5
  46. package/typings/registration/IRegistration.d.ts.map +1 -1
  47. package/typings/token/BindableToken.d.ts +80 -0
  48. package/typings/token/BindableToken.d.ts.map +1 -0
  49. package/typings/token/GroupAliasToken.d.ts +9 -24
  50. package/typings/token/GroupAliasToken.d.ts.map +1 -1
  51. package/typings/token/MultiVariantFeatureToken.d.ts +79 -0
  52. package/typings/token/MultiVariantFeatureToken.d.ts.map +1 -0
  53. package/typings/token/SingleAliasToken.d.ts +10 -22
  54. package/typings/token/SingleAliasToken.d.ts.map +1 -1
  55. package/typings/token/SingleToken.d.ts +8 -22
  56. package/typings/token/SingleToken.d.ts.map +1 -1
  57. package/typings/token/ToggleFeatureToken.d.ts +25 -0
  58. package/typings/token/ToggleFeatureToken.d.ts.map +1 -0
@@ -0,0 +1,59 @@
1
+ import { forwardArgs, InjectionToken } from './InjectionToken.js';
2
+ export class BindableToken extends InjectionToken {
3
+ token;
4
+ getArgsFn;
5
+ isLazyByDefault;
6
+ memberships;
7
+ constructor(token, { getArgsFn = forwardArgs, isLazy = false, tags = [], memberships = [] } = {}) {
8
+ super(tags);
9
+ this.token = token;
10
+ this.getArgsFn = getArgsFn;
11
+ this.isLazyByDefault = isLazy;
12
+ this.memberships = memberships;
13
+ }
14
+ bindTo(r) {
15
+ this.bindKey(r);
16
+ for (const alias of this.memberships) {
17
+ r.bindToAlias(alias);
18
+ }
19
+ }
20
+ variantOf(feature, name) {
21
+ return this.copy({ memberships: [...this.memberships, feature.variantAlias(name)] });
22
+ }
23
+ primaryVariantOf(feature) {
24
+ return this.copy({ memberships: [...this.memberships, feature.primaryAlias()] });
25
+ }
26
+ fallbackOf(feature) {
27
+ return this.copy({ memberships: [...this.memberships, feature.fallbackAlias()] });
28
+ }
29
+ args(...newArgs) {
30
+ const parentFn = this.getArgsFn;
31
+ return this.copy({ getArgsFn: (options) => [...parentFn(options), ...newArgs] });
32
+ }
33
+ argsFn(fn) {
34
+ const parentFn = this.getArgsFn;
35
+ return this.copy({ getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)] });
36
+ }
37
+ lazy() {
38
+ return this.copy({ isLazy: true });
39
+ }
40
+ addTags(...tags) {
41
+ return this.copy({ tags: [...this.getTags(), ...tags] });
42
+ }
43
+ toString() {
44
+ return this.token.toString();
45
+ }
46
+ toResolveOptions(s, { args = [], lazy } = {}) {
47
+ return { args: this.getArgsFn({ scope: s, args }), lazy: this.isLazyByDefault || lazy };
48
+ }
49
+ copy(overrides) {
50
+ const Self = this.constructor;
51
+ return new Self(this.token, {
52
+ getArgsFn: this.getArgsFn,
53
+ isLazy: this.isLazyByDefault,
54
+ tags: this.getTags(),
55
+ memberships: this.memberships,
56
+ ...overrides,
57
+ });
58
+ }
59
+ }
@@ -1,58 +1,13 @@
1
- import { forwardArgs, InjectionToken } from './InjectionToken.js';
2
- export class GroupAliasToken extends InjectionToken {
3
- token;
4
- _getArgsFn;
5
- _isLazy;
6
- constructor(token, { getArgsFn = forwardArgs, isLazy = false, tags = [], } = {}) {
7
- super(tags);
8
- this.token = token;
9
- this._getArgsFn = getArgsFn;
10
- this._isLazy = isLazy;
11
- }
1
+ import { BindableToken } from './BindableToken.js';
2
+ export class GroupAliasToken extends BindableToken {
12
3
  select(fn) {
13
4
  return (s) => fn(this.resolve(s));
14
5
  }
15
- resolve(s, { args = [], lazy } = {}) {
16
- return s.resolveByAlias(this.token, {
17
- args: this._getArgsFn({ scope: s, args }),
18
- lazy: this._isLazy || lazy,
19
- });
6
+ resolve(s, options) {
7
+ return s.resolveByAlias(this.token, this.toResolveOptions(s, options));
20
8
  }
21
- bindTo(r) {
9
+ bindKey(r) {
22
10
  r.bindToAlias(this.token);
23
11
  }
24
- args(...newArgs) {
25
- const parentFn = this._getArgsFn;
26
- return new GroupAliasToken(this.token, {
27
- getArgsFn: (options) => [...parentFn(options), ...newArgs],
28
- isLazy: this._isLazy,
29
- tags: this.getTags(),
30
- });
31
- }
32
- argsFn(fn) {
33
- const parentFn = this._getArgsFn;
34
- return new GroupAliasToken(this.token, {
35
- getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
36
- isLazy: this._isLazy,
37
- tags: this.getTags(),
38
- });
39
- }
40
- lazy() {
41
- return new GroupAliasToken(this.token, {
42
- getArgsFn: this._getArgsFn,
43
- isLazy: true,
44
- tags: this.getTags(),
45
- });
46
- }
47
- addTags(...tags) {
48
- return new GroupAliasToken(this.token, {
49
- getArgsFn: this._getArgsFn,
50
- isLazy: this._isLazy,
51
- tags: [...this.getTags(), ...tags],
52
- });
53
- }
54
- toString() {
55
- return this.token.toString();
56
- }
57
12
  }
58
13
  export const toGroupAlias = (token) => new GroupAliasToken(token);
@@ -0,0 +1,80 @@
1
+ import { forwardArgs, InjectionToken } from './InjectionToken.js';
2
+ import { ProxyRegistry } from '../utils/ProxyRegistry.js';
3
+ import { DependencyNotFoundError } from '../errors/DependencyNotFoundError.js';
4
+ import { IFeatureContextToken, IFeatureFlagsToken, } from '../feature/IFeatureFlags.js';
5
+ export class MultiVariantFeatureToken extends InjectionToken {
6
+ flag;
7
+ _getArgsFn;
8
+ _isLazy;
9
+ constructor(flag, { getArgsFn = forwardArgs, isLazy = false, tags = [] } = {}) {
10
+ super(tags);
11
+ this.flag = flag;
12
+ this._getArgsFn = getArgsFn;
13
+ this._isLazy = isLazy;
14
+ }
15
+ variantAlias(name) {
16
+ return `feature:${this.flag}:variant:${name}`;
17
+ }
18
+ primaryAlias() {
19
+ return `feature:${this.flag}:primary`;
20
+ }
21
+ fallbackAlias() {
22
+ return `feature:${this.flag}:fallback`;
23
+ }
24
+ resolve(s, { args = [], lazy } = {}) {
25
+ const resolveSelected = () => s.resolveOneByAlias(this.selectAlias(s), { args: this._getArgsFn({ scope: s, args }) });
26
+ return ProxyRegistry.getInstance().toLazyIf(resolveSelected, this._isLazy || lazy);
27
+ }
28
+ args(...newArgs) {
29
+ const parentFn = this._getArgsFn;
30
+ return this.copy({ getArgsFn: (options) => [...parentFn(options), ...newArgs] });
31
+ }
32
+ argsFn(fn) {
33
+ const parentFn = this._getArgsFn;
34
+ return this.copy({ getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)] });
35
+ }
36
+ lazy() {
37
+ return this.copy({ isLazy: true });
38
+ }
39
+ addTags(...tags) {
40
+ return this.copy({ tags: [...this.getTags(), ...tags] });
41
+ }
42
+ toString() {
43
+ return this.flag;
44
+ }
45
+ evaluate(flags, context) {
46
+ const served = flags.getVariant(this.flag, context);
47
+ return served.enabled
48
+ ? { enabled: true, variant: served.name }
49
+ : { enabled: flags.isEnabled(this.flag, context) };
50
+ }
51
+ copy(overrides) {
52
+ const Self = this.constructor;
53
+ return new Self(this.flag, {
54
+ getArgsFn: this._getArgsFn,
55
+ isLazy: this._isLazy,
56
+ tags: this.getTags(),
57
+ ...overrides,
58
+ });
59
+ }
60
+ selectAlias(s) {
61
+ const fallback = this.fallbackAlias();
62
+ if (!s.hasAlias(fallback)) {
63
+ throw new DependencyNotFoundError(`Feature flag "${this.flag}" has no fallback: a flag always has one. ` +
64
+ `Register it with @register(token.fallbackOf(feature)) in this scope or a parent.`);
65
+ }
66
+ const flags = IFeatureFlagsToken.resolve(s);
67
+ const context = IFeatureContextToken.resolve(s);
68
+ let served = { enabled: false };
69
+ try {
70
+ served = this.evaluate(flags, context);
71
+ }
72
+ catch {
73
+ }
74
+ const candidates = [
75
+ served.variant === undefined ? undefined : this.variantAlias(served.variant),
76
+ served.enabled ? this.primaryAlias() : undefined,
77
+ ];
78
+ return candidates.find((alias) => alias !== undefined && s.hasAlias(alias)) ?? fallback;
79
+ }
80
+ }
@@ -1,58 +1,13 @@
1
- import { forwardArgs, InjectionToken } from './InjectionToken.js';
2
- export class SingleAliasToken extends InjectionToken {
3
- token;
4
- _getArgsFn;
5
- _isLazy;
6
- constructor(token, { getArgsFn = forwardArgs, isLazy = false, tags = [], } = {}) {
7
- super(tags);
8
- this.token = token;
9
- this._getArgsFn = getArgsFn;
10
- this._isLazy = isLazy;
11
- }
1
+ import { BindableToken } from './BindableToken.js';
2
+ export class SingleAliasToken extends BindableToken {
12
3
  select(fn) {
13
4
  return (s) => fn(this.resolve(s));
14
5
  }
15
- resolve(s, { args = [], lazy } = {}) {
16
- return s.resolveOneByAlias(this.token, {
17
- args: this._getArgsFn({ scope: s, args }),
18
- lazy: this._isLazy || lazy,
19
- });
6
+ resolve(s, options) {
7
+ return s.resolveOneByAlias(this.token, this.toResolveOptions(s, options));
20
8
  }
21
- bindTo(r) {
9
+ bindKey(r) {
22
10
  r.bindToAlias(this.token);
23
11
  }
24
- args(...newArgs) {
25
- const parentFn = this._getArgsFn;
26
- return new SingleAliasToken(this.token, {
27
- getArgsFn: (options) => [...parentFn(options), ...newArgs],
28
- isLazy: this._isLazy,
29
- tags: this.getTags(),
30
- });
31
- }
32
- argsFn(fn) {
33
- const parentFn = this._getArgsFn;
34
- return new SingleAliasToken(this.token, {
35
- getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
36
- isLazy: this._isLazy,
37
- tags: this.getTags(),
38
- });
39
- }
40
- lazy() {
41
- return new SingleAliasToken(this.token, {
42
- getArgsFn: this._getArgsFn,
43
- isLazy: true,
44
- tags: this.getTags(),
45
- });
46
- }
47
- addTags(...tags) {
48
- return new SingleAliasToken(this.token, {
49
- getArgsFn: this._getArgsFn,
50
- isLazy: this._isLazy,
51
- tags: [...this.getTags(), ...tags],
52
- });
53
- }
54
- toString() {
55
- return this.token.toString();
56
- }
57
12
  }
58
13
  export const toSingleAlias = (token) => new SingleAliasToken(token);
@@ -1,57 +1,12 @@
1
- import { forwardArgs, InjectionToken } from './InjectionToken.js';
2
- export class SingleToken extends InjectionToken {
3
- token;
4
- _getArgsFn;
5
- _isLazy;
6
- constructor(token, { getArgsFn = forwardArgs, isLazy = false, tags = [], } = {}) {
7
- super(tags);
8
- this.token = token;
9
- this._getArgsFn = getArgsFn;
10
- this._isLazy = isLazy;
11
- }
1
+ import { BindableToken } from './BindableToken.js';
2
+ export class SingleToken extends BindableToken {
12
3
  select(fn) {
13
4
  return (s) => fn(this.resolve(s));
14
5
  }
15
- resolve(s, { args = [], lazy } = {}) {
16
- return s.resolve(this.token, {
17
- args: this._getArgsFn({ scope: s, args }),
18
- lazy: this._isLazy || lazy,
19
- });
6
+ resolve(s, options) {
7
+ return s.resolve(this.token, this.toResolveOptions(s, options));
20
8
  }
21
- bindTo(r) {
9
+ bindKey(r) {
22
10
  r.bindToKey(this.token);
23
11
  }
24
- args(...newArgs) {
25
- const parentFn = this._getArgsFn;
26
- return new SingleToken(this.token, {
27
- getArgsFn: (options) => [...parentFn(options), ...newArgs],
28
- isLazy: this._isLazy,
29
- tags: this.getTags(),
30
- });
31
- }
32
- argsFn(fn) {
33
- const parentFn = this._getArgsFn;
34
- return new SingleToken(this.token, {
35
- getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
36
- isLazy: this._isLazy,
37
- tags: this.getTags(),
38
- });
39
- }
40
- lazy() {
41
- return new SingleToken(this.token, {
42
- getArgsFn: this._getArgsFn,
43
- isLazy: true,
44
- tags: this.getTags(),
45
- });
46
- }
47
- addTags(...tags) {
48
- return new SingleToken(this.token, {
49
- getArgsFn: this._getArgsFn,
50
- isLazy: this._isLazy,
51
- tags: [...this.getTags(), ...tags],
52
- });
53
- }
54
- toString() {
55
- return this.token.toString();
56
- }
57
12
  }
@@ -0,0 +1,6 @@
1
+ import { MultiVariantFeatureToken } from './MultiVariantFeatureToken.js';
2
+ export class ToggleFeatureToken extends MultiVariantFeatureToken {
3
+ evaluate(flags, context) {
4
+ return { enabled: flags.isEnabled(this.flag, context) };
5
+ }
6
+ }
@@ -235,6 +235,10 @@ export class Container implements IContainer {
235
235
  return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key);
236
236
  }
237
237
 
238
+ hasAlias(alias: DependencyKey): boolean {
239
+ return this.aliases.getKeysByAlias(alias).length > 0 || this.parent.hasAlias(alias);
240
+ }
241
+
238
242
  addInstance(instance: Instance) {
239
243
  this.instances.add(instance);
240
244
  }
@@ -103,6 +103,10 @@ export class EmptyContainer implements IContainer {
103
103
  return false;
104
104
  }
105
105
 
106
+ hasAlias(alias: DependencyKey): boolean {
107
+ return false;
108
+ }
109
+
106
110
  removeScope(): void {}
107
111
 
108
112
  /**
@@ -99,6 +99,9 @@ export interface IContainer extends Tagged {
99
99
  /** Whether a provider is registered under `key` in this scope (parents are not checked). */
100
100
  hasRegistration(key: DependencyKey): boolean;
101
101
 
102
+ /** Whether a provider carrying `alias` is registered in this scope or a parent. Never throws for a missing alias. */
103
+ hasAlias(alias: DependencyKey): boolean;
104
+
102
105
  /**
103
106
  * Resolves a key, or constructs a class with the injector. Takes a key or a
104
107
  * class, not a token: for a token call `token.resolve(scope)`.
@@ -0,0 +1,44 @@
1
+ import { SingleToken } from '../token/SingleToken';
2
+
3
+ /**
4
+ * What a flag is evaluated for - usually the current user. Shaped after the
5
+ * Unleash context, so an Unleash client accepts it as is.
6
+ */
7
+ export type FeatureContext = {
8
+ userId?: string;
9
+ sessionId?: string;
10
+ remoteAddress?: string;
11
+ environment?: string;
12
+ properties?: Record<string, string | number | undefined>;
13
+ [key: string]: unknown;
14
+ };
15
+
16
+ /** A variant served for a flag. Unleash reports `{ name: 'disabled', enabled: false }` for a flag that is off. */
17
+ export type FeatureVariant = { name: string; enabled: boolean };
18
+
19
+ /**
20
+ * The flag client feature tokens ask: `getVariant` for a
21
+ * `MultiVariantFeatureToken`, `isEnabled` for a `ToggleFeatureToken`.
22
+ * Synchronous, because resolution is. An Unleash client satisfies it
23
+ * structurally - register the instance itself; an asynchronous client
24
+ * (OpenFeature server SDK) needs an adapter which evaluates ahead of time and
25
+ * answers from that snapshot.
26
+ *
27
+ * @example
28
+ * container.addRegistration(Registration.fromValue(unleash).bindTo(IFeatureFlagsToken));
29
+ */
30
+ export interface IFeatureFlags {
31
+ isEnabled(name: string, context?: FeatureContext): boolean;
32
+
33
+ getVariant(name: string, context?: FeatureContext): FeatureVariant;
34
+ }
35
+
36
+ /** Where feature tokens find the flag client. */
37
+ export const IFeatureFlagsToken = new SingleToken<IFeatureFlags>('IFeatureFlags');
38
+
39
+ /**
40
+ * Where feature tokens find the context to evaluate a flag for. Register it
41
+ * in the scope that knows the user (e.g. `request`); register `{}` for flags
42
+ * that do not depend on one.
43
+ */
44
+ export const IFeatureContextToken = new SingleToken<FeatureContext>('IFeatureContext');
package/lib/index.ts CHANGED
@@ -126,10 +126,26 @@ export { type Injectable, toToken, argToToken } from './token/toToken';
126
126
  export { GroupAliasToken, toGroupAlias } from './token/GroupAliasToken';
127
127
  export { SingleAliasToken, toSingleAlias } from './token/SingleAliasToken';
128
128
  export { ClassToken } from './token/ClassToken';
129
+ export { BindableToken, type BindableTokenOptions } from './token/BindableToken';
129
130
  export { SingleToken } from './token/SingleToken';
130
131
  export { FunctionToken } from './token/FunctionToken';
131
132
  export { ConstantToken } from './token/ConstantToken';
132
133
  export { type InstancePredicate, GroupInstanceToken } from './token/GroupInstanceToken';
134
+ export {
135
+ MultiVariantFeatureToken,
136
+ type FeatureTokenOptions,
137
+ type FeatureEvaluation,
138
+ } from './token/MultiVariantFeatureToken';
139
+ export { ToggleFeatureToken } from './token/ToggleFeatureToken';
140
+
141
+ // Feature flags
142
+ export {
143
+ type IFeatureFlags,
144
+ type FeatureContext,
145
+ type FeatureVariant,
146
+ IFeatureFlagsToken,
147
+ IFeatureContextToken,
148
+ } from './feature/IFeatureFlags';
133
149
 
134
150
  // Metadata
135
151
  export { resolveConstructor } from './metadata/target';
@@ -106,11 +106,16 @@ export const getTransformers = (Target: constructor<unknown>) =>
106
106
 
107
107
  /**
108
108
  * Class decorator that configures how `Registration.fromClass(Target)` registers
109
- * the class: binding keys, scope rules and provider pipes. A bare key or token
110
- * is shorthand for `bindTo(...)`. Without a key the class name is used.
109
+ * the class: binding keys, scope rules and provider pipes. Without a key the
110
+ * class name is used.
111
+ *
112
+ * Pass a key or a token directly - `@register(ILoggerToken)`, `@register('ILogger')`.
113
+ * It is bound exactly as `bindTo(...)` would bind it, so wrapping it in
114
+ * `bindTo(...)` here is redundant; `bindTo` is for the fluent
115
+ * `Registration...` chain.
111
116
  *
112
117
  * @example
113
- * @register(bindTo(ILoggerToken), scope((s) => s.hasTag('application')), singleton())
118
+ * @register(ILoggerToken, scope((s) => s.hasTag('application')), singleton())
114
119
  * class Logger implements ILogger {}
115
120
  *
116
121
  * container.addRegistration(Registration.fromClass(Logger));
@@ -125,9 +130,11 @@ export const register = (...mappers: RegistrationMapper[]) =>
125
130
  * Binds a registration to one or more keys or tokens. A `SingleToken` binds its
126
131
  * key; an alias token (`toSingleAlias`, `toGroupAlias`) adds an alias.
127
132
  *
133
+ * Inside `@register(...)` pass the token itself instead - `@register(ILoggerToken)`
134
+ * binds it the same way, so `@register(bindTo(ILoggerToken))` is redundant.
135
+ *
128
136
  * @example
129
- * @register(bindTo(ILoggerToken))
130
- * class Logger {}
137
+ * Registration.fromClass(Logger).bindTo(ILoggerToken);
131
138
  *
132
139
  * @example
133
140
  * Registration.fromValue(config).bindTo('Config');
@@ -0,0 +1,137 @@
1
+ import { type DependencyKey, type IContainer } from '../container/IContainer';
2
+ import { forwardArgs, InjectionToken } from './InjectionToken';
3
+ import { type BindToken } from './BindToken';
4
+ import { type IRegistration } from '../registration/IRegistration';
5
+ import { type ArgsFn, type ResolveOptions } from '../provider/IProvider';
6
+ import { type Serializable } from '../utils/basic';
7
+ import { type MultiVariantFeatureToken } from './MultiVariantFeatureToken';
8
+
9
+ // Blocks inference from `name` so it must be one of the feature's variants (TS < 5.4 has no NoInfer).
10
+ type NoInferVariant<V> = [V][V extends unknown ? 0 : never];
11
+
12
+ /** Internal state of a {@link BindableToken}, propagated by its modifiers. Never pass it from outside. */
13
+ export type BindableTokenOptions = {
14
+ getArgsFn?: ArgsFn;
15
+ isLazy?: boolean;
16
+ tags?: string[];
17
+ memberships?: DependencyKey[];
18
+ };
19
+
20
+ /**
21
+ * Base of every token a registration can be bound to (`SingleToken`,
22
+ * `SingleAliasToken`, `GroupAliasToken`): a key, the args / lazy / tags state
23
+ * every modifier copies, and the feature memberships the token declares.
24
+ *
25
+ * `T` is the type bound to the token, `R` the type resolving it returns (an
26
+ * alias group resolves `T[]`). A subclass says only how it binds (`bindKey`)
27
+ * and how it resolves.
28
+ */
29
+ export abstract class BindableToken<T = any, R = T> extends InjectionToken<R> implements BindToken<T>, Serializable {
30
+ private readonly getArgsFn: ArgsFn;
31
+ private readonly isLazyByDefault: boolean;
32
+ private readonly memberships: DependencyKey[];
33
+
34
+ constructor(
35
+ readonly token: DependencyKey,
36
+ { getArgsFn = forwardArgs, isLazy = false, tags = [], memberships = [] }: BindableTokenOptions = {},
37
+ ) {
38
+ super(tags);
39
+ this.getArgsFn = getArgsFn;
40
+ this.isLazyByDefault = isLazy;
41
+ this.memberships = memberships;
42
+ }
43
+
44
+ /** Binds `r` under this token, and into every feature this token declared itself a variant or fallback of. */
45
+ bindTo(r: IRegistration<T>): void {
46
+ this.bindKey(r);
47
+ for (const alias of this.memberships) {
48
+ r.bindToAlias(alias);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Declares the dependency bound to this token the implementation of `feature`
54
+ * while the flag serves the variant `name`.
55
+ *
56
+ * @example
57
+ * const BlueButtonToken = new SingleToken<IButton>('BlueButton').variantOf(CheckoutButtonToken, 'blue');
58
+ *
59
+ * @register(BlueButtonToken)
60
+ * class BlueButton implements IButton {}
61
+ */
62
+ variantOf<V extends string>(feature: MultiVariantFeatureToken<T, V>, name: NoInferVariant<V>): this {
63
+ return this.copy({ memberships: [...this.memberships, feature.variantAlias(name)] });
64
+ }
65
+
66
+ /**
67
+ * Declares the dependency bound to this token the primary variant of
68
+ * `feature`: used while the flag is on and no named variant implementation
69
+ * matches. The enabled implementation of a `ToggleFeatureToken`.
70
+ *
71
+ * @example
72
+ * const StripeGatewayToken = new SingleToken<IPaymentGateway>('StripeGateway').primaryVariantOf(PaymentGatewayToken);
73
+ *
74
+ * @register(StripeGatewayToken)
75
+ * class StripeGateway implements IPaymentGateway {}
76
+ */
77
+ primaryVariantOf(feature: MultiVariantFeatureToken<T, string>): this {
78
+ return this.copy({ memberships: [...this.memberships, feature.primaryAlias()] });
79
+ }
80
+
81
+ /**
82
+ * Declares the dependency bound to this token the fallback of `feature`: used
83
+ * whenever no variant implementation applies - the flag is off, serves
84
+ * nothing implemented, or its client throws.
85
+ *
86
+ * @example
87
+ * const LegacyGatewayToken = new SingleToken<IPaymentGateway>('LegacyGateway').fallbackOf(PaymentGatewayToken);
88
+ *
89
+ * @register(LegacyGatewayToken)
90
+ * class LegacyGateway implements IPaymentGateway {}
91
+ */
92
+ fallbackOf(feature: MultiVariantFeatureToken<T, string>): this {
93
+ return this.copy({ memberships: [...this.memberships, feature.fallbackAlias()] });
94
+ }
95
+
96
+ args(...newArgs: unknown[]): this {
97
+ const parentFn = this.getArgsFn;
98
+ return this.copy({ getArgsFn: (options) => [...parentFn(options), ...newArgs] });
99
+ }
100
+
101
+ argsFn(fn: (s: IContainer) => unknown[]): this {
102
+ const parentFn = this.getArgsFn;
103
+ return this.copy({ getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)] });
104
+ }
105
+
106
+ lazy(): this {
107
+ return this.copy({ isLazy: true });
108
+ }
109
+
110
+ addTags(...tags: string[]): this {
111
+ return this.copy({ tags: [...this.getTags(), ...tags] });
112
+ }
113
+
114
+ toString(): string {
115
+ return this.token.toString();
116
+ }
117
+
118
+ /** Binds `r` under this token's own key or alias. */
119
+ protected abstract bindKey(r: IRegistration<T>): void;
120
+
121
+ /** The options a subclass resolves with: runtime args first, then this token's args; lazy if either asks. */
122
+ protected toResolveOptions(s: IContainer, { args = [], lazy }: ResolveOptions = {}): ResolveOptions {
123
+ // `lazy` stays undefined unless someone asked, so a provider's own `lazy()` pipe still applies.
124
+ return { args: this.getArgsFn({ scope: s, args }), lazy: this.isLazyByDefault || lazy };
125
+ }
126
+
127
+ private copy(overrides: BindableTokenOptions): this {
128
+ const Self = this.constructor as new (token: DependencyKey, options: BindableTokenOptions) => this;
129
+ return new Self(this.token, {
130
+ getArgsFn: this.getArgsFn,
131
+ isLazy: this.isLazyByDefault,
132
+ tags: this.getTags(),
133
+ memberships: this.memberships,
134
+ ...overrides,
135
+ });
136
+ }
137
+ }