Skip to content

Commit 2e26d79

Browse files
Merge branch 'inlocalstorage_storageAdapter' into inlocalstorage_sessionStorage
2 parents 98d0ef7 + afdf656 commit 2e26d79

File tree

9 files changed

+60
-53
lines changed

9 files changed

+60
-53
lines changed

src/storages/AbstractMySegmentsCacheSync.ts

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
4949
* For client-side synchronizer: it resets or updates the cache.
5050
*/
5151
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
52+
this.setChangeNumber(segmentsData.cn);
53+
5254
const { added, removed } = segmentsData as MySegmentsData;
53-
let isDiff = false;
5455

5556
if (added && removed) {
57+
let isDiff = false;
5658

5759
added.forEach(segment => {
5860
isDiff = this.addSegment(segment) || isDiff;
@@ -61,40 +63,32 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
6163
removed.forEach(segment => {
6264
isDiff = this.removeSegment(segment) || isDiff;
6365
});
64-
} else {
6566

66-
const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
67-
const storedSegmentKeys = this.getRegisteredSegments().sort();
67+
return isDiff;
68+
}
6869

69-
// Extreme fast => everything is empty
70-
if (!names.length && !storedSegmentKeys.length) {
71-
isDiff = false;
72-
} else {
70+
const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
71+
const storedSegmentKeys = this.getRegisteredSegments().sort();
7372

74-
let index = 0;
73+
// Extreme fast => everything is empty
74+
if (!names.length && !storedSegmentKeys.length) return false;
7575

76-
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
76+
let index = 0;
7777

78-
// Quick path => no changes
79-
if (index === names.length && index === storedSegmentKeys.length) {
80-
isDiff = false;
81-
} else {
78+
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
8279

83-
// Slowest path => add and/or remove segments
84-
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
85-
this.removeSegment(storedSegmentKeys[removeIndex]);
86-
}
80+
// Quick path => no changes
81+
if (index === names.length && index === storedSegmentKeys.length) return false;
8782

88-
for (let addIndex = index; addIndex < names.length; addIndex++) {
89-
this.addSegment(names[addIndex]);
90-
}
83+
// Slowest path => add and/or remove segments
84+
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
85+
this.removeSegment(storedSegmentKeys[removeIndex]);
86+
}
9187

92-
isDiff = true;
93-
}
94-
}
88+
for (let addIndex = index; addIndex < names.length; addIndex++) {
89+
this.addSegment(names[addIndex]);
9590
}
9691

97-
this.setChangeNumber(segmentsData.cn);
98-
return isDiff;
92+
return true;
9993
}
10094
}

src/storages/AbstractSplitsCacheSync.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
1414
protected abstract setChangeNumber(changeNumber: number): boolean | void
1515

1616
update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean {
17-
let updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
18-
updated = toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
1917
this.setChangeNumber(changeNumber);
20-
return updated;
18+
const updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
19+
return toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
2120
}
2221

2322
abstract getSplit(name: string): ISplit | null

src/storages/inLocalStorage/MySegmentsCacheInLocal.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
44
import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
55
import { LOG_PREFIX, DEFINED } from './constants';
66
import { StorageAdapter } from '../types';
7+
import { IMySegmentsResponse } from '../../dtos/types';
8+
import { MySegmentsData } from '../../sync/polling/types';
79

810
export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
911

@@ -19,6 +21,12 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
1921
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
2022
}
2123

24+
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
25+
const result = super.resetSegments(segmentsData);
26+
if (this.storage.save) this.storage.save();
27+
return result;
28+
}
29+
2230
protected addSegment(name: string): boolean {
2331
const segmentKey = this.keys.buildSegmentNameKey(name);
2432

src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
2626
}
2727

2828
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
29+
this.setChangeNumber(changeNumber);
2930
let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
3031
updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
31-
this.setChangeNumber(changeNumber);
32+
if (this.storage.save) this.storage.save();
3233
return updated;
3334
}
3435

src/storages/inLocalStorage/SplitsCacheInLocal.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
7979
this.hasSync = false;
8080
}
8181

82+
update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean {
83+
const result = super.update(toAdd, toRemove, changeNumber);
84+
if (this.storage.save) this.storage.save();
85+
return result;
86+
}
87+
8288
addSplit(split: ISplit) {
8389
try {
8490
const name = split.name;

src/storages/inLocalStorage/__tests__/storageAdapter.spec.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,22 @@ test.each([
4141
expect(storage.getItem('key1')).toBe(null);
4242
expect(storage.length).toBe(1);
4343

44-
// Until a till key is set/removed, changes should not be saved/persisted
44+
// Until `save` is called, changes should not be saved/persisted
4545
await storage.whenSaved();
4646
expect(wrapper.setItem).not.toHaveBeenCalled();
4747

48-
// When setting a till key, changes should be saved/persisted immediately
4948
storage.setItem('.till', '1');
5049
expect(storage.length).toBe(2);
5150
expect(storage.key(0)).toBe('key2');
5251
expect(storage.key(1)).toBe('.till');
5352

53+
// When `save` is called, changes should be saved/persisted immediately
54+
storage.save();
5455
await storage.whenSaved();
5556
expect(wrapper.setItem).toHaveBeenCalledWith('prefix', JSON.stringify({ key2: 'value2', '.till': '1' }));
5657

57-
// When removing a till key, changes should be saved/persisted immediately
58-
storage.removeItem('.till');
58+
expect(wrapper.setItem).toHaveBeenCalledTimes(1);
5959

6060
await storage.whenSaved();
61-
expect(wrapper.setItem).toHaveBeenCalledWith('prefix', JSON.stringify({ key2: 'value2' }));
62-
63-
await storage.whenSaved();
64-
expect(wrapper.setItem).toHaveBeenCalledTimes(2);
61+
expect(wrapper.setItem).toHaveBeenCalledTimes(1);
6562
});

src/storages/inLocalStorage/storageAdapter.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import SplitIO from '../../../types/splitio';
33
import { LOG_PREFIX } from './constants';
44
import { StorageAdapter } from '../types';
55

6-
function isTillKey(key: string) {
7-
return key.endsWith('.till');
8-
}
96

107
export function storageAdapter(log: ILogger, prefix: string, wrapper: SplitIO.SyncStorageWrapper | SplitIO.AsyncStorageWrapper): Required<StorageAdapter> {
118
let keys: string[] = [];
@@ -14,14 +11,6 @@ export function storageAdapter(log: ILogger, prefix: string, wrapper: SplitIO.Sy
1411
let loadPromise: Promise<void> | undefined;
1512
let savePromise = Promise.resolve();
1613

17-
function _save() {
18-
return savePromise = savePromise.then(() => {
19-
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
20-
}).catch((e) => {
21-
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
22-
});
23-
}
24-
2514
return {
2615
load() {
2716
return loadPromise || (loadPromise = Promise.resolve().then(() => {
@@ -33,32 +22,41 @@ export function storageAdapter(log: ILogger, prefix: string, wrapper: SplitIO.Sy
3322
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
3423
}));
3524
},
25+
26+
save() {
27+
return savePromise = savePromise.then(() => {
28+
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
29+
}).catch((e) => {
30+
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
31+
});
32+
},
33+
3634
whenSaved() {
3735
return savePromise;
3836
},
3937

4038
get length() {
4139
return keys.length;
4240
},
41+
4342
getItem(key: string) {
4443
return cache[key] || null;
4544
},
45+
4646
key(index: number) {
4747
return keys[index] || null;
4848
},
49+
4950
removeItem(key: string) {
5051
const index = keys.indexOf(key);
5152
if (index === -1) return;
5253
keys.splice(index, 1);
5354
delete cache[key];
54-
55-
if (isTillKey(key)) _save();
5655
},
56+
5757
setItem(key: string, value: string) {
5858
if (keys.indexOf(key) === -1) keys.push(key);
5959
cache[key] = value;
60-
61-
if (isTillKey(key)) _save();
6260
}
6361
};
6462
}

src/storages/inLocalStorage/validateCache.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ export function validateCache(options: SplitIO.InLocalStorageOptions, storage: S
8787
settings.log.error(LOG_PREFIX + e);
8888
}
8989

90+
// Persist clear
91+
if (storage.save) storage.save();
92+
9093
return false;
9194
}
9295

src/storages/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ISettings } from '../types';
1111
export interface StorageAdapter {
1212
// Methods to support async storages
1313
load?: () => Promise<void>;
14+
save?: () => Promise<void>;
1415
whenSaved?: () => Promise<void>;
1516
// Methods based on https://developer.mozilla.org/en-US/docs/Web/API/Storage
1617
readonly length: number;

0 commit comments

Comments
 (0)