@statezero/core 0.2.38 → 0.2.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/backend1/django_app/calculate-hash.d.ts +57 -0
- package/dist/actions/backend1/django_app/calculate-hash.js +80 -0
- package/dist/actions/backend1/django_app/calculate-hash.schema.json +148 -0
- package/dist/actions/backend1/django_app/get-current-username.d.ts +29 -0
- package/dist/actions/backend1/django_app/get-current-username.js +65 -0
- package/dist/actions/backend1/django_app/get-current-username.schema.json +47 -0
- package/dist/actions/backend1/django_app/get-server-status.d.ts +38 -0
- package/dist/actions/backend1/django_app/get-server-status.js +68 -0
- package/dist/actions/backend1/django_app/get-server-status.schema.json +93 -0
- package/dist/actions/backend1/django_app/get-user-info.d.ts +44 -0
- package/dist/actions/backend1/django_app/get-user-info.js +70 -0
- package/dist/actions/backend1/django_app/get-user-info.schema.json +127 -0
- package/dist/actions/backend1/django_app/index.d.ts +1 -0
- package/dist/actions/backend1/django_app/index.js +6 -0
- package/dist/actions/backend1/django_app/process-data.d.ts +51 -0
- package/dist/actions/backend1/django_app/process-data.js +78 -0
- package/dist/actions/backend1/django_app/process-data.schema.json +117 -0
- package/dist/actions/backend1/django_app/send-notification.d.ts +55 -0
- package/dist/actions/backend1/django_app/send-notification.js +81 -0
- package/dist/actions/backend1/django_app/send-notification.schema.json +175 -0
- package/dist/actions/backend1/index.d.ts +1 -0
- package/dist/actions/backend1/index.js +1 -0
- package/dist/actions/default/django_app/calculate-hash.d.ts +57 -0
- package/dist/actions/default/django_app/calculate-hash.js +80 -0
- package/dist/actions/default/django_app/calculate-hash.schema.json +148 -0
- package/dist/actions/default/django_app/get-current-username.d.ts +29 -0
- package/dist/actions/default/django_app/get-current-username.js +65 -0
- package/dist/actions/default/django_app/get-current-username.schema.json +47 -0
- package/dist/actions/default/django_app/get-server-status.d.ts +38 -0
- package/dist/actions/default/django_app/get-server-status.js +68 -0
- package/dist/actions/default/django_app/get-server-status.schema.json +93 -0
- package/dist/actions/default/django_app/get-user-info.d.ts +44 -0
- package/dist/actions/default/django_app/get-user-info.js +70 -0
- package/dist/actions/default/django_app/get-user-info.schema.json +127 -0
- package/dist/actions/default/django_app/index.d.ts +1 -0
- package/dist/actions/default/django_app/index.js +6 -0
- package/dist/actions/default/django_app/process-data.d.ts +51 -0
- package/dist/actions/default/django_app/process-data.js +78 -0
- package/dist/actions/default/django_app/process-data.schema.json +117 -0
- package/dist/actions/default/django_app/send-notification.d.ts +55 -0
- package/dist/actions/default/django_app/send-notification.js +81 -0
- package/dist/actions/default/django_app/send-notification.schema.json +175 -0
- package/dist/actions/default/index.d.ts +1 -0
- package/dist/actions/default/index.js +1 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +5 -0
- package/dist/adaptors/react/composables.d.ts +1 -0
- package/dist/adaptors/react/composables.js +4 -0
- package/dist/adaptors/react/index.d.ts +1 -0
- package/dist/adaptors/react/index.js +1 -0
- package/dist/adaptors/vue/components/LayoutRenderer.js +46 -49
- package/dist/adaptors/vue/components/defaults/index.d.ts +7 -0
- package/dist/adaptors/vue/components/defaults/index.js +31 -0
- package/dist/adaptors/vue/components/index.d.ts +1 -0
- package/dist/adaptors/vue/components/index.js +7 -0
- package/dist/adaptors/vue/composables.d.ts +2 -0
- package/dist/adaptors/vue/composables.js +44 -0
- package/dist/adaptors/vue/index.d.ts +3 -0
- package/dist/adaptors/vue/index.js +4 -0
- package/dist/adaptors/vue/reactivity.d.ts +18 -0
- package/dist/adaptors/vue/reactivity.js +132 -0
- package/dist/cli/commands/sync.d.ts +6 -0
- package/dist/cli/commands/sync.js +30 -0
- package/dist/cli/commands/syncActions.d.ts +46 -0
- package/dist/cli/commands/syncActions.js +717 -0
- package/dist/cli/commands/syncModels.d.ts +132 -0
- package/dist/cli/commands/syncModels.js +1120 -0
- package/dist/cli/configFileLoader.d.ts +10 -0
- package/dist/cli/configFileLoader.js +85 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +22 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.js +273 -0
- package/dist/core/eventReceivers.d.ts +185 -0
- package/dist/core/eventReceivers.js +266 -0
- package/dist/core/utils.d.ts +8 -0
- package/dist/core/utils.js +62 -0
- package/dist/errorHandler.d.ts +21 -0
- package/dist/errorHandler.js +27 -0
- package/dist/filtering/localFiltering.d.ts +110 -0
- package/dist/filtering/localFiltering.js +1080 -0
- package/dist/flavours/django/dates.d.ts +34 -0
- package/dist/flavours/django/dates.js +113 -0
- package/dist/flavours/django/errors.d.ts +138 -0
- package/dist/flavours/django/errors.js +195 -0
- package/dist/flavours/django/f.d.ts +6 -0
- package/dist/flavours/django/f.js +91 -0
- package/dist/flavours/django/files.d.ts +62 -0
- package/dist/flavours/django/files.js +355 -0
- package/dist/flavours/django/makeApiCall.d.ts +36 -0
- package/dist/flavours/django/makeApiCall.js +169 -0
- package/dist/flavours/django/manager.d.ts +204 -0
- package/dist/flavours/django/manager.js +222 -0
- package/dist/flavours/django/model.d.ts +137 -0
- package/dist/flavours/django/model.js +366 -0
- package/dist/flavours/django/operationFactory.d.ts +73 -0
- package/dist/flavours/django/operationFactory.js +248 -0
- package/dist/flavours/django/q.d.ts +70 -0
- package/dist/flavours/django/q.js +43 -0
- package/dist/flavours/django/queryExecutor.d.ts +149 -0
- package/dist/flavours/django/queryExecutor.js +590 -0
- package/dist/flavours/django/querySet.d.ts +301 -0
- package/dist/flavours/django/querySet.js +736 -0
- package/dist/flavours/django/serializers.d.ts +39 -0
- package/dist/flavours/django/serializers.js +296 -0
- package/dist/flavours/django/tempPk.d.ts +31 -0
- package/dist/flavours/django/tempPk.js +92 -0
- package/dist/flavours/django/utils.d.ts +19 -0
- package/dist/flavours/django/utils.js +29 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +48 -0
- package/dist/models/backend1/django_app/comprehensivemodel.d.ts +894 -0
- package/dist/models/backend1/django_app/comprehensivemodel.js +71 -0
- package/dist/models/backend1/django_app/comprehensivemodel.schema.json +870 -0
- package/dist/models/backend1/django_app/custompkmodel.d.ts +92 -0
- package/dist/models/backend1/django_app/custompkmodel.js +69 -0
- package/dist/models/backend1/django_app/custompkmodel.schema.json +71 -0
- package/dist/models/backend1/django_app/dailyrate.d.ts +230 -0
- package/dist/models/backend1/django_app/dailyrate.js +71 -0
- package/dist/models/backend1/django_app/dailyrate.schema.json +212 -0
- package/dist/models/backend1/django_app/deepmodellevel1.d.ts +140 -0
- package/dist/models/backend1/django_app/deepmodellevel1.js +72 -0
- package/dist/models/backend1/django_app/deepmodellevel1.schema.json +114 -0
- package/dist/models/backend1/django_app/deepmodellevel2.d.ts +118 -0
- package/dist/models/backend1/django_app/deepmodellevel2.js +71 -0
- package/dist/models/backend1/django_app/deepmodellevel2.schema.json +92 -0
- package/dist/models/backend1/django_app/deepmodellevel3.d.ts +92 -0
- package/dist/models/backend1/django_app/deepmodellevel3.js +69 -0
- package/dist/models/backend1/django_app/deepmodellevel3.schema.json +69 -0
- package/dist/models/backend1/django_app/dummymodel.d.ts +134 -0
- package/dist/models/backend1/django_app/dummymodel.js +71 -0
- package/dist/models/backend1/django_app/dummymodel.schema.json +109 -0
- package/dist/models/backend1/django_app/dummyrelatedmodel.d.ts +92 -0
- package/dist/models/backend1/django_app/dummyrelatedmodel.js +69 -0
- package/dist/models/backend1/django_app/dummyrelatedmodel.schema.json +69 -0
- package/dist/models/backend1/django_app/filetest.d.ts +140 -0
- package/dist/models/backend1/django_app/filetest.js +69 -0
- package/dist/models/backend1/django_app/filetest.schema.json +111 -0
- package/dist/models/backend1/django_app/index.d.ts +1 -0
- package/dist/models/backend1/django_app/index.js +21 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.d.ts +118 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.js +71 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.schema.json +94 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.d.ts +118 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.js +71 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.schema.json +94 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.d.ts +134 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.js +71 -0
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.schema.json +112 -0
- package/dist/models/backend1/django_app/modelwithcustompkrelation.d.ts +118 -0
- package/dist/models/backend1/django_app/modelwithcustompkrelation.js +71 -0
- package/dist/models/backend1/django_app/modelwithcustompkrelation.schema.json +93 -0
- package/dist/models/backend1/django_app/modelwithrestrictedfields.d.ts +134 -0
- package/dist/models/backend1/django_app/modelwithrestrictedfields.js +71 -0
- package/dist/models/backend1/django_app/modelwithrestrictedfields.schema.json +111 -0
- package/dist/models/backend1/django_app/namefiltercustompkmodel.d.ts +92 -0
- package/dist/models/backend1/django_app/namefiltercustompkmodel.js +69 -0
- package/dist/models/backend1/django_app/namefiltercustompkmodel.schema.json +71 -0
- package/dist/models/backend1/django_app/order.d.ts +220 -0
- package/dist/models/backend1/django_app/order.js +71 -0
- package/dist/models/backend1/django_app/order.schema.json +203 -0
- package/dist/models/backend1/django_app/orderitem.d.ts +172 -0
- package/dist/models/backend1/django_app/orderitem.js +72 -0
- package/dist/models/backend1/django_app/orderitem.schema.json +149 -0
- package/dist/models/backend1/django_app/product.d.ts +254 -0
- package/dist/models/backend1/django_app/product.js +71 -0
- package/dist/models/backend1/django_app/product.schema.json +277 -0
- package/dist/models/backend1/django_app/productcategory.d.ts +92 -0
- package/dist/models/backend1/django_app/productcategory.js +69 -0
- package/dist/models/backend1/django_app/productcategory.schema.json +70 -0
- package/dist/models/backend1/django_app/rateplan.d.ts +92 -0
- package/dist/models/backend1/django_app/rateplan.js +69 -0
- package/dist/models/backend1/django_app/rateplan.schema.json +70 -0
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.d.ts +108 -0
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.js +69 -0
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.schema.json +87 -0
- package/dist/models/backend1/fileobject.d.ts +4 -0
- package/dist/models/backend1/fileobject.js +9 -0
- package/dist/models/backend1/index.d.ts +2 -0
- package/dist/models/backend1/index.js +2 -0
- package/dist/models/default/django_app/comprehensivemodel.d.ts +894 -0
- package/dist/models/default/django_app/comprehensivemodel.js +71 -0
- package/dist/models/default/django_app/comprehensivemodel.schema.json +870 -0
- package/dist/models/default/django_app/custompkmodel.d.ts +92 -0
- package/dist/models/default/django_app/custompkmodel.js +69 -0
- package/dist/models/default/django_app/custompkmodel.schema.json +71 -0
- package/dist/models/default/django_app/dailyrate.d.ts +230 -0
- package/dist/models/default/django_app/dailyrate.js +71 -0
- package/dist/models/default/django_app/dailyrate.schema.json +212 -0
- package/dist/models/default/django_app/deepmodellevel1.d.ts +128 -0
- package/dist/models/default/django_app/deepmodellevel1.js +72 -0
- package/dist/models/default/django_app/deepmodellevel1.schema.json +102 -0
- package/dist/models/default/django_app/deepmodellevel2.d.ts +106 -0
- package/dist/models/default/django_app/deepmodellevel2.js +71 -0
- package/dist/models/default/django_app/deepmodellevel2.schema.json +80 -0
- package/dist/models/default/django_app/deepmodellevel3.d.ts +80 -0
- package/dist/models/default/django_app/deepmodellevel3.js +69 -0
- package/dist/models/default/django_app/deepmodellevel3.schema.json +57 -0
- package/dist/models/default/django_app/dummymodel.d.ts +122 -0
- package/dist/models/default/django_app/dummymodel.js +71 -0
- package/dist/models/default/django_app/dummymodel.schema.json +97 -0
- package/dist/models/default/django_app/dummyrelatedmodel.d.ts +80 -0
- package/dist/models/default/django_app/dummyrelatedmodel.js +69 -0
- package/dist/models/default/django_app/dummyrelatedmodel.schema.json +57 -0
- package/dist/models/default/django_app/filetest.d.ts +128 -0
- package/dist/models/default/django_app/filetest.js +69 -0
- package/dist/models/default/django_app/filetest.schema.json +99 -0
- package/dist/models/default/django_app/index.d.ts +1 -0
- package/dist/models/default/django_app/index.js +21 -0
- package/dist/models/default/django_app/m2mdepthtestlevel1.d.ts +118 -0
- package/dist/models/default/django_app/m2mdepthtestlevel1.js +71 -0
- package/dist/models/default/django_app/m2mdepthtestlevel1.schema.json +94 -0
- package/dist/models/default/django_app/m2mdepthtestlevel2.d.ts +118 -0
- package/dist/models/default/django_app/m2mdepthtestlevel2.js +71 -0
- package/dist/models/default/django_app/m2mdepthtestlevel2.schema.json +94 -0
- package/dist/models/default/django_app/m2mdepthtestlevel3.d.ts +134 -0
- package/dist/models/default/django_app/m2mdepthtestlevel3.js +71 -0
- package/dist/models/default/django_app/m2mdepthtestlevel3.schema.json +112 -0
- package/dist/models/default/django_app/modelwithcustompkrelation.d.ts +118 -0
- package/dist/models/default/django_app/modelwithcustompkrelation.js +71 -0
- package/dist/models/default/django_app/modelwithcustompkrelation.schema.json +93 -0
- package/dist/models/default/django_app/modelwithrestrictedfields.d.ts +134 -0
- package/dist/models/default/django_app/modelwithrestrictedfields.js +71 -0
- package/dist/models/default/django_app/modelwithrestrictedfields.schema.json +111 -0
- package/dist/models/default/django_app/namefiltercustompkmodel.d.ts +92 -0
- package/dist/models/default/django_app/namefiltercustompkmodel.js +69 -0
- package/dist/models/default/django_app/namefiltercustompkmodel.schema.json +71 -0
- package/dist/models/default/django_app/order.d.ts +220 -0
- package/dist/models/default/django_app/order.js +71 -0
- package/dist/models/default/django_app/order.schema.json +203 -0
- package/dist/models/default/django_app/orderitem.d.ts +172 -0
- package/dist/models/default/django_app/orderitem.js +72 -0
- package/dist/models/default/django_app/orderitem.schema.json +149 -0
- package/dist/models/default/django_app/product.d.ts +254 -0
- package/dist/models/default/django_app/product.js +71 -0
- package/dist/models/default/django_app/product.schema.json +277 -0
- package/dist/models/default/django_app/productcategory.d.ts +92 -0
- package/dist/models/default/django_app/productcategory.js +69 -0
- package/dist/models/default/django_app/productcategory.schema.json +70 -0
- package/dist/models/default/django_app/rateplan.d.ts +92 -0
- package/dist/models/default/django_app/rateplan.js +69 -0
- package/dist/models/default/django_app/rateplan.schema.json +70 -0
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.d.ts +108 -0
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.js +69 -0
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.schema.json +87 -0
- package/dist/models/default/fileobject.d.ts +4 -0
- package/dist/models/default/fileobject.js +9 -0
- package/dist/models/default/index.d.ts +2 -0
- package/dist/models/default/index.js +2 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +5 -0
- package/dist/react-entry.d.ts +2 -0
- package/dist/react-entry.js +2 -0
- package/dist/reactiveAdaptor.d.ts +24 -0
- package/dist/reactiveAdaptor.js +38 -0
- package/dist/reset.d.ts +15 -0
- package/dist/reset.js +97 -0
- package/dist/setup.d.ts +15 -0
- package/dist/setup.js +33 -0
- package/dist/syncEngine/cache/cache.d.ts +75 -0
- package/dist/syncEngine/cache/cache.js +355 -0
- package/dist/syncEngine/metrics/metricOptCalcs.d.ts +79 -0
- package/dist/syncEngine/metrics/metricOptCalcs.js +284 -0
- package/dist/syncEngine/registries/metricRegistry.d.ts +58 -0
- package/dist/syncEngine/registries/metricRegistry.js +171 -0
- package/dist/syncEngine/registries/modelStoreRegistry.d.ts +11 -0
- package/dist/syncEngine/registries/modelStoreRegistry.js +63 -0
- package/dist/syncEngine/registries/querysetStoreGraph.d.ts +41 -0
- package/dist/syncEngine/registries/querysetStoreGraph.js +174 -0
- package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +72 -0
- package/dist/syncEngine/registries/querysetStoreRegistry.js +335 -0
- package/dist/syncEngine/stores/metricStore.d.ts +55 -0
- package/dist/syncEngine/stores/metricStore.js +222 -0
- package/dist/syncEngine/stores/modelStore.d.ts +53 -0
- package/dist/syncEngine/stores/modelStore.js +565 -0
- package/dist/syncEngine/stores/operation.d.ts +139 -0
- package/dist/syncEngine/stores/operation.js +291 -0
- package/dist/syncEngine/stores/operationEventHandlers.d.ts +8 -0
- package/dist/syncEngine/stores/operationEventHandlers.js +322 -0
- package/dist/syncEngine/stores/querysetStore.d.ts +60 -0
- package/dist/syncEngine/stores/querysetStore.js +294 -0
- package/dist/syncEngine/stores/reactivity.d.ts +3 -0
- package/dist/syncEngine/stores/reactivity.js +4 -0
- package/dist/syncEngine/stores/utils.d.ts +14 -0
- package/dist/syncEngine/stores/utils.js +32 -0
- package/dist/syncEngine/sync.d.ts +46 -0
- package/dist/syncEngine/sync.js +389 -0
- package/dist/testing.d.ts +63 -0
- package/dist/testing.js +175 -0
- package/dist/vue-entry.d.ts +15 -0
- package/dist/vue-entry.js +7 -0
- package/package.json +6 -7
- package/dist/adaptors/vue/components/layout.tailwind.css +0 -51
- /package/{dist → src}/adaptors/vue/components/layout.css +0 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
import { Operation, Status, Type, operationRegistry } from './operation.js';
|
|
2
|
+
import { isNil, isEmpty, trim, isEqual } from 'lodash-es';
|
|
3
|
+
import { modelEventEmitter } from './reactivity.js';
|
|
4
|
+
import { Cache } from '../cache/cache.js';
|
|
5
|
+
import { replaceTempPks, containsTempPk, resolveToRealPk, onTempPkResolved } from '../../flavours/django/tempPk.js';
|
|
6
|
+
const emitEvents = (store, event) => {
|
|
7
|
+
if (!event || !event.pks || event.pks.length === 0)
|
|
8
|
+
return;
|
|
9
|
+
// Get prior values for these pks in one pass (before computing new)
|
|
10
|
+
const lastRenderedDataArray = event.pks.map(pk => store._lastRenderedData.get(pk) ?? null);
|
|
11
|
+
// Batch render all pks - useCache=false to get fresh data for comparison
|
|
12
|
+
const newRenderedDataArray = store.render(event.pks, true, false);
|
|
13
|
+
// Single equality check on the whole batch
|
|
14
|
+
if (!isEqual(newRenderedDataArray, lastRenderedDataArray)) {
|
|
15
|
+
// Update cache for all pks
|
|
16
|
+
const pkField = store.pkField;
|
|
17
|
+
for (const item of newRenderedDataArray) {
|
|
18
|
+
if (item && item[pkField] != null) {
|
|
19
|
+
store._lastRenderedData.set(item[pkField], item);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Also mark any pks that are now null (deleted)
|
|
23
|
+
for (const pk of event.pks) {
|
|
24
|
+
if (!newRenderedDataArray.some(item => item && item[pkField] === pk)) {
|
|
25
|
+
store._lastRenderedData.set(pk, null);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
modelEventEmitter.emit(`${store.modelClass.configKey}::${store.modelClass.modelName}::render`, event);
|
|
29
|
+
store.renderCallbacks.forEach((callback) => {
|
|
30
|
+
try {
|
|
31
|
+
callback();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.warn("Error in model store render callback:", error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
class EventData {
|
|
40
|
+
constructor(ModelClass, pks) {
|
|
41
|
+
this.ModelClass = ModelClass;
|
|
42
|
+
this.pks = Array.isArray(pks) ? pks : [pks];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Single event containing all PKs from an operation
|
|
46
|
+
*/
|
|
47
|
+
static fromOperation(operation) {
|
|
48
|
+
const ModelClass = operation.queryset.ModelClass;
|
|
49
|
+
const pkField = ModelClass.primaryKeyField;
|
|
50
|
+
const pks = operation.instances
|
|
51
|
+
.filter(instance => instance != null && typeof instance === 'object' && pkField in instance)
|
|
52
|
+
.map(instance => instance[pkField]);
|
|
53
|
+
return pks.length > 0 ? new EventData(ModelClass, pks) : null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Single event containing all unique PKs across multiple operations
|
|
57
|
+
*/
|
|
58
|
+
static fromOperations(operations) {
|
|
59
|
+
if (!operations.length)
|
|
60
|
+
return null;
|
|
61
|
+
const ModelClass = operations[0].queryset.ModelClass;
|
|
62
|
+
const pkField = ModelClass.primaryKeyField;
|
|
63
|
+
const uniquePks = new Set();
|
|
64
|
+
for (const op of operations) {
|
|
65
|
+
for (const inst of op.instances) {
|
|
66
|
+
if (inst != null && typeof inst === 'object' && pkField in inst) {
|
|
67
|
+
uniquePks.add(inst[pkField]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return uniquePks.size > 0 ? new EventData(ModelClass, Array.from(uniquePks)) : null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Single event containing all unique PKs from an array of instances
|
|
75
|
+
*/
|
|
76
|
+
static fromInstances(instances, ModelClass) {
|
|
77
|
+
const pkField = ModelClass.primaryKeyField;
|
|
78
|
+
const uniquePks = new Set(instances
|
|
79
|
+
.filter(inst => inst && inst[pkField] != null)
|
|
80
|
+
.map(inst => inst[pkField]));
|
|
81
|
+
return uniquePks.size > 0 ? new EventData(ModelClass, Array.from(uniquePks)) : null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export class ModelStore {
|
|
85
|
+
constructor(modelClass, fetchFn, initialGroundTruth = null, initialOperations = null, options = {}) {
|
|
86
|
+
this.modelClass = modelClass;
|
|
87
|
+
this.fetchFn = fetchFn;
|
|
88
|
+
this.isSyncing = false;
|
|
89
|
+
this.pruneThreshold = options.pruneThreshold || 10;
|
|
90
|
+
this.groundTruthArray = initialGroundTruth || [];
|
|
91
|
+
this.operationsMap = new Map();
|
|
92
|
+
// Handle initial operations if provided
|
|
93
|
+
if (initialOperations && initialOperations.length > 0) {
|
|
94
|
+
this._loadOperations(initialOperations);
|
|
95
|
+
}
|
|
96
|
+
this.modelCache = new Cache("model-cache", {}, this.onHydrated.bind(this));
|
|
97
|
+
this._lastRenderedData = new Map();
|
|
98
|
+
this.renderCallbacks = new Set();
|
|
99
|
+
// Migrate cache entries when temp pks are resolved to real pks
|
|
100
|
+
this._unsubscribeTempPk = onTempPkResolved((tempPk, realPk) => {
|
|
101
|
+
if (this._lastRenderedData.has(tempPk)) {
|
|
102
|
+
const data = this._lastRenderedData.get(tempPk);
|
|
103
|
+
// Update the pk field in the cached data
|
|
104
|
+
if (data && typeof data === 'object') {
|
|
105
|
+
data[this.pkField] = realPk;
|
|
106
|
+
}
|
|
107
|
+
this._lastRenderedData.set(realPk, data);
|
|
108
|
+
this._lastRenderedData.delete(tempPk);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
registerRenderCallback(callback) {
|
|
113
|
+
this.renderCallbacks.add(callback);
|
|
114
|
+
return () => this.renderCallbacks.delete(callback);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Load operations from data and add them to the operations map,
|
|
118
|
+
* reusing existing operations from the registry if they exist
|
|
119
|
+
*/
|
|
120
|
+
_loadOperations(operationsData) {
|
|
121
|
+
operationsData.forEach((opData) => {
|
|
122
|
+
const existingOp = operationRegistry.get(opData.operationId);
|
|
123
|
+
if (existingOp) {
|
|
124
|
+
// If the operation exists in the registry, use it
|
|
125
|
+
this.operationsMap.set(existingOp.operationId, existingOp);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Otherwise just use the plain object data
|
|
129
|
+
this.operationsMap.set(opData.operationId, new Operation(opData, true));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Caching
|
|
134
|
+
get cacheKey() {
|
|
135
|
+
return `${this.modelClass.configKey}::${this.modelClass.modelName}`;
|
|
136
|
+
}
|
|
137
|
+
onHydrated() {
|
|
138
|
+
if (this.groundTruthArray.length === 0 && this.operationsMap.size === 0) {
|
|
139
|
+
let cached = this.modelCache.get(this.cacheKey);
|
|
140
|
+
console.log(`[ModelStore] Hydrated ${this.modelClass.modelName} with ${(cached || []).length} items from cache`);
|
|
141
|
+
if (!isNil(cached) && !isEmpty(cached)) {
|
|
142
|
+
this.setGroundTruth(cached);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
setCache(result) {
|
|
147
|
+
const pkField = this.pkField;
|
|
148
|
+
let nonTempPkItems = [];
|
|
149
|
+
result.forEach((item) => {
|
|
150
|
+
let pk = item[pkField];
|
|
151
|
+
if (typeof pk === "string" && containsTempPk(pk)) {
|
|
152
|
+
pk = replaceTempPks(item[pkField]);
|
|
153
|
+
if (isNil(pk) || isEmpty(trim(pk))) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (item && typeof item.serialize === "function") {
|
|
158
|
+
// Pass includeRepr=true to preserve repr field in cache
|
|
159
|
+
const serializedItem = item.serialize(true);
|
|
160
|
+
serializedItem[pkField] = pk;
|
|
161
|
+
nonTempPkItems.push(serializedItem);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
item[pkField] = pk;
|
|
165
|
+
nonTempPkItems.push(item);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
this.modelCache.set(this.cacheKey, nonTempPkItems);
|
|
169
|
+
}
|
|
170
|
+
clearCache() {
|
|
171
|
+
this.modelCache.delete(this.cacheKey);
|
|
172
|
+
}
|
|
173
|
+
updateCache(items, requestedPks) {
|
|
174
|
+
const pkField = this.pkField;
|
|
175
|
+
let nonTempPkItems = [];
|
|
176
|
+
items.forEach((item) => {
|
|
177
|
+
let pk = item[pkField];
|
|
178
|
+
if (typeof pk === "string" && containsTempPk(pk)) {
|
|
179
|
+
pk = replaceTempPks(item[pkField]);
|
|
180
|
+
if (isNil(pk) || isEmpty(trim(pk))) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
item[pkField] = pk;
|
|
185
|
+
nonTempPkItems.push(item);
|
|
186
|
+
});
|
|
187
|
+
// If rendering ALL items (requestedPks is null), simply replace the cache
|
|
188
|
+
if (requestedPks === null) {
|
|
189
|
+
this.setCache(nonTempPkItems);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// Otherwise, we're rendering specific items - update only those items
|
|
193
|
+
const currentCache = this.modelCache.get(this.cacheKey) || [];
|
|
194
|
+
// Filter out items that were requested but not in the result (they were deleted)
|
|
195
|
+
const filteredCache = currentCache.filter((item) => item &&
|
|
196
|
+
typeof item === "object" &&
|
|
197
|
+
pkField in item &&
|
|
198
|
+
(!requestedPks.has(item[pkField]) ||
|
|
199
|
+
nonTempPkItems.some((newItem) => newItem[pkField] === item[pkField])));
|
|
200
|
+
// Create a map for faster lookups
|
|
201
|
+
const cacheMap = new Map(filteredCache.map((item) => [item[pkField], item]));
|
|
202
|
+
// Add or update items from the result
|
|
203
|
+
for (const item of nonTempPkItems) {
|
|
204
|
+
if (item && typeof item === "object" && pkField in item) {
|
|
205
|
+
cacheMap.set(item[pkField], item);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Update the cache
|
|
209
|
+
const updatedCache = Array.from(cacheMap.values());
|
|
210
|
+
this.setCache(updatedCache);
|
|
211
|
+
}
|
|
212
|
+
// Main modelStore methods
|
|
213
|
+
get operations() {
|
|
214
|
+
return Array.from(this.operationsMap.values());
|
|
215
|
+
}
|
|
216
|
+
get pkField() {
|
|
217
|
+
return this.modelClass.primaryKeyField;
|
|
218
|
+
}
|
|
219
|
+
// Commit optimistic updates
|
|
220
|
+
addOperation(operation) {
|
|
221
|
+
this.operationsMap.set(operation.operationId, operation);
|
|
222
|
+
if (this.operationsMap.size > this.pruneThreshold) {
|
|
223
|
+
this.prune();
|
|
224
|
+
}
|
|
225
|
+
emitEvents(this, EventData.fromOperation(operation));
|
|
226
|
+
}
|
|
227
|
+
updateOperation(operation) {
|
|
228
|
+
if (!this.operationsMap.has(operation.operationId))
|
|
229
|
+
return false;
|
|
230
|
+
this.operationsMap.set(operation.operationId, operation);
|
|
231
|
+
emitEvents(this, EventData.fromOperation(operation));
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
confirm(operation) {
|
|
235
|
+
if (!this.operationsMap.has(operation.operationId))
|
|
236
|
+
return;
|
|
237
|
+
this.operationsMap.set(operation.operationId, operation);
|
|
238
|
+
emitEvents(this, EventData.fromOperation(operation));
|
|
239
|
+
}
|
|
240
|
+
reject(operation) {
|
|
241
|
+
if (!this.operationsMap.has(operation.operationId))
|
|
242
|
+
return;
|
|
243
|
+
this.operationsMap.set(operation.operationId, operation);
|
|
244
|
+
emitEvents(this, EventData.fromOperation(operation));
|
|
245
|
+
}
|
|
246
|
+
setOperations(operations = []) {
|
|
247
|
+
const prevOps = this.operations;
|
|
248
|
+
this.operationsMap.clear();
|
|
249
|
+
operations.forEach((op) => {
|
|
250
|
+
this.operationsMap.set(op.operationId, op);
|
|
251
|
+
});
|
|
252
|
+
const allOps = [...prevOps, ...this.operations];
|
|
253
|
+
emitEvents(this, EventData.fromOperations(allOps));
|
|
254
|
+
}
|
|
255
|
+
// Ground truth data methods
|
|
256
|
+
setGroundTruth(groundTruth) {
|
|
257
|
+
let prevGroundTruth = this.groundTruthArray;
|
|
258
|
+
this.groundTruthArray = Array.isArray(groundTruth) ? groundTruth : [];
|
|
259
|
+
// reactivity - gather all ops
|
|
260
|
+
const allOps = [...prevGroundTruth, ...this.groundTruthArray];
|
|
261
|
+
emitEvents(this, EventData.fromInstances(allOps, this.modelClass));
|
|
262
|
+
}
|
|
263
|
+
getGroundTruth() {
|
|
264
|
+
return this.groundTruthArray;
|
|
265
|
+
}
|
|
266
|
+
get groundTruthPks() {
|
|
267
|
+
const pk = this.pkField;
|
|
268
|
+
return this.groundTruthArray
|
|
269
|
+
.filter((instance) => instance && typeof instance === "object" && pk in instance)
|
|
270
|
+
.map((instance) => instance[pk]);
|
|
271
|
+
}
|
|
272
|
+
addToGroundTruth(instances) {
|
|
273
|
+
if (!Array.isArray(instances) || instances.length === 0)
|
|
274
|
+
return;
|
|
275
|
+
const pkField = this.pkField;
|
|
276
|
+
const pkMap = new Map();
|
|
277
|
+
instances.forEach((inst) => {
|
|
278
|
+
if (inst && typeof inst === "object" && pkField in inst) {
|
|
279
|
+
pkMap.set(inst[pkField], inst);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
console.warn(`[ModelStore ${this.modelClass.modelName}] Skipping invalid instance in addToGroundTruth:`, inst);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
if (pkMap.size === 0)
|
|
286
|
+
return;
|
|
287
|
+
const updatedGroundTruth = [];
|
|
288
|
+
const processedPks = new Set();
|
|
289
|
+
const checkpointInstances = []; // Track instances that need CHECKPOINT operations
|
|
290
|
+
for (const existingItem of this.groundTruthArray) {
|
|
291
|
+
if (!existingItem ||
|
|
292
|
+
typeof existingItem !== "object" ||
|
|
293
|
+
!(pkField in existingItem)) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const pk = existingItem[pkField];
|
|
297
|
+
if (pkMap.has(pk)) {
|
|
298
|
+
const updatedInstance = { ...existingItem, ...pkMap.get(pk) };
|
|
299
|
+
updatedGroundTruth.push(updatedInstance);
|
|
300
|
+
processedPks.add(pk);
|
|
301
|
+
// This instance already existed - add it to checkpoint list
|
|
302
|
+
checkpointInstances.push(updatedInstance);
|
|
303
|
+
pkMap.delete(pk);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
updatedGroundTruth.push(existingItem);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Add completely new instances (these don't need checkpoint operations)
|
|
310
|
+
updatedGroundTruth.push(...Array.from(pkMap.values()));
|
|
311
|
+
this.groundTruthArray = updatedGroundTruth;
|
|
312
|
+
// Create CHECKPOINT operation for instances that already existed
|
|
313
|
+
if (checkpointInstances.length > 0) {
|
|
314
|
+
const checkpointOperation = new Operation({
|
|
315
|
+
operationId: `checkpoint_${Date.now()}_${Math.random()
|
|
316
|
+
.toString(36)
|
|
317
|
+
.substr(2, 9)}`,
|
|
318
|
+
type: Type.CHECKPOINT,
|
|
319
|
+
instances: checkpointInstances,
|
|
320
|
+
status: Status.CONFIRMED,
|
|
321
|
+
timestamp: Date.now(),
|
|
322
|
+
queryset: this.modelClass.objects.all(),
|
|
323
|
+
});
|
|
324
|
+
this.operationsMap.set(checkpointOperation.operationId, checkpointOperation);
|
|
325
|
+
console.log(`[ModelStore ${this.modelClass.modelName}] Created CHECKPOINT operation for ${checkpointInstances.length} existing instances`);
|
|
326
|
+
}
|
|
327
|
+
// reactivity - use all the newly added instances (both new and updated)
|
|
328
|
+
emitEvents(this, EventData.fromInstances([...checkpointInstances, ...Array.from(pkMap.values())], this.modelClass));
|
|
329
|
+
}
|
|
330
|
+
_filteredOperations(pks, operations) {
|
|
331
|
+
if (!pks)
|
|
332
|
+
return operations;
|
|
333
|
+
const pkField = this.pkField;
|
|
334
|
+
let filteredOps = [];
|
|
335
|
+
for (const op of operations) {
|
|
336
|
+
let relevantInstances = op.instances.filter((instance) => pks.has(instance[pkField] || instance));
|
|
337
|
+
if (relevantInstances.length > 0) {
|
|
338
|
+
filteredOps.push({
|
|
339
|
+
operationId: op.operationId,
|
|
340
|
+
instances: relevantInstances,
|
|
341
|
+
timestamp: op.timestamp,
|
|
342
|
+
queryset: op.queryset,
|
|
343
|
+
type: op.type,
|
|
344
|
+
status: op.status,
|
|
345
|
+
args: op.args,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return filteredOps;
|
|
350
|
+
}
|
|
351
|
+
_filteredGroundTruth(pks, groundTruthArray) {
|
|
352
|
+
const pkField = this.pkField;
|
|
353
|
+
let groundTruthMap = new Map();
|
|
354
|
+
for (const instance of groundTruthArray) {
|
|
355
|
+
if (!instance || typeof instance !== "object" || !(pkField in instance)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const pk = instance[pkField];
|
|
359
|
+
if (!pks || pks.has(pk)) {
|
|
360
|
+
groundTruthMap.set(pk, instance);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return groundTruthMap;
|
|
364
|
+
}
|
|
365
|
+
applyOperation(operation, currentInstances) {
|
|
366
|
+
const pkField = this.pkField;
|
|
367
|
+
for (const instance of operation.instances) {
|
|
368
|
+
if (!instance || typeof instance !== "object" || !(pkField in instance)) {
|
|
369
|
+
console.warn(`[ModelStore ${this.modelClass.modelName}] Skipping instance ${instance} in operation ${operation.operationId} during applyOperation due to missing PK field '${String(pkField)}' or invalid format.`);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
let pk = instance[pkField];
|
|
373
|
+
switch (operation.type) {
|
|
374
|
+
case Type.CREATE:
|
|
375
|
+
case Type.BULK_CREATE:
|
|
376
|
+
if (!currentInstances.has(pk)) {
|
|
377
|
+
currentInstances.set(pk, instance);
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
case Type.CHECKPOINT:
|
|
381
|
+
case Type.UPDATE_INSTANCE:
|
|
382
|
+
case Type.UPDATE: {
|
|
383
|
+
const existing = currentInstances.get(pk);
|
|
384
|
+
if (existing) {
|
|
385
|
+
currentInstances.set(pk, { ...existing, ...instance });
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
const wasDeletedLocally = this.operations.some((op) => op.type === Type.DELETE &&
|
|
389
|
+
op.status !== Status.REJECTED &&
|
|
390
|
+
op.instances.some((inst) => inst && inst[pkField] === pk));
|
|
391
|
+
if (!wasDeletedLocally) {
|
|
392
|
+
currentInstances.set(pk, instance);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
case Type.DELETE_INSTANCE:
|
|
398
|
+
case Type.DELETE:
|
|
399
|
+
currentInstances.delete(pk);
|
|
400
|
+
break;
|
|
401
|
+
default:
|
|
402
|
+
console.error(`[ModelStore ${this.modelClass.modelName}] Unknown operation type: ${operation.type}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return currentInstances;
|
|
406
|
+
}
|
|
407
|
+
getTrimmedOperations() {
|
|
408
|
+
const twoMinutesAgo = Date.now() - 1000 * 60 * 2;
|
|
409
|
+
return this.operations.filter((operation) => operation.timestamp > twoMinutesAgo);
|
|
410
|
+
}
|
|
411
|
+
getInflightOperations() {
|
|
412
|
+
return this.operations.filter((operation) => operation.status != Status.CONFIRMED &&
|
|
413
|
+
operation.status != Status.REJECTED);
|
|
414
|
+
}
|
|
415
|
+
// Pruning
|
|
416
|
+
prune() {
|
|
417
|
+
let renderedPks = this.render(null, false);
|
|
418
|
+
this.setGroundTruth(renderedPks);
|
|
419
|
+
this.setOperations(this.getInflightOperations());
|
|
420
|
+
this.setCache(renderedPks);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Prune model instances that aren't referenced by any queryset store.
|
|
424
|
+
* This prevents unbounded cache growth from included nested models.
|
|
425
|
+
*
|
|
426
|
+
* @param {QuerysetStoreRegistry} querysetStoreRegistry - The registry to check for queryset references
|
|
427
|
+
*/
|
|
428
|
+
pruneUnreferencedInstances(querysetStoreRegistry) {
|
|
429
|
+
const pkField = this.pkField;
|
|
430
|
+
const modelName = this.modelClass.modelName;
|
|
431
|
+
// Collect all PKs that are needed by ANY queryset (permanent or temporary)
|
|
432
|
+
const neededPks = new Set();
|
|
433
|
+
for (const [semanticKey, store] of querysetStoreRegistry._stores.entries()) {
|
|
434
|
+
// Check top-level PKs (groundTruthPks)
|
|
435
|
+
if (store.modelClass.modelName === modelName) {
|
|
436
|
+
store.groundTruthPks.forEach(pk => neededPks.add(pk));
|
|
437
|
+
}
|
|
438
|
+
// Check included PKs (nested models)
|
|
439
|
+
if (store.includedPks.has(modelName)) {
|
|
440
|
+
const includedPkSet = store.includedPks.get(modelName);
|
|
441
|
+
includedPkSet.forEach(pk => neededPks.add(pk));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// Filter ground truth to only keep needed PKs
|
|
445
|
+
const filteredGroundTruth = this.groundTruthArray.filter(instance => {
|
|
446
|
+
if (!instance || typeof instance !== 'object' || !(pkField in instance)) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
return neededPks.has(instance[pkField]);
|
|
450
|
+
});
|
|
451
|
+
const removedCount = this.groundTruthArray.length - filteredGroundTruth.length;
|
|
452
|
+
if (removedCount > 0) {
|
|
453
|
+
console.log(`[ModelStore ${modelName}] Pruned ${removedCount} unreferenced instances (${filteredGroundTruth.length} remaining)`);
|
|
454
|
+
this.groundTruthArray = filteredGroundTruth;
|
|
455
|
+
// Update the cache to reflect the pruned data
|
|
456
|
+
this.setCache(filteredGroundTruth);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Render methods
|
|
460
|
+
render(pks = null, optimistic = true, useCache = true) {
|
|
461
|
+
// When rendering specific pks, check cache first
|
|
462
|
+
if (useCache && pks !== null) {
|
|
463
|
+
const pksArray = Array.isArray(pks) ? pks : [pks];
|
|
464
|
+
// Resolve temp pks to real pks for cache lookup
|
|
465
|
+
const resolvedPks = pksArray.map(pk => resolveToRealPk(pk));
|
|
466
|
+
const uncachedPks = [];
|
|
467
|
+
for (const pk of resolvedPks) {
|
|
468
|
+
if (!this._lastRenderedData.has(pk)) {
|
|
469
|
+
uncachedPks.push(pk);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// All pks were cached - return from cache
|
|
473
|
+
if (uncachedPks.length === 0) {
|
|
474
|
+
return this._renderFromCache(resolvedPks);
|
|
475
|
+
}
|
|
476
|
+
// Some or all pks need fresh render
|
|
477
|
+
const pkField = this.pkField;
|
|
478
|
+
if (uncachedPks.length < resolvedPks.length) {
|
|
479
|
+
// Partial cache hit - get cached and render uncached
|
|
480
|
+
const cachedPks = resolvedPks.filter(pk => this._lastRenderedData.has(pk));
|
|
481
|
+
const cached = this._renderFromCache(cachedPks);
|
|
482
|
+
const fresh = this._renderFresh(uncachedPks, optimistic);
|
|
483
|
+
// Update cache for freshly rendered items
|
|
484
|
+
for (const item of fresh) {
|
|
485
|
+
if (item && item[pkField] != null) {
|
|
486
|
+
this._lastRenderedData.set(item[pkField], item);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return [...cached, ...fresh];
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
// All pks uncached - render fresh and update cache
|
|
493
|
+
const fresh = this._renderFresh(resolvedPks, optimistic);
|
|
494
|
+
// Update cache for freshly rendered items
|
|
495
|
+
for (const item of fresh) {
|
|
496
|
+
if (item && item[pkField] != null) {
|
|
497
|
+
this._lastRenderedData.set(item[pkField], item);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return fresh;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// Full render (no cache, useCache=false, or pks=null for all)
|
|
504
|
+
return this._renderFresh(pks, optimistic);
|
|
505
|
+
}
|
|
506
|
+
_renderFromCache(pks) {
|
|
507
|
+
// Returns cached rendered data for the given pks
|
|
508
|
+
// This method can be spied on in tests to track cache hits
|
|
509
|
+
return pks
|
|
510
|
+
.map(pk => this._lastRenderedData.get(pk))
|
|
511
|
+
.filter(item => item !== null && item !== undefined);
|
|
512
|
+
}
|
|
513
|
+
_renderFresh(pks = null, optimistic = true) {
|
|
514
|
+
const pksSet = pks === null
|
|
515
|
+
? null
|
|
516
|
+
: pks instanceof Set
|
|
517
|
+
? pks
|
|
518
|
+
: new Set(Array.isArray(pks) ? pks : [pks]);
|
|
519
|
+
const renderedInstancesMap = this._filteredGroundTruth(pksSet, this.groundTruthArray);
|
|
520
|
+
const relevantOperations = this._filteredOperations(pksSet, this.operations);
|
|
521
|
+
for (const op of relevantOperations) {
|
|
522
|
+
if (op.status !== Status.REJECTED &&
|
|
523
|
+
(optimistic || op.status === Status.CONFIRMED)) {
|
|
524
|
+
this.applyOperation(op, renderedInstancesMap);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
let result = Array.from(renderedInstancesMap.values());
|
|
528
|
+
if (pks)
|
|
529
|
+
this.updateCache(result, pksSet);
|
|
530
|
+
if (isNil(pks))
|
|
531
|
+
this.setCache(result);
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
async sync(pks = null) {
|
|
535
|
+
const storeIdForLog = this.modelClass.modelName;
|
|
536
|
+
if (this.isSyncing)
|
|
537
|
+
return;
|
|
538
|
+
this.isSyncing = true;
|
|
539
|
+
try {
|
|
540
|
+
const currentPks = pks || this.groundTruthPks;
|
|
541
|
+
if (currentPks.length === 0) {
|
|
542
|
+
const trimmedOps = this.getTrimmedOperations();
|
|
543
|
+
this.setOperations(trimmedOps);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const newGroundTruth = await this.fetchFn({
|
|
547
|
+
pks: currentPks,
|
|
548
|
+
modelClass: this.modelClass,
|
|
549
|
+
});
|
|
550
|
+
if (pks) {
|
|
551
|
+
this.addToGroundTruth(newGroundTruth);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
this.setGroundTruth(newGroundTruth);
|
|
555
|
+
const trimmedOps = this.getTrimmedOperations();
|
|
556
|
+
this.setOperations(trimmedOps);
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
console.error(`[ModelStore ${storeIdForLog}] Failed to sync ground truth:`, error);
|
|
560
|
+
}
|
|
561
|
+
finally {
|
|
562
|
+
this.isSyncing = false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|