diff --git a/package.json b/package.json index 29bba33..45ffc7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@eppo/js-client-sdk-common", - "version": "4.14.3", + "version": "4.14.4", "description": "Common library for Eppo JavaScript SDKs (web, react native, and node)", "main": "dist/index.js", "files": [ diff --git a/src/client/eppo-client.ts b/src/client/eppo-client.ts index fb58fa2..8d70c4f 100644 --- a/src/client/eppo-client.ts +++ b/src/client/eppo-client.ts @@ -209,6 +209,8 @@ export default class EppoClient { // noinspection JSUnusedGlobalSymbols setFlagConfigurationStore(flagConfigurationStore: IConfigurationStore) { this.flagConfigurationStore = flagConfigurationStore; + + this.updateConfigRequestorIfExists(); } // noinspection JSUnusedGlobalSymbols @@ -216,6 +218,8 @@ export default class EppoClient { banditVariationConfigurationStore: IConfigurationStore, ) { this.banditVariationConfigurationStore = banditVariationConfigurationStore; + + this.updateConfigRequestorIfExists(); } /** Sets the EventDispatcher instance to use when tracking events with {@link track}. */ @@ -244,6 +248,19 @@ export default class EppoClient { banditModelConfigurationStore: IConfigurationStore, ) { this.banditModelConfigurationStore = banditModelConfigurationStore; + + this.updateConfigRequestorIfExists(); + } + + private updateConfigRequestorIfExists() { + // Update the ConfigurationRequestor if it exists + if (this.configurationRequestor) { + this.configurationRequestor.setConfigurationStores( + this.flagConfigurationStore, + this.banditVariationConfigurationStore || null, + this.banditModelConfigurationStore || null, + ); + } } // noinspection JSUnusedGlobalSymbols diff --git a/src/configuration-requestor.spec.ts b/src/configuration-requestor.spec.ts index 2bfe61d..419c566 100644 --- a/src/configuration-requestor.spec.ts +++ b/src/configuration-requestor.spec.ts @@ -15,7 +15,7 @@ import FetchHttpClient, { IUniversalFlagConfigResponse, } from './http-client'; import { StoreBackedConfiguration } from './i-configuration'; -import { BanditParameters, BanditVariation, Flag } from './interfaces'; +import { BanditParameters, BanditVariation, Flag, VariationType } from './interfaces'; describe('ConfigurationRequestor', () => { let flagStore: IConfigurationStore; @@ -641,5 +641,72 @@ describe('ConfigurationRequestor', () => { // expect(fetchSpy.mock.calls.length).toBe(initialFetchCount + 1); }); }); + + describe('IConfigurationStore updates', () => { + it('should update configuration when stores are changed', async () => { + // Create new stores + const newFlagStore = new MemoryOnlyConfigurationStore(); + const newBanditVariationStore = new MemoryOnlyConfigurationStore(); + const newBanditModelStore = new MemoryOnlyConfigurationStore(); + + // Add a test flag to the new flag store + await newFlagStore.setEntries({ + 'test-flag': { + key: 'test-flag', + enabled: true, + variationType: VariationType.STRING, + variations: { + control: { key: 'control', value: 'control-value' }, + treatment: { key: 'treatment', value: 'treatment-value' }, + }, + allocations: [ + { + key: 'allocation-1', + rules: [], + splits: [ + { + shards: [{ salt: '', ranges: [{ start: 0, end: 10000 }] }], + variationKey: 'treatment', + }, + ], + doLog: true, + }, + ], + totalShards: 10000, + }, + }); + + await newBanditModelStore.setEntries({ + 'test-bandit': { + banditKey: 'test-bandt', + modelVersion: 'v123', + modelName: 'falcon', + modelData: { + coefficients: {}, + gamma: 0, + defaultActionScore: 0, + actionProbabilityFloor: 0, + }, + }, + }); + + // Get the configuration and verify it has the test flag + const initialConfig = configurationRequestor.getConfiguration(); + expect(initialConfig.getFlagKeys()).toEqual([]); + expect(Object.keys(initialConfig.getBandits())).toEqual([]); + + // Update the stores + configurationRequestor.setConfigurationStores( + newFlagStore, + newBanditVariationStore, + newBanditModelStore, + ); + + // Get the configuration and verify it has the test flag + const config = configurationRequestor.getConfiguration(); + expect(config.getFlagKeys()).toEqual(['test-flag']); + expect(Object.keys(config.getBandits())).toEqual(['test-bandit']); + }); + }); }); }); diff --git a/src/configuration-requestor.ts b/src/configuration-requestor.ts index 1f48e09..d8f56d2 100644 --- a/src/configuration-requestor.ts +++ b/src/configuration-requestor.ts @@ -10,15 +10,13 @@ import { BanditVariation, BanditParameters, Flag, BanditReference } from './inte // Requests AND stores flag configurations export default class ConfigurationRequestor { private banditModelVersions: string[] = []; - private readonly configuration: StoreBackedConfiguration; + private configuration: StoreBackedConfiguration; constructor( private readonly httpClient: IHttpClient, - private readonly flagConfigurationStore: IConfigurationStore, - private readonly banditVariationConfigurationStore: IConfigurationStore< - BanditVariation[] - > | null, - private readonly banditModelConfigurationStore: IConfigurationStore | null, + private flagConfigurationStore: IConfigurationStore, + private banditVariationConfigurationStore: IConfigurationStore | null, + private banditModelConfigurationStore: IConfigurationStore | null, ) { this.configuration = new StoreBackedConfiguration( this.flagConfigurationStore, @@ -27,6 +25,26 @@ export default class ConfigurationRequestor { ); } + /** + * Updates the configuration stores and recreates the StoreBackedConfiguration + */ + public setConfigurationStores( + flagConfigurationStore: IConfigurationStore, + banditVariationConfigurationStore: IConfigurationStore | null, + banditModelConfigurationStore: IConfigurationStore | null, + ): void { + this.flagConfigurationStore = flagConfigurationStore; + this.banditVariationConfigurationStore = banditVariationConfigurationStore; + this.banditModelConfigurationStore = banditModelConfigurationStore; + + // Recreate the configuration with the new stores + this.configuration = new StoreBackedConfiguration( + this.flagConfigurationStore, + this.banditVariationConfigurationStore, + this.banditModelConfigurationStore, + ); + } + public isFlagConfigExpired(): Promise { return this.flagConfigurationStore.isExpired(); }