Skip to content

Commit

Permalink
chore: preparation work for FDv2 support
Browse files Browse the repository at this point in the history
  • Loading branch information
tanderson-ld committed Jan 7, 2025
1 parent 7f5c275 commit a39d16d
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,11 @@ export interface LDDataSourceUpdates {
* Will be called after the upsert operation is complete.
*/
upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void;

applyChanges(
basis: boolean,
data: LDFeatureStoreDataStorage,
selector: String,
callback: () => void,
): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ export interface LDFeatureStore {
*/
upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void;

applyChanges(
basis: boolean,
data: LDFeatureStoreDataStorage,
selector: String | undefined,
callback: () => void,
): void;

/**
* Tests whether the store is initialized.
*
Expand Down
86 changes: 45 additions & 41 deletions packages/shared/sdk-server/src/data_sources/DataSourceUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,38 @@ export default class DataSourceUpdates implements LDDataSourceUpdates {
) {}

init(allData: LDFeatureStoreDataStorage, callback: () => void): void {
this.applyChanges(true, allData, undefined, callback); // basis is true for init. selector is undefined for FDv1 init
}

upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void {
this.applyChanges(
false, // basis is false for upserts
{
[kind.namespace]: {
[data.key]: data,
},
},
undefined, // selector is undefined for FDv1 upsert
callback,
);
}

applyChanges(
basis: boolean,
data: LDFeatureStoreDataStorage,
selector: String | undefined,
callback: () => void,
): void {
const checkForChanges = this._hasEventListeners();
const doInit = (oldData?: LDFeatureStoreDataStorage) => {
this._featureStore.init(allData, () => {
const doApplyChanges = (oldData: LDFeatureStoreDataStorage) => {
this._featureStore.applyChanges(basis, data, selector, () => {
// Defer change events so they execute after the callback.
Promise.resolve().then(() => {
this._dependencyTracker.reset();
if (basis) {
this._dependencyTracker.reset();
}

Object.entries(allData).forEach(([namespace, items]) => {
Object.entries(data).forEach(([namespace, items]) => {
Object.keys(items || {}).forEach((key) => {
const item = items[key];
this._dependencyTracker.updateDependenciesFrom(
Expand All @@ -87,11 +111,18 @@ export default class DataSourceUpdates implements LDDataSourceUpdates {

if (checkForChanges) {
const updatedItems = new NamespacedDataSet<boolean>();
Object.keys(allData).forEach((namespace) => {
const oldDataForKind = oldData?.[namespace] || {};
const newDataForKind = allData[namespace];
const mergedData = { ...oldDataForKind, ...newDataForKind };
Object.keys(mergedData).forEach((key) => {
Object.keys(data).forEach((namespace) => {
const oldDataForKind = oldData[namespace];
const newDataForKind = data[namespace];
let iterateData;
if (basis) {
// for basis, need to iterate on all keys
iterateData = { ...oldDataForKind, ...newDataForKind };
} else {
// for non basis, only need to iterate on keys in incoming data
iterateData = { ...newDataForKind };
}
Object.keys(iterateData).forEach((key) => {
this.addIfModified(
namespace,
key,
Expand All @@ -101,55 +132,28 @@ export default class DataSourceUpdates implements LDDataSourceUpdates {
);
});
});

this.sendChangeEvents(updatedItems);
}
});
callback?.();
});
};

let oldData = {};
if (checkForChanges) {
// record old data before making changes to use for change calculations
this._featureStore.all(VersionedDataKinds.Features, (oldFlags) => {
this._featureStore.all(VersionedDataKinds.Segments, (oldSegments) => {
const oldData = {
oldData = {
[VersionedDataKinds.Features.namespace]: oldFlags,
[VersionedDataKinds.Segments.namespace]: oldSegments,
};
doInit(oldData);
});
});
} else {
doInit();
}
}

upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void {
const { key } = data;
const checkForChanges = this._hasEventListeners();
const doUpsert = (oldItem?: LDFeatureStoreItem | null) => {
this._featureStore.upsert(kind, data, () => {
// Defer change events so they execute after the callback.
Promise.resolve().then(() => {
this._dependencyTracker.updateDependenciesFrom(
kind.namespace,
key,
computeDependencies(kind.namespace, data),
);
if (checkForChanges) {
const updatedItems = new NamespacedDataSet<boolean>();
this.addIfModified(kind.namespace, key, oldItem, data, updatedItems);
this.sendChangeEvents(updatedItems);
}
});

callback?.();
});
};
if (checkForChanges) {
this._featureStore.get(kind, key, doUpsert);
} else {
doUpsert();
}
doApplyChanges(oldData);
}

addIfModified(
Expand Down
50 changes: 43 additions & 7 deletions packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,55 @@ export default class InMemoryFeatureStore implements LDFeatureStore {
}

init(allData: LDFeatureStoreDataStorage, callback: () => void): void {
this._initCalled = true;
this._allData = allData as LDFeatureStoreDataStorage;
callback?.();
this.applyChanges(true, allData, undefined, callback);
}

delete(kind: DataKind, key: string, version: number, callback: () => void): void {
const deletedItem = { version, deleted: true };
this._addItem(kind, key, deletedItem);
callback?.();
const item: LDKeyedFeatureStoreItem = { key, version, deleted: true };
this.applyChanges(
false,
{
[kind.namespace]: {
[key]: item,
},
},
undefined,
callback,
);
}

upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void {
this._addItem(kind, data.key, data);
this.applyChanges(
false,
{
[kind.namespace]: {
[data.key]: data,
},
},
undefined,
callback,
);
}

applyChanges(
basis: boolean,
data: LDFeatureStoreDataStorage,
selector: String | undefined, // TODO handle selector
callback: () => void,
): void {
if (basis) {
this._initCalled = true;
this._allData = data;
} else {
Object.entries(data).forEach(([namespace, items]) => {
Object.keys(items || {}).forEach((key) => {
const item = items[key];
// TODO: optimize this section, perhaps get rid of _addItem
this._addItem({ namespace }, key, item);
});
});
}

callback?.();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,15 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore {
this.upsert(kind, { key, version, deleted: true }, callback);
}

applyChanges(
_basis: boolean,
_data: LDFeatureStoreDataStorage,
_selector: String | undefined,
_callback: () => void,
): void {
// TODO: implement
}

close(): void {
this._itemCache?.close();
this._allItemsCache?.close();
Expand Down

0 comments on commit a39d16d

Please sign in to comment.