Skip to content

Commit a626884

Browse files
[FSSDK-13110] cache update
1 parent de6b7ec commit a626884

12 files changed

Lines changed: 74 additions & 105 deletions

‎lib/event_processor/event_processor_factory.browser.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
*/
1616
import { EventDispatcher } from './event_dispatcher/event_dispatcher';
1717
import { EventProcessor } from './event_processor';
18-
import { EventWithId } from './batch_event_processor';
19-
import {
18+
import {
2019
getOpaqueBatchEventProcessor,
2120
BatchEventProcessorOptions,
2221
OpaqueEventProcessor,
@@ -44,7 +43,7 @@ export const createBatchEventProcessor = (
4443
options: BatchEventProcessorOptions = {}
4544
): OpaqueEventProcessor => {
4645
const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : new EventStore({
47-
store: new LocalStorageCache<EventWithId>(),
46+
store: new LocalStorageCache(),
4847
maxSize: options.batchSize ? Math.max(options.batchSize * 2, DEFAULT_MAX_EVENTS_IN_STORE)
4948
: DEFAULT_MAX_EVENTS_IN_STORE,
5049
ttl: options.storeTtl,

‎lib/event_processor/event_processor_factory.react_native.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
getForwardingEventProcessor,
2525
} from './event_processor_factory';
2626
import { FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory';
27-
import { EventWithId } from './batch_event_processor';
2827
import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native';
2928
import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native';
3029
import { DEFAULT_MAX_EVENTS_IN_STORE, EventStore } from './event_store';
@@ -43,7 +42,7 @@ export const createBatchEventProcessor = (
4342
options: BatchEventProcessorOptions = {}
4443
): OpaqueEventProcessor => {
4544
const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : new EventStore({
46-
store: new AsyncStorageCache<EventWithId>(),
45+
store: new AsyncStorageCache(),
4746
maxSize: options.batchSize ? Math.max(options.batchSize * 2, DEFAULT_MAX_EVENTS_IN_STORE)
4847
: DEFAULT_MAX_EVENTS_IN_STORE,
4948
ttl: options.storeTtl,

‎lib/event_processor/event_store.spec.ts‎

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type TestStoreConfig = {
3030
}
3131

3232
const getEventStore = (config: TestStoreConfig = {}) => {
33-
const mockStore = getMockAsyncCache<StoredEvent>();
33+
const mockStore = getMockAsyncCache<string>();
3434
const store = new EventStore({...config, store: mockStore });
3535
return { mockStore, store }
3636
}
@@ -213,17 +213,18 @@ describe('EventStore', () => {
213213
const originalSet = mockStore.set.bind(mockStore);
214214

215215
let call = 0;
216-
const setSpy = vi.spyOn(mockStore, 'set').mockImplementation(async (key: string, value: StoredEvent) => {
216+
const setSpy = vi.spyOn(mockStore, 'set').mockImplementation(async (key: string, value: string) => {
217217
if (call++ > 0) {
218218
return originalSet(key, value);
219219
}
220220

221221
// Simulate old stored event without time info
222+
const stored: StoredEvent = JSON.parse(value);
222223
const eventWithoutTime: StoredEvent = {
223-
id: value.id,
224-
event: value.event,
224+
id: stored.id,
225+
event: stored.event,
225226
};
226-
return originalSet(key, eventWithoutTime);
227+
return originalSet(key, JSON.stringify(eventWithoutTime));
227228
});
228229

229230
await store.set('test', event);
@@ -235,12 +236,12 @@ describe('EventStore', () => {
235236
await exhaustMicrotasks();
236237
expect(setSpy).toHaveBeenCalledTimes(2);
237238

238-
const secondCall = setSpy.mock.calls[1];
239+
const resavedEvent: StoredEvent = JSON.parse(setSpy.mock.calls[1][1]);
239240

240-
expect(secondCall[1]._time).toBeDefined();
241-
expect(secondCall[1]._time?.storedAt).toBeLessThanOrEqual(Date.now());
242-
expect(secondCall[1]._time?.storedAt).toBeGreaterThanOrEqual(Date.now() - 10);
243-
expect(secondCall[1]._time?.ttl).toBe(ttl);
241+
expect(resavedEvent._time).toBeDefined();
242+
expect(resavedEvent._time?.storedAt).toBeLessThanOrEqual(Date.now());
243+
expect(resavedEvent._time?.storedAt).toBeGreaterThanOrEqual(Date.now() - 10);
244+
expect(resavedEvent._time?.ttl).toBe(ttl);
244245
});
245246

246247
it('should store event when key expires after store being full', async () => {
@@ -327,37 +328,38 @@ describe('EventStore', () => {
327328
const originalSet = mockStore.set.bind(mockStore);
328329

329330
let call = 0;
330-
const setSpy = vi.spyOn(mockStore, 'set').mockImplementation(async (key: string, value: StoredEvent) => {
331+
const setSpy = vi.spyOn(mockStore, 'set').mockImplementation(async (key: string, value: string) => {
331332
if (call++ > 0) {
332333
return originalSet(key, value);
333334
}
334335

335336
// Simulate old stored event without time information
337+
const stored: StoredEvent = JSON.parse(value);
336338
const eventWithoutTime: StoredEvent = {
337-
id: value.id,
338-
event: value.event,
339+
id: stored.id,
340+
event: stored.event,
339341
};
340-
return originalSet(key, eventWithoutTime);
342+
return originalSet(key, JSON.stringify(eventWithoutTime));
341343
});
342344

343345
await store.set('key-1', event);
344346
await store.set('key-2', event);
345347

346348
const results = await store.getBatched(['key-1', 'key-2']);
347-
349+
348350
expect(results).toHaveLength(2);
349351
expect(results[0]).toEqual(expect.objectContaining(event));
350352
expect(results[1]).toEqual(expect.objectContaining(event));
351353

352354
await exhaustMicrotasks();
353355
expect(setSpy).toHaveBeenCalledTimes(3);
354356

355-
const secondCall = setSpy.mock.calls[1];
357+
const resavedEvent: StoredEvent = JSON.parse(setSpy.mock.calls[1][1]);
356358

357-
expect(secondCall[1]._time).toBeDefined();
358-
expect(secondCall[1]._time?.storedAt).toBeLessThanOrEqual(Date.now());
359-
expect(secondCall[1]._time?.storedAt).toBeGreaterThanOrEqual(Date.now() - 10);
360-
expect(secondCall[1]._time?.ttl).toBe(ttl);
359+
expect(resavedEvent._time).toBeDefined();
360+
expect(resavedEvent._time?.storedAt).toBeLessThanOrEqual(Date.now());
361+
expect(resavedEvent._time?.storedAt).toBeGreaterThanOrEqual(Date.now() - 10);
362+
expect(resavedEvent._time?.ttl).toBe(ttl);
361363
});
362364

363365
it('should store event when keys expire during getBatched after store being full', async () => {
@@ -401,7 +403,7 @@ describe('EventStore', () => {
401403
const originalSet = mockStore.set.bind(mockStore);
402404

403405
let call = 0;
404-
vi.spyOn(mockStore, 'set').mockImplementation(async (key: string, value: StoredEvent) => {
406+
vi.spyOn(mockStore, 'set').mockImplementation(async (key: string, value: string) => {
405407
// only the seconde set call should fail
406408
if (call++ != 1) return originalSet(key, value);
407409
return Promise.reject(new Error('Simulated set failure'));

‎lib/event_processor/event_store.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ export type StoredEvent = EventWithId & {
2020
};
2121
};
2222

23-
const identity = <T>(v: T): T => v;
24-
2523
const LOGGER_NAME = 'EventStore';
2624
export const DEFAULT_MAX_EVENTS_IN_STORE = 500;
2725
export const DEFAULT_STORE_TTL = 10 * 24 * 60 * 60 * 1000; // 10 days
@@ -31,7 +29,7 @@ export const EVENT_STORE_PREFIX = 'optly_event:';
3129
export type EventStoreConfig = {
3230
maxSize?: number;
3331
ttl?: number,
34-
store: Store<EventWithId>,
32+
store: Store<string>,
3533
logger?: LoggerFacade,
3634
};
3735

@@ -56,9 +54,9 @@ export class EventStore extends AsyncStoreWithBatchedGet<EventWithId> implements
5654
} = config;
5755

5856
if (store.operation === 'sync') {
59-
this.store = new SyncPrefixStore(store, EVENT_STORE_PREFIX, identity, identity);
57+
this.store = new SyncPrefixStore<string, StoredEvent>(store, EVENT_STORE_PREFIX, JSON.parse, JSON.stringify);
6058
} else {
61-
this.store = new AsyncPrefixStore(store, EVENT_STORE_PREFIX, identity, identity);
59+
this.store = new AsyncPrefixStore<string, StoredEvent>(store, EVENT_STORE_PREFIX, JSON.parse, JSON.stringify);
6260
}
6361

6462
if (logger) {

‎lib/project_config/polling_datafile_manager.spec.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ describe('PollingDatafileManager', () => {
106106
const repeater = getMockRepeater();
107107
const requestHandler = getMockRequestHandler(); // response promise is pending
108108
const cache = getMockAsyncCache<string>();
109-
await cache.set('opt-datafile-keyThatExists', JSON.stringify({ name: 'keyThatExists' }));
109+
await cache.set('opt-datafile-v6-keyThatExists', JSON.stringify({ name: 'keyThatExists' }));
110110

111111
const manager = new PollingDatafileManager({
112112
repeater,
@@ -131,7 +131,7 @@ describe('PollingDatafileManager', () => {
131131
requestHandler.makeRequest.mockReturnValueOnce(mockResponse);
132132

133133
const cache = getMockAsyncCache<string>();
134-
await cache.set('opt-datafile-keyThatExists', JSON.stringify({ name: 'keyThatExists' }));
134+
await cache.set('opt-datafile-v6-keyThatExists', JSON.stringify({ name: 'keyThatExists' }));
135135

136136
const manager = new PollingDatafileManager({
137137
repeater,
@@ -155,7 +155,7 @@ describe('PollingDatafileManager', () => {
155155
const repeater = getMockRepeater();
156156
const requestHandler = getMockRequestHandler();
157157
const cache = getMockAsyncCache<string>();
158-
await cache.set('opt-datafile-keyThatExists', JSON.stringify({ name: 'keyThatExists' }));
158+
await cache.set('opt-datafile-v6-keyThatExists', JSON.stringify({ name: 'keyThatExists' }));
159159
const mockResponse = getMockAbortableRequest();
160160
requestHandler.makeRequest.mockReturnValueOnce(mockResponse);
161161

@@ -564,7 +564,7 @@ describe('PollingDatafileManager', () => {
564564
repeater.execute(0);
565565

566566
await expect(manager.onRunning()).resolves.not.toThrow();
567-
expect(spy).toHaveBeenCalledWith('opt-datafile-keyThatDoesNotExists', '{"foo": "bar"}');
567+
expect(spy).toHaveBeenCalledWith('opt-datafile-v6-keyThatDoesNotExists', '{"foo": "bar"}');
568568
});
569569
});
570570

@@ -635,9 +635,9 @@ describe('PollingDatafileManager', () => {
635635
}
636636

637637
await expect(manager.onRunning()).resolves.not.toThrow();
638-
expect(spy).toHaveBeenNthCalledWith(1, 'opt-datafile-keyThatDoesNotExists', '{"foo": "bar"}');
639-
expect(spy).toHaveBeenNthCalledWith(2, 'opt-datafile-keyThatDoesNotExists', '{"foo2": "bar2"}');
640-
expect(spy).toHaveBeenNthCalledWith(3, 'opt-datafile-keyThatDoesNotExists', '{"foo3": "bar3"}');
638+
expect(spy).toHaveBeenNthCalledWith(1, 'opt-datafile-v6-keyThatDoesNotExists', '{"foo": "bar"}');
639+
expect(spy).toHaveBeenNthCalledWith(2, 'opt-datafile-v6-keyThatDoesNotExists', '{"foo2": "bar2"}');
640+
expect(spy).toHaveBeenNthCalledWith(3, 'opt-datafile-v6-keyThatDoesNotExists', '{"foo3": "bar3"}');
641641
});
642642

643643
it('logs an error if fetch request fails and does not call onUpdate handler', async () => {

‎lib/project_config/polling_datafile_manager.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag
7373
logger,
7474
} = config;
7575
this.cache = cache;
76-
this.cacheKey = 'opt-datafile-' + sdkKey;
76+
this.cacheKey = 'opt-datafile-v6-' + sdkKey;
7777
this.sdkKey = sdkKey;
7878
this.datafileAccessToken = datafileAccessToken;
7979
this.customHeaders = customHeaders;

‎lib/utils/cache/async_storage_cache.react_native.spec.ts‎

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,56 +20,41 @@ import { getDefaultAsyncStorage } from '../import.react_native/@react-native-asy
2020

2121
vi.mock('@react-native-async-storage/async-storage');
2222

23-
type TestData = {
24-
a: number;
25-
b: string;
26-
d: { e: boolean };
27-
};
28-
2923
describe('AsyncStorageCache', () => {
3024
const asyncStorage = getDefaultAsyncStorage();
3125

32-
it('should store a stringified value in async storage', async () => {
33-
const cache = new AsyncStorageCache<TestData>();
26+
it('should store the value as-is in async storage without serialization', async () => {
27+
const cache = new AsyncStorageCache();
3428

35-
const data = { a: 1, b: '2', d: { e: true } };
36-
await cache.set('key', data);
29+
await cache.set('key', 'value');
3730

38-
expect(await asyncStorage.getItem('key')).toBe(JSON.stringify(data));
39-
expect(await cache.get('key')).toEqual(data);
31+
expect(await asyncStorage.getItem('key')).toBe('value');
32+
expect(await cache.get('key')).toBe('value');
4033
});
4134

4235
it('should return undefined if get is called for a nonexistent key', async () => {
43-
const cache = new AsyncStorageCache<string>();
36+
const cache = new AsyncStorageCache();
4437

4538
expect(await cache.get('nonexistent')).toBeUndefined();
4639
});
4740

4841
it('should return the value if get is called for an existing key', async () => {
49-
const cache = new AsyncStorageCache<string>();
42+
const cache = new AsyncStorageCache();
5043
await cache.set('key', 'value');
5144

5245
expect(await cache.get('key')).toBe('value');
5346
});
5447

55-
it('should return the value after json parsing if get is called for an existing key', async () => {
56-
const cache = new AsyncStorageCache<TestData>();
57-
const data = { a: 1, b: '2', d: { e: true } };
58-
await cache.set('key', data);
59-
60-
expect(await cache.get('key')).toEqual(data);
61-
});
62-
6348
it('should remove the key from async storage when remove is called', async () => {
64-
const cache = new AsyncStorageCache<string>();
49+
const cache = new AsyncStorageCache();
6550
await cache.set('key', 'value');
6651
await cache.remove('key');
6752

6853
expect(await asyncStorage.getItem('key')).toBeNull();
6954
});
7055

7156
it('should remove all keys from async storage when clear is called', async () => {
72-
const cache = new AsyncStorageCache<string>();
57+
const cache = new AsyncStorageCache();
7358
await cache.set('key1', 'value1');
7459
await cache.set('key2', 'value2');
7560

@@ -79,15 +64,15 @@ describe('AsyncStorageCache', () => {
7964
});
8065

8166
it('should return all keys when getKeys is called', async () => {
82-
const cache = new AsyncStorageCache<string>();
67+
const cache = new AsyncStorageCache();
8368
await cache.set('key1', 'value1');
8469
await cache.set('key2', 'value2');
8570

8671
expect(await cache.getKeys()).toEqual(['key1', 'key2']);
8772
});
8873

8974
it('should return an array of values for an array of keys when getBatched is called', async () => {
90-
const cache = new AsyncStorageCache<string>();
75+
const cache = new AsyncStorageCache();
9176
await cache.set('key1', 'value1');
9277
await cache.set('key2', 'value2');
9378

‎lib/utils/cache/async_storage_cache.react_native.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,21 @@ import { AsyncStore } from "./store";
1919
import { getDefaultAsyncStorage } from "../import.react_native/@react-native-async-storage/async-storage";
2020
import { Platform } from '../../platform_support';
2121

22-
export class AsyncStorageCache<V> implements AsyncStore<V> {
22+
export class AsyncStorageCache implements AsyncStore<string> {
2323
public readonly operation = 'async';
2424
private asyncStorage = getDefaultAsyncStorage();
2525

26-
async get(key: string): Promise<V | undefined> {
26+
async get(key: string): Promise<string | undefined> {
2727
const value = await this.asyncStorage.getItem(key);
28-
return value ? JSON.parse(value) : undefined;
28+
return value ?? undefined;
2929
}
3030

3131
async remove(key: string): Promise<unknown> {
3232
return this.asyncStorage.removeItem(key);
3333
}
3434

35-
async set(key: string, val: V): Promise<unknown> {
36-
return this.asyncStorage.setItem(key, JSON.stringify(val));
35+
async set(key: string, val: string): Promise<unknown> {
36+
return this.asyncStorage.setItem(key, val);
3737
}
3838

3939
async clear(): Promise<unknown> {
@@ -44,9 +44,9 @@ export class AsyncStorageCache<V> implements AsyncStore<V> {
4444
return [... await this.asyncStorage.getAllKeys()];
4545
}
4646

47-
async getBatched(keys: string[]): Promise<Maybe<V>[]> {
47+
async getBatched(keys: string[]): Promise<Maybe<string>[]> {
4848
const items = await this.asyncStorage.multiGet(keys);
49-
return items.map(([key, value]) => value ? JSON.parse(value) : undefined);
49+
return items.map(([key, value]) => value ?? undefined);
5050
}
5151
}
5252

0 commit comments

Comments
 (0)