From a39d16deae17495265b93a151878122260b61644 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 7 Jan 2025 15:00:50 -0600 Subject: [PATCH] chore: preparation work for FDv2 support --- .../src/api/subsystems/LDDataSourceUpdates.ts | 7 ++ .../src/api/subsystems/LDFeatureStore.ts | 7 ++ .../src/data_sources/DataSourceUpdates.ts | 86 ++++++++++--------- .../src/store/InMemoryFeatureStore.ts | 50 +++++++++-- .../src/store/PersistentDataStoreWrapper.ts | 9 ++ 5 files changed, 111 insertions(+), 48 deletions(-) diff --git a/packages/shared/sdk-server/src/api/subsystems/LDDataSourceUpdates.ts b/packages/shared/sdk-server/src/api/subsystems/LDDataSourceUpdates.ts index 8b3badd38..04436bd17 100644 --- a/packages/shared/sdk-server/src/api/subsystems/LDDataSourceUpdates.ts +++ b/packages/shared/sdk-server/src/api/subsystems/LDDataSourceUpdates.ts @@ -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; } diff --git a/packages/shared/sdk-server/src/api/subsystems/LDFeatureStore.ts b/packages/shared/sdk-server/src/api/subsystems/LDFeatureStore.ts index 9bdfb9430..f062630c1 100644 --- a/packages/shared/sdk-server/src/api/subsystems/LDFeatureStore.ts +++ b/packages/shared/sdk-server/src/api/subsystems/LDFeatureStore.ts @@ -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. * diff --git a/packages/shared/sdk-server/src/data_sources/DataSourceUpdates.ts b/packages/shared/sdk-server/src/data_sources/DataSourceUpdates.ts index ac6e3820d..9de85c737 100644 --- a/packages/shared/sdk-server/src/data_sources/DataSourceUpdates.ts +++ b/packages/shared/sdk-server/src/data_sources/DataSourceUpdates.ts @@ -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( @@ -87,11 +111,18 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { if (checkForChanges) { const updatedItems = new NamespacedDataSet(); - 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, @@ -101,6 +132,7 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { ); }); }); + this.sendChangeEvents(updatedItems); } }); @@ -108,48 +140,20 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { }); }; + 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(); - 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( diff --git a/packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts b/packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts index 61814f2aa..e2a495d3b 100644 --- a/packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts +++ b/packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts @@ -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?.(); } diff --git a/packages/shared/sdk-server/src/store/PersistentDataStoreWrapper.ts b/packages/shared/sdk-server/src/store/PersistentDataStoreWrapper.ts index c682e4234..61878a002 100644 --- a/packages/shared/sdk-server/src/store/PersistentDataStoreWrapper.ts +++ b/packages/shared/sdk-server/src/store/PersistentDataStoreWrapper.ts @@ -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();