diff --git a/packages/client/src/provider/in-memory-provider/in-memory-provider.ts b/packages/client/src/provider/in-memory-provider/in-memory-provider.ts index 9b9cf9092..5cb141e7d 100644 --- a/packages/client/src/provider/in-memory-provider/in-memory-provider.ts +++ b/packages/client/src/provider/in-memory-provider/in-memory-provider.ts @@ -29,7 +29,7 @@ export class InMemoryProvider implements Provider { name: 'in-memory', } as const; private _flagConfiguration: FlagConfiguration; - private _flagEvalCache: Record> = {}; + private _context: EvaluationContext | undefined; constructor(flagConfiguration: FlagConfiguration = {}) { this._flagConfiguration = { ...flagConfiguration }; @@ -40,14 +40,10 @@ export class InMemoryProvider implements Provider { try { for (const key in this._flagConfiguration) { - if (this._flagConfiguration[key].disabled) { - continue; - } - - this._flagEvalCache[key] = this.resolveFlagWithReason(key, context); - + this.resolveFlagWithReason(key, context); } + this._context = context; this.events.emit(ProviderEvents.Ready); } catch (error) { this.status = ProviderStatus.ERROR; @@ -60,7 +56,7 @@ export class InMemoryProvider implements Provider { * Overwrites the configured flags. * @param { FlagConfiguration } flagConfiguration new flag configuration */ - putConfiguration(flagConfiguration: FlagConfiguration) { + async putConfiguration(flagConfiguration: FlagConfiguration) { const flagsChanged = Object.entries(flagConfiguration) .filter(([key, value]) => this._flagConfiguration[key] !== value) .map(([key]) => key); @@ -70,17 +66,8 @@ export class InMemoryProvider implements Provider { this._flagConfiguration = { ...flagConfiguration }; this.events.emit(ProviderEvents.ConfigurationChanged, { flagsChanged }); - } - - /** - * Overwrites the configured flags & updates internal cache (resolved flags). - * @param { FlagConfiguration } flagConfiguration new flag configuration - * @param { EvaluationContext } [context] flag evaluation context - */ - async putConfigurationAndUpdateResolution(flagConfiguration: FlagConfiguration, context?: EvaluationContext | undefined): Promise { - this.putConfiguration(flagConfiguration); - await this.initialize(context); + await this.initialize(this._context); } resolveBooleanEvaluation( @@ -89,7 +76,7 @@ export class InMemoryProvider implements Provider { context?: EvaluationContext, logger?: Logger, ): ResolutionDetails { - return this.resolveFromCache(flagKey, defaultValue, logger); + return this.resolveAndCheckFlag(flagKey, defaultValue, context || this._context, logger); } resolveNumberEvaluation( @@ -98,7 +85,7 @@ export class InMemoryProvider implements Provider { context?: EvaluationContext, logger?: Logger, ): ResolutionDetails { - return this.resolveFromCache(flagKey, defaultValue, logger); + return this.resolveAndCheckFlag(flagKey, defaultValue, context || this._context, logger); } resolveStringEvaluation( @@ -107,7 +94,7 @@ export class InMemoryProvider implements Provider { context?: EvaluationContext, logger?: Logger, ): ResolutionDetails { - return this.resolveFromCache(flagKey, defaultValue, logger); + return this.resolveAndCheckFlag(flagKey, defaultValue, context || this._context, logger); } resolveObjectEvaluation( @@ -116,12 +103,12 @@ export class InMemoryProvider implements Provider { context?: EvaluationContext, logger?: Logger, ): ResolutionDetails { - return this.resolveFromCache(flagKey, defaultValue, logger); + return this.resolveAndCheckFlag(flagKey, defaultValue, context || this._context, logger); } - private resolveFromCache(flagKey: string, - defaultValue: T, logger?: Logger): ResolutionDetails { - if (!(flagKey in this._flagEvalCache)) { + private resolveAndCheckFlag(flagKey: string, + defaultValue: T, context?: EvaluationContext, logger?: Logger): ResolutionDetails { + if (!(flagKey in this._flagConfiguration)) { const message = `no flag found with key ${flagKey}`; logger?.debug(message); throw new FlagNotFoundError(message); @@ -131,7 +118,7 @@ export class InMemoryProvider implements Provider { return { value: defaultValue, reason: StandardResolutionReasons.DISABLED }; } - const resolvedFlag = this._flagEvalCache[flagKey] as ResolutionDetails; + const resolvedFlag = this.resolveFlagWithReason(flagKey, context) as ResolutionDetails; if (resolvedFlag.value === undefined) { const message = `no value associated with variant provided for ${flagKey} found`; @@ -143,7 +130,7 @@ export class InMemoryProvider implements Provider { throw new TypeMismatchError(); } - return this.status === ProviderStatus.STALE ? { ...resolvedFlag, reason: StandardResolutionReasons.CACHED } : resolvedFlag; + return resolvedFlag; } private resolveFlagWithReason( diff --git a/packages/client/test/in-memory-provider.spec.ts b/packages/client/test/in-memory-provider.spec.ts index 8c601fb4a..00f3ef99f 100644 --- a/packages/client/test/in-memory-provider.spec.ts +++ b/packages/client/test/in-memory-provider.spec.ts @@ -70,7 +70,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(booleanFlagSpec); + await provider.putConfiguration(booleanFlagSpec); const resolution = provider.resolveBooleanEvaluation('a-boolean-flag', true); @@ -88,7 +88,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(booleanFlagSpec); + await provider.putConfiguration(booleanFlagSpec); expect(() => provider.resolveBooleanEvaluation('another-boolean-flag', false)).toThrow(); }); @@ -104,7 +104,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(booleanFlagDisabledSpec); + await provider.putConfiguration(booleanFlagDisabledSpec); const resolution = provider.resolveBooleanEvaluation('a-boolean-flag', false); @@ -122,7 +122,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'dummy', }, }; - await provider.putConfigurationAndUpdateResolution(booleanFlagSpec); + await provider.putConfiguration(booleanFlagSpec); expect(() => provider.resolveBooleanEvaluation('a-boolean-flag', false)).toThrow(VariantFoundError); }); @@ -138,7 +138,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(booleanFlagSpec); + await provider.putConfiguration(booleanFlagSpec); expect(() => provider.resolveBooleanEvaluation('a-boolean-flag', false)).toThrow(TypeMismatchError); }); @@ -156,33 +156,12 @@ describe(InMemoryProvider, () => { }, }; const dummyContext = {}; - await provider.putConfigurationAndUpdateResolution(booleanFlagCtxSpec, dummyContext); + await provider.putConfiguration(booleanFlagCtxSpec); const resolution = provider.resolveBooleanEvaluation('a-boolean-flag', true, dummyContext); expect(resolution).toEqual({ value: false, reason: StandardResolutionReasons.TARGETING_MATCH, variant: 'off' }); }); - - it('resolves to variant value with reason cache if provider state is STALE', async () => { - const booleanFlagSpec = { - 'a-boolean-flag': { - variants: { - on: true, - off: false, - }, - disabled: false, - defaultVariant: 'on', - }, - }; - await provider.putConfigurationAndUpdateResolution(booleanFlagSpec); - - provider.putConfiguration(booleanFlagSpec); - - const resolution = provider.resolveBooleanEvaluation('a-boolean-flag', true); - - expect(provider.status).toBe(ProviderStatus.STALE); - expect(resolution).toEqual({ value: true, reason: StandardResolutionReasons.CACHED, variant: 'on' }); - }); }); describe('string flags', () => { @@ -201,7 +180,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(stringFlagSpec); + await provider.putConfiguration(stringFlagSpec); const resolution = provider.resolveStringEvaluation('a-string-flag', itsDefault); @@ -220,7 +199,7 @@ describe(InMemoryProvider, () => { }, }; - await provider.putConfigurationAndUpdateResolution(StringFlagSpec); + await provider.putConfiguration(StringFlagSpec); expect(() => provider.resolveStringEvaluation('another-string-flag', itsDefault)).toThrow(FlagNotFoundError); }); @@ -236,7 +215,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(StringFlagDisabledSpec); + await provider.putConfiguration(StringFlagDisabledSpec); const resolution = provider.resolveStringEvaluation('a-string-flag', itsDefault); @@ -254,7 +233,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'dummy', }, }; - await provider.putConfigurationAndUpdateResolution(StringFlagSpec); + await provider.putConfiguration(StringFlagSpec); expect(() => provider.resolveStringEvaluation('a-string-flag', itsDefault)).toThrow(VariantFoundError); }); @@ -271,7 +250,7 @@ describe(InMemoryProvider, () => { }, }; - await provider.putConfigurationAndUpdateResolution(StringFlagSpec); + await provider.putConfiguration(StringFlagSpec); expect(() => provider.resolveStringEvaluation('a-string-flag', itsDefault)).toThrow(TypeMismatchError); }); @@ -289,33 +268,12 @@ describe(InMemoryProvider, () => { }, }; const dummyContext = {}; - await provider.putConfigurationAndUpdateResolution(StringFlagCtxSpec, dummyContext); + await provider.putConfiguration(StringFlagCtxSpec); const resolution = provider.resolveStringEvaluation('a-string-flag', itsDefault, dummyContext); expect(resolution).toEqual({ value: itsOff, reason: StandardResolutionReasons.TARGETING_MATCH, variant: 'off' }); }); - - it('resolves to default value with reason CACHED if provider has stale status', async () => { - const StringFlagSpec = { - 'a-string-flag': { - variants: { - on: itsOn, - off: itsOff, - }, - disabled: false, - defaultVariant: 'on', - }, - }; - - await provider.putConfigurationAndUpdateResolution(StringFlagSpec); - - provider.putConfiguration(StringFlagSpec); - - const resolution = provider.resolveStringEvaluation('a-string-flag', itsDefault); - - expect(resolution).toEqual({ value: itsOn, reason: StandardResolutionReasons.CACHED, variant: 'on' }); - }); }); describe('number flags', () => { @@ -334,7 +292,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(numberFlagSpec); + await provider.putConfiguration(numberFlagSpec); const resolution = provider.resolveNumberEvaluation('a-number-flag', defaultNumber); @@ -352,7 +310,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'dummy', }, }; - await provider.putConfigurationAndUpdateResolution(numberFlagSpec); + await provider.putConfiguration(numberFlagSpec); expect(() => provider.resolveNumberEvaluation('another-number-flag', defaultNumber)).toThrow(FlagNotFoundError); }); @@ -368,7 +326,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(numberFlagDisabledSpec); + await provider.putConfiguration(numberFlagDisabledSpec); const resolution = provider.resolveNumberEvaluation('a-number-flag', defaultNumber); @@ -386,7 +344,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'dummy', }, }; - await provider.putConfigurationAndUpdateResolution(numberFlagSpec); + await provider.putConfiguration(numberFlagSpec); expect(() => provider.resolveNumberEvaluation('a-number-flag', defaultNumber)).toThrow(VariantFoundError); }); @@ -403,7 +361,7 @@ describe(InMemoryProvider, () => { }, }; - await provider.putConfigurationAndUpdateResolution(numberFlagSpec); + await provider.putConfiguration(numberFlagSpec); expect(() => provider.resolveNumberEvaluation('a-number-flag', defaultNumber)).toThrow(TypeMismatchError); }); @@ -421,7 +379,7 @@ describe(InMemoryProvider, () => { }, }; const dummyContext = {}; - await provider.putConfigurationAndUpdateResolution(numberFlagCtxSpec, dummyContext); + await provider.putConfiguration(numberFlagCtxSpec); const resolution = provider.resolveNumberEvaluation('a-number-flag', defaultNumber, dummyContext); @@ -431,30 +389,6 @@ describe(InMemoryProvider, () => { variant: 'off', }); }); - - it('resolves to default variant value with reason CACHED if provider status is stale', async () => { - const numberFlagSpec = { - 'a-number-flag': { - variants: { - on: onNumber, - off: offNumber, - }, - disabled: false, - defaultVariant: 'on', - }, - }; - await provider.putConfigurationAndUpdateResolution(numberFlagSpec,); - - provider.putConfiguration(numberFlagSpec); - - const resolution = provider.resolveNumberEvaluation('a-number-flag', defaultNumber); - - expect(resolution).toEqual({ - value: onNumber, - reason: StandardResolutionReasons.CACHED, - variant: 'on', - }); - }); }); describe('Object flags', () => { @@ -473,7 +407,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(ObjectFlagSpec); + await provider.putConfiguration(ObjectFlagSpec); const resolution = provider.resolveObjectEvaluation('a-object-flag', defaultObject); @@ -491,7 +425,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'dummy', }, }; - await provider.putConfigurationAndUpdateResolution(ObjectFlagSpec); + await provider.putConfiguration(ObjectFlagSpec); expect(() => provider.resolveObjectEvaluation('another-number-flag', defaultObject)).toThrow(FlagNotFoundError); }); @@ -507,7 +441,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'on', }, }; - await provider.putConfigurationAndUpdateResolution(ObjectFlagDisabledSpec); + await provider.putConfiguration(ObjectFlagDisabledSpec); const resolution = provider.resolveObjectEvaluation('a-object-flag', defaultObject); @@ -525,7 +459,7 @@ describe(InMemoryProvider, () => { defaultVariant: 'dummy', }, }; - await provider.putConfigurationAndUpdateResolution(ObjectFlagSpec); + await provider.putConfiguration(ObjectFlagSpec); expect(() => provider.resolveObjectEvaluation('a-Object-flag', defaultObject)).toThrow(VariantFoundError); }); @@ -542,7 +476,7 @@ describe(InMemoryProvider, () => { }, }; - await provider.putConfigurationAndUpdateResolution(ObjectFlagSpec); + await provider.putConfiguration(ObjectFlagSpec); expect(() => provider.resolveObjectEvaluation('a-object-flag', defaultObject)).toThrow(TypeMismatchError); }); @@ -560,7 +494,7 @@ describe(InMemoryProvider, () => { }, }; const dummyContext = {}; - await provider.putConfigurationAndUpdateResolution(ObjectFlagCtxSpec, dummyContext); + await provider.putConfiguration(ObjectFlagCtxSpec); const resolution = provider.resolveObjectEvaluation('a-object-flag', defaultObject, dummyContext); @@ -570,104 +504,10 @@ describe(InMemoryProvider, () => { variant: 'off', }); }); - it('resolves to default variant value with reason CACHED if provider status is STALE', async () => { - const ObjectFlagSpec = { - 'a-object-flag': { - variants: { - on: onObject, - off: offObject, - }, - disabled: false, - defaultVariant: 'on', - }, - }; - - await provider.putConfigurationAndUpdateResolution(ObjectFlagSpec); - - provider.putConfiguration(ObjectFlagSpec); - - const resolution = provider.resolveObjectEvaluation('a-object-flag', defaultObject); - - expect(resolution).toEqual({ - value: onObject, - reason: StandardResolutionReasons.CACHED, - variant: 'on', - }); - }); }); describe('events', () => { - it('emits provider changed event, stale event, and has STALE status if a new value is added', () => { - const flagsSpec = { - 'some-flag': { - variants: { - on: 'initial-value', - }, - defaultVariant: 'on', - disabled: false, - }, - }; - const provider = new InMemoryProvider(flagsSpec); - - const configChangedSpy = jest.fn(); - provider.events.addHandler(ProviderEvents.ConfigurationChanged, configChangedSpy); - - const staleSpy = jest.fn(); - provider.events.addHandler(ProviderEvents.Stale, staleSpy); - - const newFlag = { - 'some-other-flag': { - variants: { - off: 'some-other-value', - }, - defaultVariant: 'off', - disabled: false, - }, - }; - - const newflagsSpec = { ...flagsSpec, ...newFlag }; - provider.putConfiguration(newflagsSpec); - - expect(configChangedSpy).toHaveBeenCalledWith({ flagsChanged: ['some-other-flag'] }); - expect(staleSpy).toHaveBeenCalled(); - expect(provider.status).toBe(ProviderStatus.STALE); - }); - - it('emits provider changed event, stale event and has STALE status if an existing value is changed', () => { - const flagsSpec = { - 'some-flag': { - variants: { - on: 'initial-value', - }, - defaultVariant: 'on', - disabled: false, - }, - }; - const provider = new InMemoryProvider(flagsSpec); - - const configChangedSpy = jest.fn(); - provider.events.addHandler(ProviderEvents.ConfigurationChanged, configChangedSpy); - - const staleSpy = jest.fn(); - provider.events.addHandler(ProviderEvents.Stale, staleSpy); - - const newFlagSpec = { - 'some-flag': { - variants: { - off: 'some-other-value', - }, - defaultVariant: 'off', - disabled: false, - }, - }; - provider.putConfiguration(newFlagSpec); - - expect(configChangedSpy).toHaveBeenCalledWith({ flagsChanged: ['some-flag'] }); - expect(staleSpy).toHaveBeenCalled(); - expect(provider.status).toBe(ProviderStatus.STALE); - }); - - it('emits provider changed event, stale event, ready event and has READY status if an existing value is changed and provider cache is updated', async () => { + it('emits provider changed event, ready event and has READY status', () => { const flagsSpec = { 'some-flag': { variants: { @@ -682,9 +522,6 @@ describe(InMemoryProvider, () => { const configChangedSpy = jest.fn(); provider.events.addHandler(ProviderEvents.ConfigurationChanged, configChangedSpy); - const staleSpy = jest.fn(); - provider.events.addHandler(ProviderEvents.Stale, staleSpy); - const readySpy = jest.fn(); provider.events.addHandler(ProviderEvents.Ready, readySpy); @@ -697,11 +534,10 @@ describe(InMemoryProvider, () => { disabled: false, }, }; - await provider.putConfigurationAndUpdateResolution(newFlagSpec); + provider.putConfiguration(newFlagSpec); expect(configChangedSpy).toHaveBeenCalledWith({ flagsChanged: ['some-flag'] }); - expect(staleSpy).toHaveBeenCalled(); - expect(readySpy).toHaveBeenCalled; + expect(readySpy).toHaveBeenCalled(); expect(provider.status).toBe(ProviderStatus.READY); }); }); @@ -728,7 +564,7 @@ describe(InMemoryProvider, () => { variant: 'on', }); - await provider.putConfigurationAndUpdateResolution({ + await provider.putConfiguration({ 'some-flag': { variants: { on: 'new-value',