-
Notifications
You must be signed in to change notification settings - Fork 2
Offline Init Part 1 of 4: Add getFlagsConfiguration() to export flag configuration as JSON #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
be8cc13
e6fcbfd
1b804de
d777a2c
5136bc5
91d9fc7
a6d7de6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,14 @@ import { | |
| BanditVariation, | ||
| BoundedEventQueue, | ||
| ContextAttributes, | ||
| Environment, | ||
| EppoClient, | ||
| Event, | ||
| EventDispatcher, | ||
| Flag, | ||
| FlagConfigurationRequestParameters, | ||
| FlagKey, | ||
| FormatEnum, | ||
| MemoryOnlyConfigurationStore, | ||
| NamedEventQueue, | ||
| applicationLogger, | ||
|
|
@@ -41,6 +43,41 @@ export { IClientConfig }; | |
|
|
||
| let clientInstance: EppoClient; | ||
|
|
||
| // We keep references to the configuration stores at module level because EppoClient | ||
| // does not expose public getters for store metadata (format, createdAt, environment) | ||
| // or bandit configurations. These references are needed by getFlagsConfiguration() | ||
| // and getBanditsConfiguration() to reconstruct exportable configuration JSON. | ||
| let flagConfigurationStore: MemoryOnlyConfigurationStore<Flag>; | ||
| let banditVariationConfigurationStore: MemoryOnlyConfigurationStore<BanditVariation[]>; | ||
| let banditModelConfigurationStore: MemoryOnlyConfigurationStore<BanditParameters>; | ||
|
Comment on lines
+46
to
+52
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trying not to mess with the core SDK for now, but future us can expose these which would tidy this up a bit. |
||
|
|
||
| /** | ||
| * Represents a bandit reference linking a bandit to its flag variations. | ||
| * | ||
| * TODO: Remove this local definition once BanditReference is exported from @eppo/js-client-sdk-common. | ||
| * This duplicates the BanditReference interface from the common package's http-client module, | ||
| * which is not currently exported from the package's public API. | ||
| */ | ||
| interface BanditReference { | ||
| modelVersion: string; | ||
| flagVariations: BanditVariation[]; | ||
| } | ||
|
|
||
| /** | ||
| * Represents the universal flag configuration response format. | ||
| * | ||
| * TODO: Remove this local definition once IUniversalFlagConfigResponse is exported from @eppo/js-client-sdk-common. | ||
| * This duplicates the IUniversalFlagConfigResponse interface from the common package's http-client module, | ||
| * which is not currently exported from the package's public API. | ||
| */ | ||
| interface FlagsConfigurationResponse { | ||
| createdAt: string; | ||
| format: FormatEnum; | ||
| environment: Environment; | ||
| flags: Record<string, Flag>; | ||
| banditReferences: Record<string, BanditReference>; | ||
| } | ||
|
Comment on lines
+54
to
+79
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @greghuels it turns out the types from common repo used by the HttpClient were not exported. For containing change footprint opting to reproduce these here and then in a later effort can export and use them here.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good! |
||
|
|
||
| export const NO_OP_EVENT_DISPATCHER: EventDispatcher = { | ||
| // eslint-disable-next-line @typescript-eslint/no-empty-function | ||
| attachContext: () => {}, | ||
|
|
@@ -86,9 +123,9 @@ export async function init(config: IClientConfig): Promise<EppoClient> { | |
| throwOnFailedInitialization, | ||
| }; | ||
|
|
||
| const flagConfigurationStore = new MemoryOnlyConfigurationStore<Flag>(); | ||
| const banditVariationConfigurationStore = new MemoryOnlyConfigurationStore<BanditVariation[]>(); | ||
| const banditModelConfigurationStore = new MemoryOnlyConfigurationStore<BanditParameters>(); | ||
| flagConfigurationStore = new MemoryOnlyConfigurationStore<Flag>(); | ||
| banditVariationConfigurationStore = new MemoryOnlyConfigurationStore<BanditVariation[]>(); | ||
| banditModelConfigurationStore = new MemoryOnlyConfigurationStore<BanditParameters>(); | ||
| const eventDispatcher = newEventDispatcher(apiKey, eventTracking); | ||
|
|
||
| clientInstance = new EppoClient({ | ||
|
|
@@ -144,6 +181,71 @@ export function getInstance(): EppoClient { | |
| return clientInstance; | ||
| } | ||
|
|
||
| /** | ||
| * Reconstructs the current flags configuration as a JSON string. | ||
| * This can be used to bootstrap another SDK instance using offlineInit(). | ||
| * | ||
| * @returns JSON string containing the flags configuration, or null if not initialized | ||
| * @public | ||
| */ | ||
| export function getFlagsConfiguration(): string | null { | ||
| if (!flagConfigurationStore) { | ||
| return null; | ||
| } | ||
|
|
||
| // Build configuration matching FlagsConfigurationResponse structure. | ||
| // All fields are required - they are guaranteed to exist after successful initialization. | ||
| const configuration: FlagsConfigurationResponse = { | ||
| createdAt: flagConfigurationStore.getConfigPublishedAt() ?? new Date().toISOString(), | ||
| format: flagConfigurationStore.getFormat() ?? FormatEnum.SERVER, | ||
| environment: flagConfigurationStore.getEnvironment() ?? { name: 'UNKNOWN' }, | ||
| flags: flagConfigurationStore.entries(), | ||
| banditReferences: reconstructBanditReferences(), | ||
| }; | ||
|
|
||
| return JSON.stringify(configuration); | ||
| } | ||
|
|
||
| /** | ||
| * Reconstructs banditReferences from stored variations and parameters. | ||
| * The variations are stored indexed by flag key, so we need to re-pivot them | ||
| * back to being indexed by bandit key for export. | ||
| */ | ||
| function reconstructBanditReferences(): Record<string, BanditReference> { | ||
| if (!banditVariationConfigurationStore || !banditModelConfigurationStore) { | ||
| return {}; | ||
| } | ||
|
|
||
| const variationsByFlagKey = banditVariationConfigurationStore.entries(); | ||
| const banditParameters = banditModelConfigurationStore.entries(); | ||
|
|
||
| // Flatten all variations and group by bandit key | ||
| const variationsByBanditKey: Record<string, BanditVariation[]> = {}; | ||
| for (const variations of Object.values(variationsByFlagKey)) { | ||
| for (const variation of variations) { | ||
| const banditKey = variation.key; | ||
| if (!variationsByBanditKey[banditKey]) { | ||
| variationsByBanditKey[banditKey] = []; | ||
| } | ||
| variationsByBanditKey[banditKey].push(variation); | ||
| } | ||
| } | ||
|
|
||
| // Build banditReferences with model versions | ||
| const banditReferences: Record<string, BanditReference> = {}; | ||
| for (const [banditKey, variations] of Object.entries(variationsByBanditKey)) { | ||
| const params = banditParameters[banditKey]; | ||
| if (params) { | ||
| banditReferences[banditKey] = { | ||
| modelVersion: params.modelVersion, | ||
| flagVariations: variations, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| return banditReferences; | ||
| } | ||
|
|
||
| function newEventDispatcher( | ||
| sdkKey: string, | ||
| config: IClientConfig['eventTracking'] = {}, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.