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
package/README.md
CHANGED
|
@@ -53,6 +53,7 @@ provider pipelines, aliases, and custom injector strategies.
|
|
|
53
53
|
- [Token](#token) `bindTo`
|
|
54
54
|
- [Scope](#scope) `scope`
|
|
55
55
|
- [Composing decorators](#composing-decorators) `createComposeClassDecorator`
|
|
56
|
+
- [Feature flags](#feature-flags) `MultiVariantFeatureToken` `ToggleFeatureToken`
|
|
56
57
|
- [Module](#module)
|
|
57
58
|
- [Hook](#hook) `@hook`
|
|
58
59
|
- [Hook domains](#hook-domains) `ScopeHook` `InjectorHook` `ProviderHook`
|
|
@@ -2887,6 +2888,174 @@ describe('composing decorators', () => {
|
|
|
2887
2888
|
|
|
2888
2889
|
```
|
|
2889
2890
|
|
|
2891
|
+
### Feature flags
|
|
2892
|
+
|
|
2893
|
+
Feature tokens let a feature flag choose which implementation is injected, so
|
|
2894
|
+
consumers depend on one type and never see the flag. The implementations
|
|
2895
|
+
decide where they belong - each one's own token declares its role:
|
|
2896
|
+
|
|
2897
|
+
- `token.variantOf(Feature, 'blue')` - used while the flag serves the variant `blue` (`getVariant`)
|
|
2898
|
+
- `token.primaryVariantOf(Feature)` - used while the flag is on and no named variant implementation matches
|
|
2899
|
+
- `token.fallbackOf(Feature)` - used otherwise: the flag is off, serves nothing implemented, or its client throws
|
|
2900
|
+
|
|
2901
|
+
**Every flag has a fallback**: resolving a feature without one registered is a
|
|
2902
|
+
`DependencyNotFoundError`, whatever the flag serves. `MultiVariantFeatureToken<T, V>`
|
|
2903
|
+
is the general case; `ToggleFeatureToken<T>` is its on/off case - no named
|
|
2904
|
+
variants, only a primary one (`isEnabled`). Each implementation stays
|
|
2905
|
+
injectable through its own token.
|
|
2906
|
+
|
|
2907
|
+
The flag client comes from `IFeatureFlagsToken` and the context it evaluates
|
|
2908
|
+
for from `IFeatureContextToken`, both resolved from the resolving scope on every
|
|
2909
|
+
resolution - register the context in your `request` scope. A missing client or
|
|
2910
|
+
context is a `DependencyNotFoundError`, never a silent fallback.
|
|
2911
|
+
|
|
2912
|
+
`IFeatureFlags` is shaped after Unleash (`isEnabled(name, context)`,
|
|
2913
|
+
`getVariant(name, context)`), so an Unleash client is registered as is:
|
|
2914
|
+
`R.fromValue(unleash).bindTo(IFeatureFlagsToken)`. Resolution is synchronous;
|
|
2915
|
+
wrap an asynchronous client (e.g. the OpenFeature server SDK) in an adapter
|
|
2916
|
+
that evaluates ahead of time. `Feature.lazy()` defers the evaluation to first use.
|
|
2917
|
+
|
|
2918
|
+
```typescript
|
|
2919
|
+
import 'reflect-metadata';
|
|
2920
|
+
import {
|
|
2921
|
+
by,
|
|
2922
|
+
Container,
|
|
2923
|
+
type FeatureContext,
|
|
2924
|
+
type FeatureVariant,
|
|
2925
|
+
type IFeatureFlags,
|
|
2926
|
+
IFeatureContextToken,
|
|
2927
|
+
IFeatureFlagsToken,
|
|
2928
|
+
inject,
|
|
2929
|
+
MultiVariantFeatureToken,
|
|
2930
|
+
register,
|
|
2931
|
+
Registration as R,
|
|
2932
|
+
SingleToken,
|
|
2933
|
+
ToggleFeatureToken,
|
|
2934
|
+
} from 'ts-ioc-container';
|
|
2935
|
+
|
|
2936
|
+
/**
|
|
2937
|
+
* Checkout Domain - Feature Flags
|
|
2938
|
+
*
|
|
2939
|
+
* A flag chooses which implementation is injected; the consumer depends on one
|
|
2940
|
+
* type and never sees the flag. The implementations decide where they belong:
|
|
2941
|
+
* each one's own token declares it
|
|
2942
|
+
*
|
|
2943
|
+
* - `.variantOf(feature, name)` - used while the flag serves the variant `name`;
|
|
2944
|
+
* - `.primaryVariantOf(feature)` - used while the flag is on and no named
|
|
2945
|
+
* variant implementation matches (the "on" side of a toggle);
|
|
2946
|
+
* - `.fallbackOf(feature)` - used otherwise. Every flag has one.
|
|
2947
|
+
*
|
|
2948
|
+
* `ToggleFeatureToken` is the on/off case of `MultiVariantFeatureToken`: no
|
|
2949
|
+
* named variants, only a primary one.
|
|
2950
|
+
*
|
|
2951
|
+
* The flag client is any object with `isEnabled(name, context)` and
|
|
2952
|
+
* `getVariant(name, context)` - an Unleash client works as is:
|
|
2953
|
+
* container.addRegistration(R.fromValue(unleash).bindTo(IFeatureFlagsToken));
|
|
2954
|
+
*/
|
|
2955
|
+
|
|
2956
|
+
// Toggle: new checkout on / off
|
|
2957
|
+
interface IPaymentGateway {
|
|
2958
|
+
pay(amount: number): string;
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
const PaymentGatewayToken = new ToggleFeatureToken<IPaymentGateway>('new-checkout');
|
|
2962
|
+
|
|
2963
|
+
// Each implementation's own token declares where it belongs
|
|
2964
|
+
@register(new SingleToken<IPaymentGateway>('LegacyGateway').fallbackOf(PaymentGatewayToken))
|
|
2965
|
+
class LegacyGateway implements IPaymentGateway {
|
|
2966
|
+
pay(amount: number) {
|
|
2967
|
+
return `legacy charged ${amount}`;
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
@register(new SingleToken<IPaymentGateway>('StripeGateway').primaryVariantOf(PaymentGatewayToken))
|
|
2972
|
+
class StripeGateway implements IPaymentGateway {
|
|
2973
|
+
pay(amount: number) {
|
|
2974
|
+
return `stripe charged ${amount}`;
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// Multi-variant: A/B/n experiment on the checkout button
|
|
2979
|
+
interface ICheckoutButton {
|
|
2980
|
+
render(): string;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
const CheckoutButtonToken = new MultiVariantFeatureToken<ICheckoutButton, 'blue' | 'green'>('checkout-button');
|
|
2984
|
+
|
|
2985
|
+
@register(new SingleToken<ICheckoutButton>('GreyButton').fallbackOf(CheckoutButtonToken))
|
|
2986
|
+
class GreyButton implements ICheckoutButton {
|
|
2987
|
+
render() {
|
|
2988
|
+
return 'grey button';
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
@register(new SingleToken<ICheckoutButton>('BlueButton').variantOf(CheckoutButtonToken, 'blue'))
|
|
2993
|
+
class BlueButton implements ICheckoutButton {
|
|
2994
|
+
render() {
|
|
2995
|
+
return 'blue button';
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
@register(new SingleToken<ICheckoutButton>('GreenButton').variantOf(CheckoutButtonToken, 'green'))
|
|
3000
|
+
class GreenButton implements ICheckoutButton {
|
|
3001
|
+
render() {
|
|
3002
|
+
return 'green button';
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
class CheckoutPage {
|
|
3007
|
+
constructor(
|
|
3008
|
+
@inject(by(PaymentGatewayToken)) private readonly gateway: IPaymentGateway,
|
|
3009
|
+
@inject(by(CheckoutButtonToken)) private readonly button: ICheckoutButton,
|
|
3010
|
+
) {}
|
|
3011
|
+
|
|
3012
|
+
render(amount: number) {
|
|
3013
|
+
return `${this.button.render()}, ${this.gateway.pay(amount)}`;
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
// Stands in for an Unleash client
|
|
3018
|
+
class InMemoryFlags implements IFeatureFlags {
|
|
3019
|
+
isEnabled(name: string, context?: FeatureContext) {
|
|
3020
|
+
return name === 'new-checkout' && context?.userId === 'beta-tester';
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
getVariant(name: string, context?: FeatureContext): FeatureVariant {
|
|
3024
|
+
const bucket = { 'beta-tester': 'green', 'user-1': 'blue' }[context?.userId ?? ''];
|
|
3025
|
+
return name === 'checkout-button' && bucket
|
|
3026
|
+
? { name: bucket, enabled: true }
|
|
3027
|
+
: { name: 'disabled', enabled: false };
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
describe('Feature flags', () => {
|
|
3032
|
+
const app = new Container({ tags: ['application'] })
|
|
3033
|
+
.addRegistration(R.fromValue(new InMemoryFlags()).bindTo(IFeatureFlagsToken))
|
|
3034
|
+
.addRegistration(R.fromClass(LegacyGateway))
|
|
3035
|
+
.addRegistration(R.fromClass(GreyButton))
|
|
3036
|
+
.addRegistration(R.fromClass(StripeGateway))
|
|
3037
|
+
.addRegistration(R.fromClass(BlueButton))
|
|
3038
|
+
.addRegistration(R.fromClass(GreenButton));
|
|
3039
|
+
|
|
3040
|
+
// The context is per request: each request evaluates the flags for its own user
|
|
3041
|
+
const handleRequest = (userId: string) => {
|
|
3042
|
+
const request = app.createScope({ tags: ['request'] });
|
|
3043
|
+
request.addRegistration(R.fromValue({ userId }).bindTo(IFeatureContextToken));
|
|
3044
|
+
return request.resolve(CheckoutPage).render(42);
|
|
3045
|
+
};
|
|
3046
|
+
|
|
3047
|
+
it('serves each user the implementations their flags select', () => {
|
|
3048
|
+
expect(handleRequest('beta-tester')).toBe('green button, stripe charged 42');
|
|
3049
|
+
expect(handleRequest('user-1')).toBe('blue button, legacy charged 42');
|
|
3050
|
+
});
|
|
3051
|
+
|
|
3052
|
+
it('serves the fallbacks to everyone else', () => {
|
|
3053
|
+
expect(handleRequest('regular-user')).toBe('grey button, legacy charged 42');
|
|
3054
|
+
});
|
|
3055
|
+
});
|
|
3056
|
+
|
|
3057
|
+
```
|
|
3058
|
+
|
|
2890
3059
|
## Module
|
|
2891
3060
|
|
|
2892
3061
|
Sometimes you want to encapsulate registration logic in separate module. This is what `IContainerModule` is for.
|
|
@@ -129,6 +129,9 @@ class Container {
|
|
|
129
129
|
hasRegistration(key) {
|
|
130
130
|
return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key);
|
|
131
131
|
}
|
|
132
|
+
hasAlias(alias) {
|
|
133
|
+
return this.aliases.getKeysByAlias(alias).length > 0 || this.parent.hasAlias(alias);
|
|
134
|
+
}
|
|
132
135
|
addInstance(instance) {
|
|
133
136
|
this.instances.add(instance);
|
|
134
137
|
}
|
|
@@ -47,6 +47,9 @@ class EmptyContainer {
|
|
|
47
47
|
hasRegistration(key) {
|
|
48
48
|
return false;
|
|
49
49
|
}
|
|
50
|
+
hasAlias(alias) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
50
53
|
removeScope() { }
|
|
51
54
|
useModule(module) {
|
|
52
55
|
throw new MethodNotImplementedError_1.MethodNotImplementedError('EmptyContainer.useModule is not implemented');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IFeatureContextToken = exports.IFeatureFlagsToken = void 0;
|
|
4
|
+
const SingleToken_1 = require("../token/SingleToken");
|
|
5
|
+
exports.IFeatureFlagsToken = new SingleToken_1.SingleToken('IFeatureFlags');
|
|
6
|
+
exports.IFeatureContextToken = new SingleToken_1.SingleToken('IFeatureContext');
|
package/cjm/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createHookContextFactory = exports.HookContext = exports.toHookFn = exports.hasHooks = exports.hook = exports.getHooks = exports.ArgumentNotFoundError = exports.TypedEventDisposedError = exports.UnsupportedTokenTypeError = exports.CannonSingletonApplyTwiceError = exports.CannotApplySingletonTwiceError = exports.ProviderDisposedError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.ContainerNotFoundError = exports.DependencyNotFoundError = exports.ContainerError = exports.Registration = exports.onResolve = exports.appendArgsFn = exports.appendArgs = exports.decorate = exports.singleton = exports.autoResolve = exports.lazy = exports.scopeAccess = exports.scope = exports.bindTo = exports.register = exports.toRegistrationFn = exports.toProviderFn = exports.toBindToken = exports.registerPipe = exports.isProviderPipe = exports.Provider = exports.ProxyInjector = exports.SimpleInjector = exports.resolveArgs = exports.by = exports.argsFn = exports.args = exports.arg = exports.inject = exports.MetadataInjector = exports.Injector = exports.EmptyContainer = exports.AutoResolveModule = exports.Container = exports.isDependencyKey = void 0;
|
|
4
|
-
exports.
|
|
5
|
-
exports.isSerializable = exports.Is = exports.unwrapProxy = exports.ProxyRegistry = exports.findOrFail = exports.pipe = exports.select = exports.TypedEvent = exports.getConstructorChain = exports.memoize = exports.once = void 0;
|
|
4
|
+
exports.handleAsyncError = exports.handleError = exports.createComposeMethodDecorator = exports.getMethodTags = exports.addMethodTag = exports.getMethodLabels = exports.addMethodLabel = exports.getMethodMeta = exports.addMethodMeta = exports.createComposeParameterDecorator = exports.getParamTags = exports.addParamTag = exports.getParamLabels = exports.addParamLabel = exports.getParamMeta = exports.addParamMeta = exports.createComposeClassDecorator = exports.getClassTags = exports.addClassTag = exports.getClassLabels = exports.addClassLabel = exports.getClassMeta = exports.addClassMeta = exports.resolveConstructor = exports.IFeatureContextToken = exports.IFeatureFlagsToken = exports.ToggleFeatureToken = exports.MultiVariantFeatureToken = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.BindableToken = exports.ClassToken = exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.argToToken = exports.toToken = exports.forwardArgs = exports.isInjectionToken = exports.InjectionToken = exports.toTask = exports.HookCollector = exports.oncePerInstance = exports.parallel = exports.sequential = exports.injectProp = exports.createHookExecutionContext = void 0;
|
|
5
|
+
exports.isSerializable = exports.Is = exports.unwrapProxy = exports.ProxyRegistry = exports.findOrFail = exports.pipe = exports.select = exports.TypedEvent = exports.getConstructorChain = exports.memoize = exports.once = exports.shallowCache = exports.debounce = exports.throttle = exports.runAtOnce = exports.runInOrder = void 0;
|
|
6
6
|
var IContainer_1 = require("./container/IContainer");
|
|
7
7
|
Object.defineProperty(exports, "isDependencyKey", { enumerable: true, get: function () { return IContainer_1.isDependencyKey; } });
|
|
8
8
|
var Container_1 = require("./container/Container");
|
|
@@ -103,6 +103,8 @@ Object.defineProperty(exports, "SingleAliasToken", { enumerable: true, get: func
|
|
|
103
103
|
Object.defineProperty(exports, "toSingleAlias", { enumerable: true, get: function () { return SingleAliasToken_1.toSingleAlias; } });
|
|
104
104
|
var ClassToken_1 = require("./token/ClassToken");
|
|
105
105
|
Object.defineProperty(exports, "ClassToken", { enumerable: true, get: function () { return ClassToken_1.ClassToken; } });
|
|
106
|
+
var BindableToken_1 = require("./token/BindableToken");
|
|
107
|
+
Object.defineProperty(exports, "BindableToken", { enumerable: true, get: function () { return BindableToken_1.BindableToken; } });
|
|
106
108
|
var SingleToken_1 = require("./token/SingleToken");
|
|
107
109
|
Object.defineProperty(exports, "SingleToken", { enumerable: true, get: function () { return SingleToken_1.SingleToken; } });
|
|
108
110
|
var FunctionToken_1 = require("./token/FunctionToken");
|
|
@@ -111,6 +113,13 @@ var ConstantToken_1 = require("./token/ConstantToken");
|
|
|
111
113
|
Object.defineProperty(exports, "ConstantToken", { enumerable: true, get: function () { return ConstantToken_1.ConstantToken; } });
|
|
112
114
|
var GroupInstanceToken_1 = require("./token/GroupInstanceToken");
|
|
113
115
|
Object.defineProperty(exports, "GroupInstanceToken", { enumerable: true, get: function () { return GroupInstanceToken_1.GroupInstanceToken; } });
|
|
116
|
+
var MultiVariantFeatureToken_1 = require("./token/MultiVariantFeatureToken");
|
|
117
|
+
Object.defineProperty(exports, "MultiVariantFeatureToken", { enumerable: true, get: function () { return MultiVariantFeatureToken_1.MultiVariantFeatureToken; } });
|
|
118
|
+
var ToggleFeatureToken_1 = require("./token/ToggleFeatureToken");
|
|
119
|
+
Object.defineProperty(exports, "ToggleFeatureToken", { enumerable: true, get: function () { return ToggleFeatureToken_1.ToggleFeatureToken; } });
|
|
120
|
+
var IFeatureFlags_1 = require("./feature/IFeatureFlags");
|
|
121
|
+
Object.defineProperty(exports, "IFeatureFlagsToken", { enumerable: true, get: function () { return IFeatureFlags_1.IFeatureFlagsToken; } });
|
|
122
|
+
Object.defineProperty(exports, "IFeatureContextToken", { enumerable: true, get: function () { return IFeatureFlags_1.IFeatureContextToken; } });
|
|
114
123
|
var target_1 = require("./metadata/target");
|
|
115
124
|
Object.defineProperty(exports, "resolveConstructor", { enumerable: true, get: function () { return target_1.resolveConstructor; } });
|
|
116
125
|
var class_1 = require("./metadata/class");
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BindableToken = void 0;
|
|
4
|
+
const InjectionToken_1 = require("./InjectionToken");
|
|
5
|
+
class BindableToken extends InjectionToken_1.InjectionToken {
|
|
6
|
+
token;
|
|
7
|
+
getArgsFn;
|
|
8
|
+
isLazyByDefault;
|
|
9
|
+
memberships;
|
|
10
|
+
constructor(token, { getArgsFn = InjectionToken_1.forwardArgs, isLazy = false, tags = [], memberships = [] } = {}) {
|
|
11
|
+
super(tags);
|
|
12
|
+
this.token = token;
|
|
13
|
+
this.getArgsFn = getArgsFn;
|
|
14
|
+
this.isLazyByDefault = isLazy;
|
|
15
|
+
this.memberships = memberships;
|
|
16
|
+
}
|
|
17
|
+
bindTo(r) {
|
|
18
|
+
this.bindKey(r);
|
|
19
|
+
for (const alias of this.memberships) {
|
|
20
|
+
r.bindToAlias(alias);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
variantOf(feature, name) {
|
|
24
|
+
return this.copy({ memberships: [...this.memberships, feature.variantAlias(name)] });
|
|
25
|
+
}
|
|
26
|
+
primaryVariantOf(feature) {
|
|
27
|
+
return this.copy({ memberships: [...this.memberships, feature.primaryAlias()] });
|
|
28
|
+
}
|
|
29
|
+
fallbackOf(feature) {
|
|
30
|
+
return this.copy({ memberships: [...this.memberships, feature.fallbackAlias()] });
|
|
31
|
+
}
|
|
32
|
+
args(...newArgs) {
|
|
33
|
+
const parentFn = this.getArgsFn;
|
|
34
|
+
return this.copy({ getArgsFn: (options) => [...parentFn(options), ...newArgs] });
|
|
35
|
+
}
|
|
36
|
+
argsFn(fn) {
|
|
37
|
+
const parentFn = this.getArgsFn;
|
|
38
|
+
return this.copy({ getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)] });
|
|
39
|
+
}
|
|
40
|
+
lazy() {
|
|
41
|
+
return this.copy({ isLazy: true });
|
|
42
|
+
}
|
|
43
|
+
addTags(...tags) {
|
|
44
|
+
return this.copy({ tags: [...this.getTags(), ...tags] });
|
|
45
|
+
}
|
|
46
|
+
toString() {
|
|
47
|
+
return this.token.toString();
|
|
48
|
+
}
|
|
49
|
+
toResolveOptions(s, { args = [], lazy } = {}) {
|
|
50
|
+
return { args: this.getArgsFn({ scope: s, args }), lazy: this.isLazyByDefault || lazy };
|
|
51
|
+
}
|
|
52
|
+
copy(overrides) {
|
|
53
|
+
const Self = this.constructor;
|
|
54
|
+
return new Self(this.token, {
|
|
55
|
+
getArgsFn: this.getArgsFn,
|
|
56
|
+
isLazy: this.isLazyByDefault,
|
|
57
|
+
tags: this.getTags(),
|
|
58
|
+
memberships: this.memberships,
|
|
59
|
+
...overrides,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.BindableToken = BindableToken;
|
|
@@ -1,62 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toGroupAlias = exports.GroupAliasToken = void 0;
|
|
4
|
-
const
|
|
5
|
-
class GroupAliasToken extends
|
|
6
|
-
token;
|
|
7
|
-
_getArgsFn;
|
|
8
|
-
_isLazy;
|
|
9
|
-
constructor(token, { getArgsFn = InjectionToken_1.forwardArgs, isLazy = false, tags = [], } = {}) {
|
|
10
|
-
super(tags);
|
|
11
|
-
this.token = token;
|
|
12
|
-
this._getArgsFn = getArgsFn;
|
|
13
|
-
this._isLazy = isLazy;
|
|
14
|
-
}
|
|
4
|
+
const BindableToken_1 = require("./BindableToken");
|
|
5
|
+
class GroupAliasToken extends BindableToken_1.BindableToken {
|
|
15
6
|
select(fn) {
|
|
16
7
|
return (s) => fn(this.resolve(s));
|
|
17
8
|
}
|
|
18
|
-
resolve(s,
|
|
19
|
-
return s.resolveByAlias(this.token,
|
|
20
|
-
args: this._getArgsFn({ scope: s, args }),
|
|
21
|
-
lazy: this._isLazy || lazy,
|
|
22
|
-
});
|
|
9
|
+
resolve(s, options) {
|
|
10
|
+
return s.resolveByAlias(this.token, this.toResolveOptions(s, options));
|
|
23
11
|
}
|
|
24
|
-
|
|
12
|
+
bindKey(r) {
|
|
25
13
|
r.bindToAlias(this.token);
|
|
26
14
|
}
|
|
27
|
-
args(...newArgs) {
|
|
28
|
-
const parentFn = this._getArgsFn;
|
|
29
|
-
return new GroupAliasToken(this.token, {
|
|
30
|
-
getArgsFn: (options) => [...parentFn(options), ...newArgs],
|
|
31
|
-
isLazy: this._isLazy,
|
|
32
|
-
tags: this.getTags(),
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
argsFn(fn) {
|
|
36
|
-
const parentFn = this._getArgsFn;
|
|
37
|
-
return new GroupAliasToken(this.token, {
|
|
38
|
-
getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
|
|
39
|
-
isLazy: this._isLazy,
|
|
40
|
-
tags: this.getTags(),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
lazy() {
|
|
44
|
-
return new GroupAliasToken(this.token, {
|
|
45
|
-
getArgsFn: this._getArgsFn,
|
|
46
|
-
isLazy: true,
|
|
47
|
-
tags: this.getTags(),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
addTags(...tags) {
|
|
51
|
-
return new GroupAliasToken(this.token, {
|
|
52
|
-
getArgsFn: this._getArgsFn,
|
|
53
|
-
isLazy: this._isLazy,
|
|
54
|
-
tags: [...this.getTags(), ...tags],
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
toString() {
|
|
58
|
-
return this.token.toString();
|
|
59
|
-
}
|
|
60
15
|
}
|
|
61
16
|
exports.GroupAliasToken = GroupAliasToken;
|
|
62
17
|
const toGroupAlias = (token) => new GroupAliasToken(token);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MultiVariantFeatureToken = void 0;
|
|
4
|
+
const InjectionToken_1 = require("./InjectionToken");
|
|
5
|
+
const ProxyRegistry_1 = require("../utils/ProxyRegistry");
|
|
6
|
+
const DependencyNotFoundError_1 = require("../errors/DependencyNotFoundError");
|
|
7
|
+
const IFeatureFlags_1 = require("../feature/IFeatureFlags");
|
|
8
|
+
class MultiVariantFeatureToken extends InjectionToken_1.InjectionToken {
|
|
9
|
+
flag;
|
|
10
|
+
_getArgsFn;
|
|
11
|
+
_isLazy;
|
|
12
|
+
constructor(flag, { getArgsFn = InjectionToken_1.forwardArgs, isLazy = false, tags = [] } = {}) {
|
|
13
|
+
super(tags);
|
|
14
|
+
this.flag = flag;
|
|
15
|
+
this._getArgsFn = getArgsFn;
|
|
16
|
+
this._isLazy = isLazy;
|
|
17
|
+
}
|
|
18
|
+
variantAlias(name) {
|
|
19
|
+
return `feature:${this.flag}:variant:${name}`;
|
|
20
|
+
}
|
|
21
|
+
primaryAlias() {
|
|
22
|
+
return `feature:${this.flag}:primary`;
|
|
23
|
+
}
|
|
24
|
+
fallbackAlias() {
|
|
25
|
+
return `feature:${this.flag}:fallback`;
|
|
26
|
+
}
|
|
27
|
+
resolve(s, { args = [], lazy } = {}) {
|
|
28
|
+
const resolveSelected = () => s.resolveOneByAlias(this.selectAlias(s), { args: this._getArgsFn({ scope: s, args }) });
|
|
29
|
+
return ProxyRegistry_1.ProxyRegistry.getInstance().toLazyIf(resolveSelected, this._isLazy || lazy);
|
|
30
|
+
}
|
|
31
|
+
args(...newArgs) {
|
|
32
|
+
const parentFn = this._getArgsFn;
|
|
33
|
+
return this.copy({ getArgsFn: (options) => [...parentFn(options), ...newArgs] });
|
|
34
|
+
}
|
|
35
|
+
argsFn(fn) {
|
|
36
|
+
const parentFn = this._getArgsFn;
|
|
37
|
+
return this.copy({ getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)] });
|
|
38
|
+
}
|
|
39
|
+
lazy() {
|
|
40
|
+
return this.copy({ isLazy: true });
|
|
41
|
+
}
|
|
42
|
+
addTags(...tags) {
|
|
43
|
+
return this.copy({ tags: [...this.getTags(), ...tags] });
|
|
44
|
+
}
|
|
45
|
+
toString() {
|
|
46
|
+
return this.flag;
|
|
47
|
+
}
|
|
48
|
+
evaluate(flags, context) {
|
|
49
|
+
const served = flags.getVariant(this.flag, context);
|
|
50
|
+
return served.enabled
|
|
51
|
+
? { enabled: true, variant: served.name }
|
|
52
|
+
: { enabled: flags.isEnabled(this.flag, context) };
|
|
53
|
+
}
|
|
54
|
+
copy(overrides) {
|
|
55
|
+
const Self = this.constructor;
|
|
56
|
+
return new Self(this.flag, {
|
|
57
|
+
getArgsFn: this._getArgsFn,
|
|
58
|
+
isLazy: this._isLazy,
|
|
59
|
+
tags: this.getTags(),
|
|
60
|
+
...overrides,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
selectAlias(s) {
|
|
64
|
+
const fallback = this.fallbackAlias();
|
|
65
|
+
if (!s.hasAlias(fallback)) {
|
|
66
|
+
throw new DependencyNotFoundError_1.DependencyNotFoundError(`Feature flag "${this.flag}" has no fallback: a flag always has one. ` +
|
|
67
|
+
`Register it with @register(token.fallbackOf(feature)) in this scope or a parent.`);
|
|
68
|
+
}
|
|
69
|
+
const flags = IFeatureFlags_1.IFeatureFlagsToken.resolve(s);
|
|
70
|
+
const context = IFeatureFlags_1.IFeatureContextToken.resolve(s);
|
|
71
|
+
let served = { enabled: false };
|
|
72
|
+
try {
|
|
73
|
+
served = this.evaluate(flags, context);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
}
|
|
77
|
+
const candidates = [
|
|
78
|
+
served.variant === undefined ? undefined : this.variantAlias(served.variant),
|
|
79
|
+
served.enabled ? this.primaryAlias() : undefined,
|
|
80
|
+
];
|
|
81
|
+
return candidates.find((alias) => alias !== undefined && s.hasAlias(alias)) ?? fallback;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.MultiVariantFeatureToken = MultiVariantFeatureToken;
|
|
@@ -1,62 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toSingleAlias = exports.SingleAliasToken = void 0;
|
|
4
|
-
const
|
|
5
|
-
class SingleAliasToken extends
|
|
6
|
-
token;
|
|
7
|
-
_getArgsFn;
|
|
8
|
-
_isLazy;
|
|
9
|
-
constructor(token, { getArgsFn = InjectionToken_1.forwardArgs, isLazy = false, tags = [], } = {}) {
|
|
10
|
-
super(tags);
|
|
11
|
-
this.token = token;
|
|
12
|
-
this._getArgsFn = getArgsFn;
|
|
13
|
-
this._isLazy = isLazy;
|
|
14
|
-
}
|
|
4
|
+
const BindableToken_1 = require("./BindableToken");
|
|
5
|
+
class SingleAliasToken extends BindableToken_1.BindableToken {
|
|
15
6
|
select(fn) {
|
|
16
7
|
return (s) => fn(this.resolve(s));
|
|
17
8
|
}
|
|
18
|
-
resolve(s,
|
|
19
|
-
return s.resolveOneByAlias(this.token,
|
|
20
|
-
args: this._getArgsFn({ scope: s, args }),
|
|
21
|
-
lazy: this._isLazy || lazy,
|
|
22
|
-
});
|
|
9
|
+
resolve(s, options) {
|
|
10
|
+
return s.resolveOneByAlias(this.token, this.toResolveOptions(s, options));
|
|
23
11
|
}
|
|
24
|
-
|
|
12
|
+
bindKey(r) {
|
|
25
13
|
r.bindToAlias(this.token);
|
|
26
14
|
}
|
|
27
|
-
args(...newArgs) {
|
|
28
|
-
const parentFn = this._getArgsFn;
|
|
29
|
-
return new SingleAliasToken(this.token, {
|
|
30
|
-
getArgsFn: (options) => [...parentFn(options), ...newArgs],
|
|
31
|
-
isLazy: this._isLazy,
|
|
32
|
-
tags: this.getTags(),
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
argsFn(fn) {
|
|
36
|
-
const parentFn = this._getArgsFn;
|
|
37
|
-
return new SingleAliasToken(this.token, {
|
|
38
|
-
getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
|
|
39
|
-
isLazy: this._isLazy,
|
|
40
|
-
tags: this.getTags(),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
lazy() {
|
|
44
|
-
return new SingleAliasToken(this.token, {
|
|
45
|
-
getArgsFn: this._getArgsFn,
|
|
46
|
-
isLazy: true,
|
|
47
|
-
tags: this.getTags(),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
addTags(...tags) {
|
|
51
|
-
return new SingleAliasToken(this.token, {
|
|
52
|
-
getArgsFn: this._getArgsFn,
|
|
53
|
-
isLazy: this._isLazy,
|
|
54
|
-
tags: [...this.getTags(), ...tags],
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
toString() {
|
|
58
|
-
return this.token.toString();
|
|
59
|
-
}
|
|
60
15
|
}
|
|
61
16
|
exports.SingleAliasToken = SingleAliasToken;
|
|
62
17
|
const toSingleAlias = (token) => new SingleAliasToken(token);
|
package/cjm/token/SingleToken.js
CHANGED
|
@@ -1,61 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SingleToken = void 0;
|
|
4
|
-
const
|
|
5
|
-
class SingleToken extends
|
|
6
|
-
token;
|
|
7
|
-
_getArgsFn;
|
|
8
|
-
_isLazy;
|
|
9
|
-
constructor(token, { getArgsFn = InjectionToken_1.forwardArgs, isLazy = false, tags = [], } = {}) {
|
|
10
|
-
super(tags);
|
|
11
|
-
this.token = token;
|
|
12
|
-
this._getArgsFn = getArgsFn;
|
|
13
|
-
this._isLazy = isLazy;
|
|
14
|
-
}
|
|
4
|
+
const BindableToken_1 = require("./BindableToken");
|
|
5
|
+
class SingleToken extends BindableToken_1.BindableToken {
|
|
15
6
|
select(fn) {
|
|
16
7
|
return (s) => fn(this.resolve(s));
|
|
17
8
|
}
|
|
18
|
-
resolve(s,
|
|
19
|
-
return s.resolve(this.token,
|
|
20
|
-
args: this._getArgsFn({ scope: s, args }),
|
|
21
|
-
lazy: this._isLazy || lazy,
|
|
22
|
-
});
|
|
9
|
+
resolve(s, options) {
|
|
10
|
+
return s.resolve(this.token, this.toResolveOptions(s, options));
|
|
23
11
|
}
|
|
24
|
-
|
|
12
|
+
bindKey(r) {
|
|
25
13
|
r.bindToKey(this.token);
|
|
26
14
|
}
|
|
27
|
-
args(...newArgs) {
|
|
28
|
-
const parentFn = this._getArgsFn;
|
|
29
|
-
return new SingleToken(this.token, {
|
|
30
|
-
getArgsFn: (options) => [...parentFn(options), ...newArgs],
|
|
31
|
-
isLazy: this._isLazy,
|
|
32
|
-
tags: this.getTags(),
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
argsFn(fn) {
|
|
36
|
-
const parentFn = this._getArgsFn;
|
|
37
|
-
return new SingleToken(this.token, {
|
|
38
|
-
getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
|
|
39
|
-
isLazy: this._isLazy,
|
|
40
|
-
tags: this.getTags(),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
lazy() {
|
|
44
|
-
return new SingleToken(this.token, {
|
|
45
|
-
getArgsFn: this._getArgsFn,
|
|
46
|
-
isLazy: true,
|
|
47
|
-
tags: this.getTags(),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
addTags(...tags) {
|
|
51
|
-
return new SingleToken(this.token, {
|
|
52
|
-
getArgsFn: this._getArgsFn,
|
|
53
|
-
isLazy: this._isLazy,
|
|
54
|
-
tags: [...this.getTags(), ...tags],
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
toString() {
|
|
58
|
-
return this.token.toString();
|
|
59
|
-
}
|
|
60
15
|
}
|
|
61
16
|
exports.SingleToken = SingleToken;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ToggleFeatureToken = void 0;
|
|
4
|
+
const MultiVariantFeatureToken_1 = require("./MultiVariantFeatureToken");
|
|
5
|
+
class ToggleFeatureToken extends MultiVariantFeatureToken_1.MultiVariantFeatureToken {
|
|
6
|
+
evaluate(flags, context) {
|
|
7
|
+
return { enabled: flags.isEnabled(this.flag, context) };
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.ToggleFeatureToken = ToggleFeatureToken;
|
|
@@ -126,6 +126,9 @@ export class Container {
|
|
|
126
126
|
hasRegistration(key) {
|
|
127
127
|
return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key);
|
|
128
128
|
}
|
|
129
|
+
hasAlias(alias) {
|
|
130
|
+
return this.aliases.getKeysByAlias(alias).length > 0 || this.parent.hasAlias(alias);
|
|
131
|
+
}
|
|
129
132
|
addInstance(instance) {
|
|
130
133
|
this.instances.add(instance);
|
|
131
134
|
}
|
package/esm/index.js
CHANGED
|
@@ -31,10 +31,14 @@ export { toToken, argToToken } from './token/toToken.js';
|
|
|
31
31
|
export { GroupAliasToken, toGroupAlias } from './token/GroupAliasToken.js';
|
|
32
32
|
export { SingleAliasToken, toSingleAlias } from './token/SingleAliasToken.js';
|
|
33
33
|
export { ClassToken } from './token/ClassToken.js';
|
|
34
|
+
export { BindableToken } from './token/BindableToken.js';
|
|
34
35
|
export { SingleToken } from './token/SingleToken.js';
|
|
35
36
|
export { FunctionToken } from './token/FunctionToken.js';
|
|
36
37
|
export { ConstantToken } from './token/ConstantToken.js';
|
|
37
38
|
export { GroupInstanceToken } from './token/GroupInstanceToken.js';
|
|
39
|
+
export { MultiVariantFeatureToken, } from './token/MultiVariantFeatureToken.js';
|
|
40
|
+
export { ToggleFeatureToken } from './token/ToggleFeatureToken.js';
|
|
41
|
+
export { IFeatureFlagsToken, IFeatureContextToken, } from './feature/IFeatureFlags.js';
|
|
38
42
|
export { resolveConstructor } from './metadata/target.js';
|
|
39
43
|
export { addClassMeta, getClassMeta, addClassLabel, getClassLabels, addClassTag, getClassTags, createComposeClassDecorator, } from './metadata/class.js';
|
|
40
44
|
export { addParamMeta, getParamMeta, addParamLabel, getParamLabels, addParamTag, getParamTags, createComposeParameterDecorator, } from './metadata/parameter.js';
|