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.
- package/README.md +169 -0
- package/cjm/container/Container.js +3 -0
- package/cjm/container/EmptyContainer.js +3 -0
- package/cjm/feature/IFeatureFlags.js +6 -0
- package/cjm/index.js +11 -2
- package/cjm/token/BindableToken.js +63 -0
- package/cjm/token/GroupAliasToken.js +5 -50
- package/cjm/token/MultiVariantFeatureToken.js +84 -0
- package/cjm/token/SingleAliasToken.js +5 -50
- package/cjm/token/SingleToken.js +5 -50
- package/cjm/token/ToggleFeatureToken.js +10 -0
- package/esm/container/Container.js +3 -0
- package/esm/container/EmptyContainer.js +3 -0
- package/esm/feature/IFeatureFlags.js +3 -0
- package/esm/index.js +4 -0
- package/esm/token/BindableToken.js +59 -0
- package/esm/token/GroupAliasToken.js +5 -50
- package/esm/token/MultiVariantFeatureToken.js +80 -0
- package/esm/token/SingleAliasToken.js +5 -50
- package/esm/token/SingleToken.js +5 -50
- package/esm/token/ToggleFeatureToken.js +6 -0
- package/lib/container/Container.ts +4 -0
- package/lib/container/EmptyContainer.ts +4 -0
- package/lib/container/IContainer.ts +3 -0
- package/lib/feature/IFeatureFlags.ts +44 -0
- package/lib/index.ts +16 -0
- package/lib/registration/IRegistration.ts +12 -5
- package/lib/token/BindableToken.ts +137 -0
- package/lib/token/GroupAliasToken.ts +10 -69
- package/lib/token/MultiVariantFeatureToken.ts +160 -0
- package/lib/token/SingleAliasToken.ts +11 -67
- package/lib/token/SingleToken.ts +9 -67
- package/lib/token/ToggleFeatureToken.ts +27 -0
- package/package.json +2 -2
- package/typings/container/Container.d.ts +1 -0
- package/typings/container/Container.d.ts.map +1 -1
- package/typings/container/EmptyContainer.d.ts +1 -0
- package/typings/container/EmptyContainer.d.ts.map +1 -1
- package/typings/container/IContainer.d.ts +2 -0
- package/typings/container/IContainer.d.ts.map +1 -1
- package/typings/feature/IFeatureFlags.d.ts +42 -0
- package/typings/feature/IFeatureFlags.d.ts.map +1 -0
- package/typings/index.d.ts +4 -0
- package/typings/index.d.ts.map +1 -1
- package/typings/registration/IRegistration.d.ts +12 -5
- package/typings/registration/IRegistration.d.ts.map +1 -1
- package/typings/token/BindableToken.d.ts +80 -0
- package/typings/token/BindableToken.d.ts.map +1 -0
- package/typings/token/GroupAliasToken.d.ts +9 -24
- package/typings/token/GroupAliasToken.d.ts.map +1 -1
- package/typings/token/MultiVariantFeatureToken.d.ts +79 -0
- package/typings/token/MultiVariantFeatureToken.d.ts.map +1 -0
- package/typings/token/SingleAliasToken.d.ts +10 -22
- package/typings/token/SingleAliasToken.d.ts.map +1 -1
- package/typings/token/SingleToken.d.ts +8 -22
- package/typings/token/SingleToken.d.ts.map +1 -1
- package/typings/token/ToggleFeatureToken.d.ts +25 -0
- 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 {
|
|
2
|
-
export class GroupAliasToken extends
|
|
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,
|
|
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
|
-
|
|
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 {
|
|
2
|
-
export class SingleAliasToken extends
|
|
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,
|
|
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
|
-
|
|
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);
|
package/esm/token/SingleToken.js
CHANGED
|
@@ -1,57 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export class SingleToken extends
|
|
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,
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
}
|
|
@@ -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.
|
|
110
|
-
*
|
|
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(
|
|
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
|
-
*
|
|
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
|
+
}
|