Skip to content

Commit 65aa8c8

Browse files
committed
adds fdv2 transactional persistent store
1 parent 179b199 commit 65aa8c8

File tree

3 files changed

+182
-67
lines changed

3 files changed

+182
-67
lines changed

packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ export default class InMemoryFeatureStore implements LDFeatureStore {
1818
if (Object.prototype.hasOwnProperty.call(items, key)) {
1919
const item = items[key];
2020
if (item && !item.deleted) {
21-
return callback?.(item);
21+
callback?.(item);
2222
}
2323
}
2424
}
25-
return callback?.(null);
25+
callback?.(null);
2626
}
2727

2828
all(kind: DataKind, callback: (res: LDFeatureStoreKindData) => void): void {
@@ -110,7 +110,7 @@ export default class InMemoryFeatureStore implements LDFeatureStore {
110110
}
111111

112112
initialized(callback: (isInitialized: boolean) => void): void {
113-
return callback?.(this._initCalled);
113+
callback?.(this._initCalled);
114114
}
115115

116116
/* eslint-disable class-methods-use-this */

packages/shared/sdk-server/src/store/PersistentDataStoreWrapper.ts

Lines changed: 81 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -123,32 +123,35 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore {
123123

124124
init(allData: LDFeatureStoreDataStorage, callback: () => void): void {
125125
this._queue.enqueue((cb) => {
126-
const afterStoreInit = () => {
127-
this._isInitialized = true;
128-
if (this._itemCache) {
129-
this._itemCache.clear();
130-
this._allItemsCache!.clear();
126+
this._internalInit(allData, cb);
127+
}, callback);
128+
}
129+
private _internalInit(allData: LDFeatureStoreDataStorage, callback: () => void): void {
130+
const afterStoreInit = () => {
131+
this._isInitialized = true;
132+
if (this._itemCache) {
133+
this._itemCache.clear();
134+
this._allItemsCache!.clear();
131135

132-
Object.keys(allData).forEach((kindNamespace) => {
133-
const kind = persistentStoreKinds[kindNamespace];
134-
const items = allData[kindNamespace];
135-
this._allItemsCache!.set(allForKindCacheKey(kind), items);
136-
Object.keys(items).forEach((key) => {
137-
const itemForKey = items[key];
136+
Object.keys(allData).forEach((kindNamespace) => {
137+
const kind = persistentStoreKinds[kindNamespace];
138+
const items = allData[kindNamespace];
139+
this._allItemsCache!.set(allForKindCacheKey(kind), items);
140+
Object.keys(items).forEach((key) => {
141+
const itemForKey = items[key];
138142

139-
const itemDescriptor: ItemDescriptor = {
140-
version: itemForKey.version,
141-
item: itemForKey,
142-
};
143-
this._itemCache!.set(cacheKey(kind, key), itemDescriptor);
144-
});
143+
const itemDescriptor: ItemDescriptor = {
144+
version: itemForKey.version,
145+
item: itemForKey,
146+
};
147+
this._itemCache!.set(cacheKey(kind, key), itemDescriptor);
145148
});
146-
}
147-
cb();
148-
};
149+
});
150+
}
151+
callback();
152+
};
149153

150-
this._core.init(sortDataSet(allData), afterStoreInit);
151-
}, callback);
154+
this._core.init(sortDataSet(allData), afterStoreInit);
152155
}
153156

154157
get(kind: DataKind, key: string, callback: (res: LDFeatureStoreItem | null) => void): void {
@@ -218,59 +221,73 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore {
218221

219222
upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void {
220223
this._queue.enqueue((cb) => {
221-
// Clear the caches which contain all the values of a specific kind.
222-
if (this._allItemsCache) {
223-
this._allItemsCache.clear();
224-
}
224+
this._internalUpsert(kind, data, cb);
225+
}, callback);
226+
}
227+
228+
private _internalUpsert(
229+
kind: DataKind,
230+
data: LDKeyedFeatureStoreItem,
231+
callback: () => void,
232+
): void {
233+
// Clear the caches which contain all the values of a specific kind.
234+
if (this._allItemsCache) {
235+
this._allItemsCache.clear();
236+
}
225237

226-
const persistKind = persistentStoreKinds[kind.namespace];
227-
this._core.upsert(
228-
persistKind,
229-
data.key,
230-
persistKind.serialize(data),
231-
(err, updatedDescriptor) => {
232-
if (!err && updatedDescriptor) {
233-
if (updatedDescriptor.serializedItem) {
234-
const value = deserialize(persistKind, updatedDescriptor);
235-
this._itemCache?.set(cacheKey(kind, data.key), value);
236-
} else if (updatedDescriptor.deleted) {
237-
// Deleted and there was not a serialized representation.
238-
this._itemCache?.set(data.key, {
239-
key: data.key,
240-
version: updatedDescriptor.version,
241-
deleted: true,
242-
});
243-
}
238+
const persistKind = persistentStoreKinds[kind.namespace];
239+
this._core.upsert(
240+
persistKind,
241+
data.key,
242+
persistKind.serialize(data),
243+
(err, updatedDescriptor) => {
244+
if (!err && updatedDescriptor) {
245+
if (updatedDescriptor.serializedItem) {
246+
const value = deserialize(persistKind, updatedDescriptor);
247+
this._itemCache?.set(cacheKey(kind, data.key), value);
248+
} else if (updatedDescriptor.deleted) {
249+
// Deleted and there was not a serialized representation.
250+
this._itemCache?.set(data.key, {
251+
key: data.key,
252+
version: updatedDescriptor.version,
253+
deleted: true,
254+
});
244255
}
245-
cb();
246-
},
247-
);
248-
}, callback);
256+
}
257+
callback();
258+
},
259+
);
249260
}
250261

251262
delete(kind: DataKind, key: string, version: number, callback: () => void): void {
252263
this.upsert(kind, { key, version, deleted: true }, callback);
253264
}
254265

255266
applyChanges(
256-
_basis: boolean,
257-
_data: LDFeatureStoreDataStorage,
258-
_selector: String | undefined,
259-
_callback: () => void,
267+
basis: boolean,
268+
data: LDFeatureStoreDataStorage,
269+
selector: String | undefined, // TODO: SDK-1044 - Utilize selector
270+
callback: () => void,
260271
): void {
261-
// TODO: SDK-1029 - Transactional persistent store - update this to not iterate over items and instead send data to underlying PersistentDataStore
262-
// no need for queue at the moment as init and upsert handle that, but as part of SDK-1029, queue may be needed
263-
if (_basis) {
264-
this.init(_data, _callback);
265-
} else {
266-
Object.entries(_data).forEach(([namespace, items]) => {
267-
Object.keys(items || {}).forEach((key) => {
268-
const item = items[key];
269-
this.upsert({ namespace }, { key, ...item }, () => {});
272+
this._queue.enqueue((cb) => {
273+
if (basis) {
274+
this._internalInit(data, cb);
275+
} else {
276+
const promises: Promise<void>[] = [];
277+
Object.entries(data).forEach(([namespace, items]) => {
278+
Object.keys(items || {}).forEach((key) => {
279+
promises.push(
280+
new Promise<void>((resolve, _) => {
281+
const item = items[key];
282+
this._internalUpsert({ namespace }, { key, ...item }, resolve); // callback intentionally not passed
283+
}),
284+
);
285+
});
270286
});
271-
});
272-
_callback();
273-
}
287+
// invoke callback after all operations complete
288+
Promise.all(promises).then(cb);
289+
}
290+
}, callback);
274291
}
275292

276293
close(): void {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { DataKind } from '../api/interfaces';
2+
import {
3+
LDFeatureStore,
4+
LDFeatureStoreDataStorage,
5+
LDFeatureStoreItem,
6+
LDFeatureStoreKindData,
7+
LDKeyedFeatureStoreItem,
8+
} from '../api/subsystems';
9+
import InMemoryFeatureStore from './InMemoryFeatureStore';
10+
11+
/**
12+
* This decorator can take a non-transactional {@link LDFeatureStore} implementation
13+
* and adapt it to be transactional through the use of an in-memory store acting as
14+
* cache.
15+
*/
16+
export default class TransactionalPersistentStore implements LDFeatureStore {
17+
private _memoryStore: LDFeatureStore;
18+
private _activeStore: LDFeatureStore;
19+
20+
constructor(private readonly _nonTransPersistenceStore: LDFeatureStore) {
21+
this._activeStore = this._nonTransPersistenceStore;
22+
this._memoryStore = new InMemoryFeatureStore();
23+
}
24+
25+
get(kind: DataKind, key: string, callback: (res: LDFeatureStoreItem | null) => void): void {
26+
this._activeStore.get(kind, key, callback);
27+
}
28+
29+
all(kind: DataKind, callback: (res: LDFeatureStoreKindData) => void): void {
30+
this._activeStore.all(kind, callback);
31+
}
32+
33+
init(allData: LDFeatureStoreDataStorage, callback: () => void): void {
34+
// adapt to applyChanges for common handling
35+
this.applyChanges(true, allData, undefined, callback);
36+
}
37+
38+
delete(kind: DataKind, key: string, version: number, callback: () => void): void {
39+
// adapt to applyChanges for common handling
40+
const item: LDKeyedFeatureStoreItem = { key, version, deleted: true };
41+
this.applyChanges(
42+
false,
43+
{
44+
[kind.namespace]: {
45+
[key]: item,
46+
},
47+
},
48+
undefined,
49+
callback,
50+
);
51+
}
52+
53+
upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void {
54+
// adapt to applyChanges for common handling
55+
this.applyChanges(
56+
false,
57+
{
58+
[kind.namespace]: {
59+
[data.key]: data,
60+
},
61+
},
62+
undefined,
63+
callback,
64+
);
65+
}
66+
67+
applyChanges(
68+
basis: boolean,
69+
data: LDFeatureStoreDataStorage,
70+
selector: String | undefined, // TODO: SDK-1044 - Utilize selector
71+
callback: () => void,
72+
): void {
73+
this._memoryStore.applyChanges(basis, data, selector, () => {
74+
// TODO: SDK-1047 conditional propgation to persistence based on parameter
75+
this._nonTransPersistenceStore.applyChanges(basis, data, selector, callback)
76+
});
77+
78+
if (basis) {
79+
// basis causes memory store to become the active store
80+
this._activeStore = this._memoryStore;
81+
}
82+
}
83+
84+
initialized(callback: (isInitialized: boolean) => void): void {
85+
// this is valid because the active store will only switch to the in memory store
86+
// after it has already been initialized itself
87+
this._activeStore.initialized(callback);
88+
}
89+
90+
close(): void {
91+
this._nonTransPersistenceStore.close();
92+
this._memoryStore.close();
93+
}
94+
95+
getDescription(): string {
96+
return 'transactional persistent store';
97+
}
98+
}

0 commit comments

Comments
 (0)