import { IConfigurationStore } from './configuration-store/configuration-store'; import { IHttpClient } from './http-client'; import { ConfigStoreHydrationPacket, IConfiguration, StoreBackedConfiguration, } from './i-configuration'; import { BanditVariation, BanditParameters, Flag, BanditReference } from './interfaces'; // Requests AND stores flag configurations export default class ConfigurationRequestor { private banditModelVersions: string[] = []; private configuration: StoreBackedConfiguration; constructor( private readonly httpClient: IHttpClient, private flagConfigurationStore: IConfigurationStore, private banditVariationConfigurationStore: IConfigurationStore | null, private banditModelConfigurationStore: IConfigurationStore | null, ) { this.configuration = new StoreBackedConfiguration( this.flagConfigurationStore, this.banditVariationConfigurationStore, this.banditModelConfigurationStore, ); } /** * 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(); } public getConfiguration(): IConfiguration { return this.configuration; } async fetchAndStoreConfigurations(): Promise { const configResponse = await this.httpClient.getUniversalFlagConfiguration(); if (!configResponse?.flags) { return; } const flagResponsePacket: ConfigStoreHydrationPacket = { entries: configResponse.flags, environment: configResponse.environment, createdAt: configResponse.createdAt, format: configResponse.format, }; let banditVariationPacket: ConfigStoreHydrationPacket | undefined; let banditModelPacket: ConfigStoreHydrationPacket | undefined; const flagsHaveBandits = Object.keys(configResponse.banditReferences ?? {}).length > 0; const banditStoresProvided = Boolean( this.banditVariationConfigurationStore && this.banditModelConfigurationStore, ); if (flagsHaveBandits && banditStoresProvided) { // Map bandit flag associations by flag key for quick lookup (instead of bandit key as provided by the UFC) const banditVariations = this.indexBanditVariationsByFlagKey(configResponse.banditReferences); banditVariationPacket = { entries: banditVariations, environment: configResponse.environment, createdAt: configResponse.createdAt, format: configResponse.format, }; if ( this.requiresBanditModelConfigurationStoreUpdate( this.banditModelVersions, configResponse.banditReferences, ) ) { const banditResponse = await this.httpClient.getBanditParameters(); if (banditResponse?.bandits) { banditModelPacket = { entries: banditResponse.bandits, environment: configResponse.environment, createdAt: configResponse.createdAt, format: configResponse.format, }; this.banditModelVersions = this.getLoadedBanditModelVersions(banditResponse.bandits); } } } if ( await this.configuration.hydrateConfigurationStores( flagResponsePacket, banditVariationPacket, banditModelPacket, ) ) { // TODO: Notify that config updated. } } private getLoadedBanditModelVersions(entries: Record): string[] { return Object.values(entries).map((banditParam: BanditParameters) => banditParam.modelVersion); } private requiresBanditModelConfigurationStoreUpdate( currentBanditModelVersions: string[], banditReferences: Record, ): boolean { const referencedModelVersions = Object.values(banditReferences).map( (banditReference: BanditReference) => banditReference.modelVersion, ); return !referencedModelVersions.every((modelVersion) => currentBanditModelVersions.includes(modelVersion), ); } private indexBanditVariationsByFlagKey( banditVariationsByBanditKey: Record, ): Record { const banditVariationsByFlagKey: Record = {}; Object.values(banditVariationsByBanditKey).forEach((banditReference) => { banditReference.flagVariations.forEach((banditVariation) => { let banditVariations = banditVariationsByFlagKey[banditVariation.flagKey]; if (!banditVariations) { banditVariations = []; banditVariationsByFlagKey[banditVariation.flagKey] = banditVariations; } banditVariations.push(banditVariation); }); }); return banditVariationsByFlagKey; } }