Skip to content

Commit 4d458ba

Browse files
authored
feat: assignment cache to dedupe logging by the precomputed client (#164)
* Add assignment cache to precomputed init methods * Add test to confirm that it dedups assignment logging for the precomputedInit client * Add test for offlinePrecomputedInit * Switch test to useNonExpiringInMemoryAssignmentCache * v3.9.8 * Update docs
1 parent e9475e7 commit 4d458ba

File tree

5 files changed

+58
-3
lines changed

5 files changed

+58
-3
lines changed

docs/js-client-sdk.init.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
## init() function
66

7-
Initializes the Eppo client with configuration parameters. This method should be called once on application startup.
7+
Initializes the Eppo client with configuration parameters. This method should be called once on application startup. If an initialization is in process, calling `init` will return the in-progress `Promise<EppoClient>`<!-- -->. Once the initialization completes, calling `init` again will kick off the initialization routine (if `forceReinitialization` is `true`<!-- -->).
88

99
**Signature:**
1010

docs/js-client-sdk.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ Used to access a singleton SDK precomputed client instance. Use the method after
120120

121121
</td><td>
122122

123-
Initializes the Eppo client with configuration parameters. This method should be called once on application startup.
123+
Initializes the Eppo client with configuration parameters. This method should be called once on application startup. If an initialization is in process, calling `init` will return the in-progress `Promise<EppoClient>`<!-- -->. Once the initialization completes, calling `init` again will kick off the initialization routine (if `forceReinitialization` is `true`<!-- -->).
124124

125125

126126
</td></tr>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eppo/js-client-sdk",
3-
"version": "3.9.7",
3+
"version": "3.9.8",
44
"description": "Eppo SDK for client-side JavaScript applications",
55
"main": "dist/index.js",
66
"files": [

src/index.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,8 @@ describe('EppoPrecomputedJSClient E2E test', () => {
12821282
});
12831283

12841284
it('logs assignments correctly', () => {
1285+
const mockAssignmentCache = td.object<AssignmentCache>();
1286+
globalClient.useCustomAssignmentCache(mockAssignmentCache);
12851287
// Reset the mock logger before this test
12861288
mockLogger = td.object<IAssignmentLogger>();
12871289
globalClient.setAssignmentLogger(mockLogger);
@@ -1310,6 +1312,24 @@ describe('EppoPrecomputedJSClient E2E test', () => {
13101312
format: 'PRECOMPUTED',
13111313
});
13121314
});
1315+
1316+
it('deduplicates assignment logging', () => {
1317+
// Reset the mock logger and assignment cache before this test
1318+
const mockLogger = td.object<IAssignmentLogger>();
1319+
globalClient.useNonExpiringInMemoryAssignmentCache();
1320+
globalClient.setAssignmentLogger(mockLogger);
1321+
1322+
expect(td.explain(mockLogger.logAssignment).callCount).toEqual(0);
1323+
globalClient.getStringAssignment('string-flag', 'default');
1324+
expect(td.explain(mockLogger.logAssignment).callCount).toEqual(1);
1325+
globalClient.getStringAssignment('string-flag', 'default');
1326+
expect(td.explain(mockLogger.logAssignment).callCount).toEqual(1);
1327+
1328+
expect(td.explain(mockLogger.logAssignment).calls[0]?.args[0]).toMatchObject({
1329+
featureFlag: 'string-flag',
1330+
subject: 'test-subject',
1331+
});
1332+
});
13131333
});
13141334

13151335
describe('offlinePrecomputedInit', () => {
@@ -1396,6 +1416,22 @@ describe('offlinePrecomputedInit', () => {
13961416
{ times: 1 },
13971417
);
13981418
});
1419+
1420+
it('deduplicates assignment logging', () => {
1421+
const client = offlinePrecomputedInit({
1422+
precomputedConfiguration,
1423+
assignmentLogger: mockLogger,
1424+
});
1425+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(0);
1426+
client.getStringAssignment('string-flag', 'default');
1427+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1428+
client.getStringAssignment('string-flag', 'default');
1429+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1430+
expect(td.explain(mockLogger.logAssignment).calls[0]?.args[0]).toMatchObject({
1431+
subject: 'test-subject-key',
1432+
featureFlag: 'string-flag',
1433+
});
1434+
});
13991435
});
14001436

14011437
describe('EppoClient config', () => {

src/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,16 @@ export async function precomputedInit(
694694
skipInitialRequest = false,
695695
} = config;
696696

697+
// Add assignment cache initialization
698+
const storageKeySuffix = buildStorageKeySuffix(apiKey);
699+
const assignmentCache = assignmentCacheFactory({
700+
chromeStorage: chromeStorageIfAvailable(),
701+
storageKeySuffix,
702+
});
703+
if (assignmentCache instanceof HybridAssignmentCache) {
704+
await assignmentCache.init();
705+
}
706+
697707
// Set up parameters for requesting updated configurations
698708
const requestParameters: PrecomputedFlagsRequestParameters = {
699709
apiKey,
@@ -724,6 +734,7 @@ export async function precomputedInit(
724734
if (config.banditLogger) {
725735
EppoPrecomputedJSClient.instance.setBanditLogger(config.banditLogger);
726736
}
737+
EppoPrecomputedJSClient.instance.useCustomAssignmentCache(assignmentCache);
727738
await EppoPrecomputedJSClient.instance.fetchPrecomputedFlags();
728739

729740
EppoPrecomputedJSClient.initialized = true;
@@ -804,6 +815,13 @@ export function offlinePrecomputedInit(
804815
};
805816

806817
shutdownEppoPrecomputedClient();
818+
819+
// Add memory-only assignment cache for offline mode
820+
const assignmentCache = assignmentCacheFactory({
821+
storageKeySuffix: 'offline',
822+
forceMemoryOnly: true,
823+
});
824+
807825
EppoPrecomputedJSClient.instance = new EppoPrecomputedJSClient({
808826
precomputedFlagStore: memoryOnlyPrecomputedStore,
809827
precomputedBanditStore: memoryOnlyPrecomputedBanditStore,
@@ -816,6 +834,7 @@ export function offlinePrecomputedInit(
816834
if (config.banditLogger) {
817835
EppoPrecomputedJSClient.instance.setBanditLogger(config.banditLogger);
818836
}
837+
EppoPrecomputedJSClient.instance.useCustomAssignmentCache(assignmentCache);
819838
} catch (error) {
820839
applicationLogger.warn(
821840
'[Eppo SDK] Encountered an error initializing precomputed client, assignment calls will return the default value and not be logged',

0 commit comments

Comments
 (0)