@statezero/core 0.2.36 → 0.2.38
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/adaptors/vue/components/LayoutRenderer.js +166 -0
- package/dist/adaptors/vue/components/defaults/AlertElement.js +31 -0
- package/dist/adaptors/vue/components/defaults/DisplayElement.js +44 -0
- package/dist/adaptors/vue/components/defaults/DividerElement.js +10 -0
- package/dist/adaptors/vue/components/defaults/ErrorBlock.js +24 -0
- package/dist/adaptors/vue/components/defaults/GroupElement.js +41 -0
- package/dist/adaptors/vue/components/defaults/LabelElement.js +21 -0
- package/dist/adaptors/vue/components/defaults/TabsElement.js +38 -0
- package/package.json +7 -6
- package/dist/actions/backend1/django_app/calculate-hash.d.ts +0 -57
- package/dist/actions/backend1/django_app/calculate-hash.js +0 -80
- package/dist/actions/backend1/django_app/calculate-hash.schema.json +0 -148
- package/dist/actions/backend1/django_app/get-current-username.d.ts +0 -29
- package/dist/actions/backend1/django_app/get-current-username.js +0 -65
- package/dist/actions/backend1/django_app/get-current-username.schema.json +0 -47
- package/dist/actions/backend1/django_app/get-server-status.d.ts +0 -38
- package/dist/actions/backend1/django_app/get-server-status.js +0 -68
- package/dist/actions/backend1/django_app/get-server-status.schema.json +0 -93
- package/dist/actions/backend1/django_app/get-user-info.d.ts +0 -44
- package/dist/actions/backend1/django_app/get-user-info.js +0 -70
- package/dist/actions/backend1/django_app/get-user-info.schema.json +0 -127
- package/dist/actions/backend1/django_app/index.d.ts +0 -1
- package/dist/actions/backend1/django_app/index.js +0 -6
- package/dist/actions/backend1/django_app/process-data.d.ts +0 -51
- package/dist/actions/backend1/django_app/process-data.js +0 -78
- package/dist/actions/backend1/django_app/process-data.schema.json +0 -117
- package/dist/actions/backend1/django_app/send-notification.d.ts +0 -55
- package/dist/actions/backend1/django_app/send-notification.js +0 -81
- package/dist/actions/backend1/django_app/send-notification.schema.json +0 -175
- package/dist/actions/backend1/index.d.ts +0 -1
- package/dist/actions/backend1/index.js +0 -1
- package/dist/actions/default/django_app/calculate-hash.d.ts +0 -57
- package/dist/actions/default/django_app/calculate-hash.js +0 -80
- package/dist/actions/default/django_app/calculate-hash.schema.json +0 -148
- package/dist/actions/default/django_app/get-current-username.d.ts +0 -29
- package/dist/actions/default/django_app/get-current-username.js +0 -65
- package/dist/actions/default/django_app/get-current-username.schema.json +0 -47
- package/dist/actions/default/django_app/get-server-status.d.ts +0 -38
- package/dist/actions/default/django_app/get-server-status.js +0 -68
- package/dist/actions/default/django_app/get-server-status.schema.json +0 -93
- package/dist/actions/default/django_app/get-user-info.d.ts +0 -44
- package/dist/actions/default/django_app/get-user-info.js +0 -70
- package/dist/actions/default/django_app/get-user-info.schema.json +0 -127
- package/dist/actions/default/django_app/index.d.ts +0 -1
- package/dist/actions/default/django_app/index.js +0 -6
- package/dist/actions/default/django_app/process-data.d.ts +0 -51
- package/dist/actions/default/django_app/process-data.js +0 -78
- package/dist/actions/default/django_app/process-data.schema.json +0 -117
- package/dist/actions/default/django_app/send-notification.d.ts +0 -55
- package/dist/actions/default/django_app/send-notification.js +0 -81
- package/dist/actions/default/django_app/send-notification.schema.json +0 -175
- package/dist/actions/default/index.d.ts +0 -1
- package/dist/actions/default/index.js +0 -1
- package/dist/actions/index.d.ts +0 -1
- package/dist/actions/index.js +0 -5
- package/dist/adaptors/react/composables.d.ts +0 -1
- package/dist/adaptors/react/composables.js +0 -4
- package/dist/adaptors/react/index.d.ts +0 -1
- package/dist/adaptors/react/index.js +0 -1
- package/dist/adaptors/vue/components/defaults/index.d.ts +0 -7
- package/dist/adaptors/vue/components/defaults/index.js +0 -31
- package/dist/adaptors/vue/components/index.d.ts +0 -1
- package/dist/adaptors/vue/components/index.js +0 -7
- package/dist/adaptors/vue/composables.d.ts +0 -2
- package/dist/adaptors/vue/composables.js +0 -44
- package/dist/adaptors/vue/index.d.ts +0 -3
- package/dist/adaptors/vue/index.js +0 -4
- package/dist/adaptors/vue/reactivity.d.ts +0 -18
- package/dist/adaptors/vue/reactivity.js +0 -132
- package/dist/cli/commands/sync.d.ts +0 -6
- package/dist/cli/commands/sync.js +0 -30
- package/dist/cli/commands/syncActions.d.ts +0 -46
- package/dist/cli/commands/syncActions.js +0 -717
- package/dist/cli/commands/syncModels.d.ts +0 -132
- package/dist/cli/commands/syncModels.js +0 -1120
- package/dist/cli/configFileLoader.d.ts +0 -10
- package/dist/cli/configFileLoader.js +0 -85
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -22
- package/dist/config.d.ts +0 -57
- package/dist/config.js +0 -273
- package/dist/core/eventReceivers.d.ts +0 -185
- package/dist/core/eventReceivers.js +0 -266
- package/dist/core/utils.d.ts +0 -8
- package/dist/core/utils.js +0 -62
- package/dist/errorHandler.d.ts +0 -21
- package/dist/errorHandler.js +0 -27
- package/dist/filtering/localFiltering.d.ts +0 -110
- package/dist/filtering/localFiltering.js +0 -1080
- package/dist/flavours/django/dates.d.ts +0 -34
- package/dist/flavours/django/dates.js +0 -113
- package/dist/flavours/django/errors.d.ts +0 -138
- package/dist/flavours/django/errors.js +0 -195
- package/dist/flavours/django/f.d.ts +0 -6
- package/dist/flavours/django/f.js +0 -91
- package/dist/flavours/django/files.d.ts +0 -62
- package/dist/flavours/django/files.js +0 -355
- package/dist/flavours/django/makeApiCall.d.ts +0 -36
- package/dist/flavours/django/makeApiCall.js +0 -169
- package/dist/flavours/django/manager.d.ts +0 -204
- package/dist/flavours/django/manager.js +0 -222
- package/dist/flavours/django/model.d.ts +0 -137
- package/dist/flavours/django/model.js +0 -366
- package/dist/flavours/django/operationFactory.d.ts +0 -73
- package/dist/flavours/django/operationFactory.js +0 -248
- package/dist/flavours/django/q.d.ts +0 -70
- package/dist/flavours/django/q.js +0 -43
- package/dist/flavours/django/queryExecutor.d.ts +0 -149
- package/dist/flavours/django/queryExecutor.js +0 -590
- package/dist/flavours/django/querySet.d.ts +0 -301
- package/dist/flavours/django/querySet.js +0 -736
- package/dist/flavours/django/serializers.d.ts +0 -39
- package/dist/flavours/django/serializers.js +0 -296
- package/dist/flavours/django/tempPk.d.ts +0 -31
- package/dist/flavours/django/tempPk.js +0 -92
- package/dist/flavours/django/utils.d.ts +0 -19
- package/dist/flavours/django/utils.js +0 -29
- package/dist/index.d.ts +0 -46
- package/dist/index.js +0 -48
- package/dist/models/backend1/django_app/comprehensivemodel.d.ts +0 -894
- package/dist/models/backend1/django_app/comprehensivemodel.js +0 -71
- package/dist/models/backend1/django_app/comprehensivemodel.schema.json +0 -870
- package/dist/models/backend1/django_app/custompkmodel.d.ts +0 -92
- package/dist/models/backend1/django_app/custompkmodel.js +0 -69
- package/dist/models/backend1/django_app/custompkmodel.schema.json +0 -71
- package/dist/models/backend1/django_app/dailyrate.d.ts +0 -230
- package/dist/models/backend1/django_app/dailyrate.js +0 -71
- package/dist/models/backend1/django_app/dailyrate.schema.json +0 -212
- package/dist/models/backend1/django_app/deepmodellevel1.d.ts +0 -140
- package/dist/models/backend1/django_app/deepmodellevel1.js +0 -72
- package/dist/models/backend1/django_app/deepmodellevel1.schema.json +0 -114
- package/dist/models/backend1/django_app/deepmodellevel2.d.ts +0 -118
- package/dist/models/backend1/django_app/deepmodellevel2.js +0 -71
- package/dist/models/backend1/django_app/deepmodellevel2.schema.json +0 -92
- package/dist/models/backend1/django_app/deepmodellevel3.d.ts +0 -92
- package/dist/models/backend1/django_app/deepmodellevel3.js +0 -69
- package/dist/models/backend1/django_app/deepmodellevel3.schema.json +0 -69
- package/dist/models/backend1/django_app/dummymodel.d.ts +0 -134
- package/dist/models/backend1/django_app/dummymodel.js +0 -71
- package/dist/models/backend1/django_app/dummymodel.schema.json +0 -109
- package/dist/models/backend1/django_app/dummyrelatedmodel.d.ts +0 -92
- package/dist/models/backend1/django_app/dummyrelatedmodel.js +0 -69
- package/dist/models/backend1/django_app/dummyrelatedmodel.schema.json +0 -69
- package/dist/models/backend1/django_app/filetest.d.ts +0 -140
- package/dist/models/backend1/django_app/filetest.js +0 -69
- package/dist/models/backend1/django_app/filetest.schema.json +0 -111
- package/dist/models/backend1/django_app/index.d.ts +0 -1
- package/dist/models/backend1/django_app/index.js +0 -21
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.d.ts +0 -118
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.js +0 -71
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.schema.json +0 -94
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.d.ts +0 -118
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.js +0 -71
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.schema.json +0 -94
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.d.ts +0 -134
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.js +0 -71
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.schema.json +0 -112
- package/dist/models/backend1/django_app/modelwithcustompkrelation.d.ts +0 -118
- package/dist/models/backend1/django_app/modelwithcustompkrelation.js +0 -71
- package/dist/models/backend1/django_app/modelwithcustompkrelation.schema.json +0 -93
- package/dist/models/backend1/django_app/modelwithrestrictedfields.d.ts +0 -134
- package/dist/models/backend1/django_app/modelwithrestrictedfields.js +0 -71
- package/dist/models/backend1/django_app/modelwithrestrictedfields.schema.json +0 -111
- package/dist/models/backend1/django_app/namefiltercustompkmodel.d.ts +0 -92
- package/dist/models/backend1/django_app/namefiltercustompkmodel.js +0 -69
- package/dist/models/backend1/django_app/namefiltercustompkmodel.schema.json +0 -71
- package/dist/models/backend1/django_app/order.d.ts +0 -220
- package/dist/models/backend1/django_app/order.js +0 -71
- package/dist/models/backend1/django_app/order.schema.json +0 -203
- package/dist/models/backend1/django_app/orderitem.d.ts +0 -172
- package/dist/models/backend1/django_app/orderitem.js +0 -72
- package/dist/models/backend1/django_app/orderitem.schema.json +0 -149
- package/dist/models/backend1/django_app/product.d.ts +0 -254
- package/dist/models/backend1/django_app/product.js +0 -71
- package/dist/models/backend1/django_app/product.schema.json +0 -277
- package/dist/models/backend1/django_app/productcategory.d.ts +0 -92
- package/dist/models/backend1/django_app/productcategory.js +0 -69
- package/dist/models/backend1/django_app/productcategory.schema.json +0 -70
- package/dist/models/backend1/django_app/rateplan.d.ts +0 -92
- package/dist/models/backend1/django_app/rateplan.js +0 -69
- package/dist/models/backend1/django_app/rateplan.schema.json +0 -70
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.d.ts +0 -108
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.js +0 -69
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.schema.json +0 -87
- package/dist/models/backend1/fileobject.d.ts +0 -4
- package/dist/models/backend1/fileobject.js +0 -9
- package/dist/models/backend1/index.d.ts +0 -2
- package/dist/models/backend1/index.js +0 -2
- package/dist/models/default/django_app/comprehensivemodel.d.ts +0 -894
- package/dist/models/default/django_app/comprehensivemodel.js +0 -71
- package/dist/models/default/django_app/comprehensivemodel.schema.json +0 -870
- package/dist/models/default/django_app/custompkmodel.d.ts +0 -92
- package/dist/models/default/django_app/custompkmodel.js +0 -69
- package/dist/models/default/django_app/custompkmodel.schema.json +0 -71
- package/dist/models/default/django_app/dailyrate.d.ts +0 -230
- package/dist/models/default/django_app/dailyrate.js +0 -71
- package/dist/models/default/django_app/dailyrate.schema.json +0 -212
- package/dist/models/default/django_app/deepmodellevel1.d.ts +0 -128
- package/dist/models/default/django_app/deepmodellevel1.js +0 -72
- package/dist/models/default/django_app/deepmodellevel1.schema.json +0 -102
- package/dist/models/default/django_app/deepmodellevel2.d.ts +0 -106
- package/dist/models/default/django_app/deepmodellevel2.js +0 -71
- package/dist/models/default/django_app/deepmodellevel2.schema.json +0 -80
- package/dist/models/default/django_app/deepmodellevel3.d.ts +0 -80
- package/dist/models/default/django_app/deepmodellevel3.js +0 -69
- package/dist/models/default/django_app/deepmodellevel3.schema.json +0 -57
- package/dist/models/default/django_app/dummymodel.d.ts +0 -122
- package/dist/models/default/django_app/dummymodel.js +0 -71
- package/dist/models/default/django_app/dummymodel.schema.json +0 -97
- package/dist/models/default/django_app/dummyrelatedmodel.d.ts +0 -80
- package/dist/models/default/django_app/dummyrelatedmodel.js +0 -69
- package/dist/models/default/django_app/dummyrelatedmodel.schema.json +0 -57
- package/dist/models/default/django_app/filetest.d.ts +0 -128
- package/dist/models/default/django_app/filetest.js +0 -69
- package/dist/models/default/django_app/filetest.schema.json +0 -99
- package/dist/models/default/django_app/index.d.ts +0 -1
- package/dist/models/default/django_app/index.js +0 -21
- package/dist/models/default/django_app/m2mdepthtestlevel1.d.ts +0 -118
- package/dist/models/default/django_app/m2mdepthtestlevel1.js +0 -71
- package/dist/models/default/django_app/m2mdepthtestlevel1.schema.json +0 -94
- package/dist/models/default/django_app/m2mdepthtestlevel2.d.ts +0 -118
- package/dist/models/default/django_app/m2mdepthtestlevel2.js +0 -71
- package/dist/models/default/django_app/m2mdepthtestlevel2.schema.json +0 -94
- package/dist/models/default/django_app/m2mdepthtestlevel3.d.ts +0 -134
- package/dist/models/default/django_app/m2mdepthtestlevel3.js +0 -71
- package/dist/models/default/django_app/m2mdepthtestlevel3.schema.json +0 -112
- package/dist/models/default/django_app/modelwithcustompkrelation.d.ts +0 -118
- package/dist/models/default/django_app/modelwithcustompkrelation.js +0 -71
- package/dist/models/default/django_app/modelwithcustompkrelation.schema.json +0 -93
- package/dist/models/default/django_app/modelwithrestrictedfields.d.ts +0 -134
- package/dist/models/default/django_app/modelwithrestrictedfields.js +0 -71
- package/dist/models/default/django_app/modelwithrestrictedfields.schema.json +0 -111
- package/dist/models/default/django_app/namefiltercustompkmodel.d.ts +0 -92
- package/dist/models/default/django_app/namefiltercustompkmodel.js +0 -69
- package/dist/models/default/django_app/namefiltercustompkmodel.schema.json +0 -71
- package/dist/models/default/django_app/order.d.ts +0 -220
- package/dist/models/default/django_app/order.js +0 -71
- package/dist/models/default/django_app/order.schema.json +0 -203
- package/dist/models/default/django_app/orderitem.d.ts +0 -172
- package/dist/models/default/django_app/orderitem.js +0 -72
- package/dist/models/default/django_app/orderitem.schema.json +0 -149
- package/dist/models/default/django_app/product.d.ts +0 -254
- package/dist/models/default/django_app/product.js +0 -71
- package/dist/models/default/django_app/product.schema.json +0 -277
- package/dist/models/default/django_app/productcategory.d.ts +0 -92
- package/dist/models/default/django_app/productcategory.js +0 -69
- package/dist/models/default/django_app/productcategory.schema.json +0 -70
- package/dist/models/default/django_app/rateplan.d.ts +0 -92
- package/dist/models/default/django_app/rateplan.js +0 -69
- package/dist/models/default/django_app/rateplan.schema.json +0 -70
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.d.ts +0 -108
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.js +0 -69
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.schema.json +0 -87
- package/dist/models/default/fileobject.d.ts +0 -4
- package/dist/models/default/fileobject.js +0 -9
- package/dist/models/default/index.d.ts +0 -2
- package/dist/models/default/index.js +0 -2
- package/dist/models/index.d.ts +0 -1
- package/dist/models/index.js +0 -5
- package/dist/react-entry.d.ts +0 -2
- package/dist/react-entry.js +0 -2
- package/dist/reactiveAdaptor.d.ts +0 -24
- package/dist/reactiveAdaptor.js +0 -38
- package/dist/reset.d.ts +0 -15
- package/dist/reset.js +0 -97
- package/dist/setup.d.ts +0 -15
- package/dist/setup.js +0 -33
- package/dist/syncEngine/cache/cache.d.ts +0 -75
- package/dist/syncEngine/cache/cache.js +0 -355
- package/dist/syncEngine/metrics/metricOptCalcs.d.ts +0 -79
- package/dist/syncEngine/metrics/metricOptCalcs.js +0 -284
- package/dist/syncEngine/registries/metricRegistry.d.ts +0 -58
- package/dist/syncEngine/registries/metricRegistry.js +0 -171
- package/dist/syncEngine/registries/modelStoreRegistry.d.ts +0 -11
- package/dist/syncEngine/registries/modelStoreRegistry.js +0 -63
- package/dist/syncEngine/registries/querysetStoreGraph.d.ts +0 -41
- package/dist/syncEngine/registries/querysetStoreGraph.js +0 -174
- package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +0 -72
- package/dist/syncEngine/registries/querysetStoreRegistry.js +0 -335
- package/dist/syncEngine/stores/metricStore.d.ts +0 -55
- package/dist/syncEngine/stores/metricStore.js +0 -222
- package/dist/syncEngine/stores/modelStore.d.ts +0 -53
- package/dist/syncEngine/stores/modelStore.js +0 -565
- package/dist/syncEngine/stores/operation.d.ts +0 -139
- package/dist/syncEngine/stores/operation.js +0 -291
- package/dist/syncEngine/stores/operationEventHandlers.d.ts +0 -8
- package/dist/syncEngine/stores/operationEventHandlers.js +0 -322
- package/dist/syncEngine/stores/querysetStore.d.ts +0 -60
- package/dist/syncEngine/stores/querysetStore.js +0 -294
- package/dist/syncEngine/stores/reactivity.d.ts +0 -3
- package/dist/syncEngine/stores/reactivity.js +0 -4
- package/dist/syncEngine/stores/utils.d.ts +0 -14
- package/dist/syncEngine/stores/utils.js +0 -32
- package/dist/syncEngine/sync.d.ts +0 -46
- package/dist/syncEngine/sync.js +0 -389
- package/dist/testing.d.ts +0 -63
- package/dist/testing.js +0 -175
- package/dist/vue-entry.d.ts +0 -15
- package/dist/vue-entry.js +0 -7
- /package/{src → dist}/adaptors/vue/components/layout.css +0 -0
- /package/{src → dist}/adaptors/vue/components/layout.tailwind.css +0 -0
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import { operationEvents, Status, Type, operationRegistry, OperationMembership } from './operation.js';
|
|
2
|
-
import { modelStoreRegistry } from '../registries/modelStoreRegistry.js';
|
|
3
|
-
import { querysetStoreRegistry } from '../registries/querysetStoreRegistry.js';
|
|
4
|
-
import { metricRegistry } from '../registries/metricRegistry.js';
|
|
5
|
-
import { getFingerprint } from './utils.js';
|
|
6
|
-
import { QuerySet } from '../../flavours/django/querySet.js';
|
|
7
|
-
import { isEqual, isNil } from 'lodash-es';
|
|
8
|
-
import hash from 'object-hash';
|
|
9
|
-
import { filter } from '../../filtering/localFiltering.js';
|
|
10
|
-
/**
|
|
11
|
-
* Evaluates and routes a CREATE operation to querysets, tracking membership state.
|
|
12
|
-
*
|
|
13
|
-
* For each queryset:
|
|
14
|
-
* - If offset > 0: mark as MAYBE (can't determine position based on slicing)
|
|
15
|
-
* - If item doesn't match filter: mark DEFINITELY_NO
|
|
16
|
-
* - If item matches filter AND (no limit OR count < limit): mark DEFINITELY_YES
|
|
17
|
-
* - If item matches filter AND count >= limit: mark MAYBE (ordering determines inclusion)
|
|
18
|
-
*
|
|
19
|
-
* @param {Operation} operation - The CREATE operation to route
|
|
20
|
-
* @param {Function} applyAction - Function to apply the operation to a store
|
|
21
|
-
*/
|
|
22
|
-
function routeCreateOperation(operation, applyAction) {
|
|
23
|
-
const modelClass = operation.queryset.ModelClass;
|
|
24
|
-
const instances = operation.instances;
|
|
25
|
-
Array.from(querysetStoreRegistry._stores.entries()).forEach(([semanticKey, store]) => {
|
|
26
|
-
if (store.modelClass !== modelClass)
|
|
27
|
-
return;
|
|
28
|
-
const serializerOptions = store.queryset?._serializerOptions || {};
|
|
29
|
-
const { offset, limit } = serializerOptions;
|
|
30
|
-
// Offset > 0: can't determine position based on slicing
|
|
31
|
-
if (offset != null && offset > 0) {
|
|
32
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.MAYBE);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
// Evaluate if instances match this queryset's filter
|
|
36
|
-
const ast = store.queryset.build();
|
|
37
|
-
const matchingInstances = filter(instances, ast, modelClass, false);
|
|
38
|
-
if (matchingInstances.length === 0) {
|
|
39
|
-
// No instances match - mark DEFINITELY_NO (no need to sync this queryset)
|
|
40
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_NO);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
// Item matches filter - check limit
|
|
44
|
-
if (limit != null) {
|
|
45
|
-
const currentCount = store.groundTruthPks?.length || 0;
|
|
46
|
-
// At capacity: check if ordering could affect position
|
|
47
|
-
if (currentCount >= limit) {
|
|
48
|
-
// Check explicit ordering on queryset OR implicit ordering from Django Meta
|
|
49
|
-
const hasExplicitOrdering = store.queryset._orderBy && store.queryset._orderBy.length > 0;
|
|
50
|
-
const hasImplicitOrdering = (modelClass.schema?.default_ordering?.length || 0) > 0;
|
|
51
|
-
if (hasExplicitOrdering || hasImplicitOrdering) {
|
|
52
|
-
// With ordering, new item could displace existing items - can't know without server
|
|
53
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.MAYBE);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
// No ordering - new items go at end, won't be in first N
|
|
57
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_NO);
|
|
58
|
-
}
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
// Room for some or all items - continue to DEFINITELY_YES below
|
|
62
|
-
}
|
|
63
|
-
// Matches filter, has room or no limit - DEFINITELY_YES
|
|
64
|
-
applyAction(store);
|
|
65
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_YES);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Evaluates and tracks membership state for UPDATE operations.
|
|
70
|
-
*
|
|
71
|
-
* For UPDATE, we check filter FIRST (optimization), then offset:
|
|
72
|
-
* - If item didn't match before AND doesn't match after: DEFINITELY_NO (regardless of offset)
|
|
73
|
-
* - If item matched before OR matches after AND offset > 0: MAYBE (can't determine position)
|
|
74
|
-
* - If item matched before OR matches after AND offset = 0: DEFINITELY_YES
|
|
75
|
-
*
|
|
76
|
-
* @param {Operation} operation - The UPDATE operation
|
|
77
|
-
*/
|
|
78
|
-
function routeUpdateOperation(operation) {
|
|
79
|
-
const modelClass = operation.queryset.ModelClass;
|
|
80
|
-
const beforeInstances = operation.frozenInstances;
|
|
81
|
-
const afterInstances = operation.instances;
|
|
82
|
-
Array.from(querysetStoreRegistry._stores.entries()).forEach(([semanticKey, store]) => {
|
|
83
|
-
if (store.modelClass !== modelClass)
|
|
84
|
-
return;
|
|
85
|
-
// Check filter match FIRST (optimization: skip offset check if no match)
|
|
86
|
-
const ast = store.queryset.build();
|
|
87
|
-
const matchedBefore = filter(beforeInstances, ast, modelClass, false).length > 0;
|
|
88
|
-
const matchesAfter = filter(afterInstances, ast, modelClass, false).length > 0;
|
|
89
|
-
// If item never matched filter, definitely not affected (regardless of offset)
|
|
90
|
-
if (!matchedBefore && !matchesAfter) {
|
|
91
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_NO);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
// Item matches filter - now check offset
|
|
95
|
-
const serializerOptions = store.queryset?._serializerOptions || {};
|
|
96
|
-
const { offset } = serializerOptions;
|
|
97
|
-
if (offset != null && offset > 0) {
|
|
98
|
-
// Can't determine position in paginated window
|
|
99
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.MAYBE);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
// Item matched before OR matches after, no offset - definitely affected
|
|
103
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_YES);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Evaluates and tracks membership state for DELETE operations.
|
|
108
|
-
*
|
|
109
|
-
* For DELETE, we check filter FIRST (optimization), then offset:
|
|
110
|
-
* - If item didn't match before: DEFINITELY_NO (regardless of offset)
|
|
111
|
-
* - If item matched before AND offset > 0: MAYBE (can't determine position)
|
|
112
|
-
* - If item matched before AND offset = 0: DEFINITELY_YES
|
|
113
|
-
*
|
|
114
|
-
* @param {Operation} operation - The DELETE operation
|
|
115
|
-
*/
|
|
116
|
-
function routeDeleteOperation(operation) {
|
|
117
|
-
const modelClass = operation.queryset.ModelClass;
|
|
118
|
-
const beforeInstances = operation.frozenInstances;
|
|
119
|
-
Array.from(querysetStoreRegistry._stores.entries()).forEach(([semanticKey, store]) => {
|
|
120
|
-
if (store.modelClass !== modelClass)
|
|
121
|
-
return;
|
|
122
|
-
// Check filter match FIRST (optimization: skip offset check if no match)
|
|
123
|
-
const ast = store.queryset.build();
|
|
124
|
-
const matchedBefore = filter(beforeInstances, ast, modelClass, false).length > 0;
|
|
125
|
-
// If item never matched filter, definitely not affected (regardless of offset)
|
|
126
|
-
if (!matchedBefore) {
|
|
127
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_NO);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
// Item matched filter - now check offset
|
|
131
|
-
const serializerOptions = store.queryset?._serializerOptions || {};
|
|
132
|
-
const { offset } = serializerOptions;
|
|
133
|
-
if (offset != null && offset > 0) {
|
|
134
|
-
// Can't determine position in paginated window
|
|
135
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.MAYBE);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
// Item matched before, no offset - definitely affected
|
|
139
|
-
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_YES);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Process an operation in the model store
|
|
144
|
-
*
|
|
145
|
-
* @param {Operation} operation - The operation to process
|
|
146
|
-
* @param {string} actionType - The action to perform ('add', 'update', 'confirm', 'reject')
|
|
147
|
-
*/
|
|
148
|
-
function processModelStore(operation, actionType) {
|
|
149
|
-
const ModelClass = operation.queryset.ModelClass;
|
|
150
|
-
const modelStore = modelStoreRegistry.getStore(ModelClass);
|
|
151
|
-
if (!modelStore)
|
|
152
|
-
return;
|
|
153
|
-
switch (actionType) {
|
|
154
|
-
case 'add':
|
|
155
|
-
modelStore.addOperation(operation);
|
|
156
|
-
break;
|
|
157
|
-
case 'update':
|
|
158
|
-
modelStore.updateOperation(operation);
|
|
159
|
-
break;
|
|
160
|
-
case 'confirm':
|
|
161
|
-
modelStore.confirm(operation);
|
|
162
|
-
break;
|
|
163
|
-
case 'reject':
|
|
164
|
-
modelStore.reject(operation);
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Process an operation in the queryset stores based on operation type
|
|
170
|
-
* Uses different routing strategies based on the operation type
|
|
171
|
-
*
|
|
172
|
-
* @param {Operation} operation - The operation to process
|
|
173
|
-
* @param {string} actionType - The action to perform ('add', 'update', 'confirm', 'reject')
|
|
174
|
-
*/
|
|
175
|
-
function processQuerysetStores(operation, actionType) {
|
|
176
|
-
const ModelClass = operation.queryset.ModelClass;
|
|
177
|
-
const queryset = operation.queryset;
|
|
178
|
-
// Apply the appropriate action to a single queryset store
|
|
179
|
-
const applyAction = (store) => {
|
|
180
|
-
switch (actionType) {
|
|
181
|
-
case 'add':
|
|
182
|
-
store.addOperation(operation);
|
|
183
|
-
break;
|
|
184
|
-
case 'update':
|
|
185
|
-
store.updateOperation(operation);
|
|
186
|
-
break;
|
|
187
|
-
case 'confirm':
|
|
188
|
-
store.confirm(operation);
|
|
189
|
-
break;
|
|
190
|
-
case 'reject':
|
|
191
|
-
store.reject(operation);
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
let querysetStoreMap;
|
|
196
|
-
// Route to querysets based on operation type
|
|
197
|
-
// All operation types track membership state for sync optimization
|
|
198
|
-
// CREATE: adds operation to store + tracks membership
|
|
199
|
-
// UPDATE/DELETE: only tracks membership (model store + reactivity handles rendering)
|
|
200
|
-
switch (operation.type) {
|
|
201
|
-
case Type.CREATE:
|
|
202
|
-
case Type.BULK_CREATE:
|
|
203
|
-
case Type.GET_OR_CREATE:
|
|
204
|
-
case Type.UPDATE_OR_CREATE:
|
|
205
|
-
// For creates, evaluate each queryset and track membership state
|
|
206
|
-
// This allows us to skip syncing querysets that we know don't contain the item
|
|
207
|
-
routeCreateOperation(operation, applyAction);
|
|
208
|
-
return;
|
|
209
|
-
case Type.UPDATE:
|
|
210
|
-
case Type.UPDATE_INSTANCE:
|
|
211
|
-
// Track membership for sync optimization
|
|
212
|
-
// Check both before (frozenInstances) and after (instances) state
|
|
213
|
-
routeUpdateOperation(operation);
|
|
214
|
-
return;
|
|
215
|
-
case Type.DELETE:
|
|
216
|
-
case Type.DELETE_INSTANCE:
|
|
217
|
-
// Track membership for sync optimization
|
|
218
|
-
// Check before state only (item being deleted)
|
|
219
|
-
routeDeleteOperation(operation);
|
|
220
|
-
return;
|
|
221
|
-
case Type.CHECKPOINT:
|
|
222
|
-
// Model store handles the change, querysets re-render via local filtering
|
|
223
|
-
// No membership tracking needed for checkpoints
|
|
224
|
-
return;
|
|
225
|
-
default:
|
|
226
|
-
// For other operation types, route like creates
|
|
227
|
-
routeCreateOperation(operation, applyAction);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Process an operation in the metric stores
|
|
233
|
-
*
|
|
234
|
-
* For metrics, we route operations UP the family tree - any metric on an ancestor
|
|
235
|
-
* queryset should receive the operation so it can check if it affects the metric.
|
|
236
|
-
*
|
|
237
|
-
* @param {Operation} operation - The operation to process
|
|
238
|
-
* @param {string} actionType - The action to perform ('add', 'update', 'confirm', 'reject')
|
|
239
|
-
*/
|
|
240
|
-
function processMetricStores(operation, actionType) {
|
|
241
|
-
const queryset = operation.queryset;
|
|
242
|
-
const allMetricStores = new Set();
|
|
243
|
-
// Walk up the queryset family tree and collect all metrics
|
|
244
|
-
let current = queryset;
|
|
245
|
-
while (current) {
|
|
246
|
-
const stores = metricRegistry.getAllStoresForQueryset(current);
|
|
247
|
-
if (stores && stores.length > 0) {
|
|
248
|
-
stores.forEach(store => allMetricStores.add(store));
|
|
249
|
-
}
|
|
250
|
-
current = current.__parent;
|
|
251
|
-
}
|
|
252
|
-
if (allMetricStores.size === 0) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
// Apply the action to each matching metric store
|
|
256
|
-
allMetricStores.forEach(store => {
|
|
257
|
-
switch (actionType) {
|
|
258
|
-
case 'add':
|
|
259
|
-
store.addOperation(operation);
|
|
260
|
-
break;
|
|
261
|
-
case 'update':
|
|
262
|
-
store.updateOperation(operation);
|
|
263
|
-
break;
|
|
264
|
-
case 'confirm':
|
|
265
|
-
store.confirm(operation);
|
|
266
|
-
break;
|
|
267
|
-
case 'reject':
|
|
268
|
-
store.reject(operation);
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Common processing logic for operations, handling validation and routing
|
|
275
|
-
* to the appropriate store processors
|
|
276
|
-
*
|
|
277
|
-
* @param {Operation} operation - The operation to process
|
|
278
|
-
* @param {string} actionType - The action to perform ('add', 'update', 'confirm', 'reject')
|
|
279
|
-
*/
|
|
280
|
-
function processOperation(operation, actionType) {
|
|
281
|
-
if (!operation || !operation.queryset || !operation.queryset.ModelClass) {
|
|
282
|
-
console.warn(`Received invalid operation in processOperation (${actionType})`, operation);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
if (operation.doNotPropagate) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
// Process model store first
|
|
289
|
-
processModelStore(operation, actionType);
|
|
290
|
-
// Then process queryset stores with improved routing
|
|
291
|
-
processQuerysetStores(operation, actionType);
|
|
292
|
-
// Finally process metric stores
|
|
293
|
-
processMetricStores(operation, actionType);
|
|
294
|
-
}
|
|
295
|
-
// Define handlers as named arrow functions at the top level
|
|
296
|
-
const handleOperationCreated = operation => processOperation(operation, 'add');
|
|
297
|
-
const handleOperationUpdated = operation => processOperation(operation, 'update');
|
|
298
|
-
const handleOperationMutated = operation => processOperation(operation, 'update');
|
|
299
|
-
const handleOperationConfirmed = operation => processOperation(operation, 'confirm');
|
|
300
|
-
const handleOperationRejected = operation => processOperation(operation, 'reject');
|
|
301
|
-
/**
|
|
302
|
-
* Initialize the operation event handler system by setting up event listeners
|
|
303
|
-
*/
|
|
304
|
-
export function initEventHandler() {
|
|
305
|
-
operationEvents.on(Status.CREATED, handleOperationCreated);
|
|
306
|
-
operationEvents.on(Status.UPDATED, handleOperationUpdated);
|
|
307
|
-
operationEvents.on(Status.CONFIRMED, handleOperationConfirmed);
|
|
308
|
-
operationEvents.on(Status.REJECTED, handleOperationRejected);
|
|
309
|
-
operationEvents.on(Status.MUTATED, handleOperationMutated);
|
|
310
|
-
console.log('Operation event handler initialized');
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Clean up by removing all event listeners
|
|
314
|
-
*/
|
|
315
|
-
export function cleanupEventHandler() {
|
|
316
|
-
operationEvents.off(Status.CREATED, handleOperationCreated);
|
|
317
|
-
operationEvents.off(Status.UPDATED, handleOperationUpdated);
|
|
318
|
-
operationEvents.off(Status.CONFIRMED, handleOperationConfirmed);
|
|
319
|
-
operationEvents.off(Status.REJECTED, handleOperationRejected);
|
|
320
|
-
operationEvents.off(Status.MUTATED, handleOperationMutated);
|
|
321
|
-
console.log('Operation event handler cleaned up');
|
|
322
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
export class QuerysetStore {
|
|
2
|
-
constructor(modelClass: any, fetchFn: any, queryset: any, initialGroundTruthPks?: null, initialOperations?: null, options?: {});
|
|
3
|
-
modelClass: any;
|
|
4
|
-
fetchFn: any;
|
|
5
|
-
queryset: any;
|
|
6
|
-
operationsMap: Map<any, any>;
|
|
7
|
-
groundTruthPks: never[];
|
|
8
|
-
isSyncing: boolean;
|
|
9
|
-
lastSync: number | null;
|
|
10
|
-
isTemp: any;
|
|
11
|
-
pruneThreshold: any;
|
|
12
|
-
includedPks: Map<any, any>;
|
|
13
|
-
qsCache: Cache;
|
|
14
|
-
_lastRenderedPks: any[] | null;
|
|
15
|
-
renderCallbacks: Set<any>;
|
|
16
|
-
_modelStoreUnregister: any;
|
|
17
|
-
get cacheKey(): any;
|
|
18
|
-
onHydrated(hydratedData: any): void;
|
|
19
|
-
setCache(result: any): void;
|
|
20
|
-
clearCache(): void;
|
|
21
|
-
get operations(): any[];
|
|
22
|
-
get pkField(): any;
|
|
23
|
-
get groundTruthSet(): Set<never>;
|
|
24
|
-
_emitRenderEvent(): void;
|
|
25
|
-
addOperation(operation: any): Promise<void>;
|
|
26
|
-
updateOperation(operation: any): Promise<true | undefined>;
|
|
27
|
-
confirm(operation: any): Promise<void>;
|
|
28
|
-
reject(operation: any): Promise<void>;
|
|
29
|
-
setGroundTruth(groundTruthPks: any): Promise<void>;
|
|
30
|
-
setOperations(operations: any): Promise<void>;
|
|
31
|
-
getTrimmedOperations(): any[];
|
|
32
|
-
getInflightOperations(): any[];
|
|
33
|
-
prune(): void;
|
|
34
|
-
registerRenderCallback(callback: any): () => boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Register this store with the model store for change notifications.
|
|
37
|
-
* Called when a temp store is promoted to permanent.
|
|
38
|
-
*/
|
|
39
|
-
registerWithModelStore(): void;
|
|
40
|
-
/**
|
|
41
|
-
* Helper to validate PKs against the model store and apply local filtering/sorting.
|
|
42
|
-
* This is the core of the rendering logic.
|
|
43
|
-
* @private
|
|
44
|
-
*/
|
|
45
|
-
private _getValidatedAndFilteredPks;
|
|
46
|
-
render(optimistic?: boolean, fromCache?: boolean): any[];
|
|
47
|
-
renderFromData(optimistic?: boolean): any[];
|
|
48
|
-
/**
|
|
49
|
-
* Render by getting all instances from the model store and filtering locally.
|
|
50
|
-
* Used when a queryset has no ground truth (temp stores, newly created stores, etc.)
|
|
51
|
-
*/
|
|
52
|
-
renderFromModelStore(): any[];
|
|
53
|
-
applyOperation(operation: any, currentPks: any): any;
|
|
54
|
-
/**
|
|
55
|
-
* Sync this queryset with the database.
|
|
56
|
-
* Fetches from DB and sets ground truth.
|
|
57
|
-
*/
|
|
58
|
-
sync(): Promise<void>;
|
|
59
|
-
}
|
|
60
|
-
import { Cache } from '../cache/cache.js';
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { Operation, Status, Type, operationRegistry } from './operation.js';
|
|
2
|
-
import { querysetEventEmitter } from './reactivity.js';
|
|
3
|
-
import { isNil, isEmpty, trim, isEqual } from 'lodash-es';
|
|
4
|
-
import { replaceTempPks, containsTempPk } from '../../flavours/django/tempPk.js';
|
|
5
|
-
import { modelStoreRegistry } from '../registries/modelStoreRegistry.js';
|
|
6
|
-
import { processIncludedEntities } from '../../flavours/django/makeApiCall.js';
|
|
7
|
-
import { Cache } from '../cache/cache.js';
|
|
8
|
-
import { filter } from "../../filtering/localFiltering.js";
|
|
9
|
-
export class QuerysetStore {
|
|
10
|
-
constructor(modelClass, fetchFn, queryset, initialGroundTruthPks = null, initialOperations = null, options = {}) {
|
|
11
|
-
this.modelClass = modelClass;
|
|
12
|
-
this.fetchFn = fetchFn;
|
|
13
|
-
this.queryset = queryset;
|
|
14
|
-
this.isSyncing = false;
|
|
15
|
-
this.lastSync = null;
|
|
16
|
-
this.isTemp = options.isTemp || false;
|
|
17
|
-
this.pruneThreshold = options.pruneThreshold || 10;
|
|
18
|
-
this.groundTruthPks = initialGroundTruthPks || [];
|
|
19
|
-
this.operationsMap = new Map();
|
|
20
|
-
// Track which model PKs are in this queryset's included data
|
|
21
|
-
// Map<modelName, Set<pk>>
|
|
22
|
-
this.includedPks = new Map();
|
|
23
|
-
if (Array.isArray(initialOperations)) {
|
|
24
|
-
for (const opData of initialOperations) {
|
|
25
|
-
const existing = operationRegistry.get(opData.operationId);
|
|
26
|
-
const op = existing || new Operation(opData, true);
|
|
27
|
-
this.operationsMap.set(op.operationId, op);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
this.qsCache = new Cache("queryset-cache", {}, this.onHydrated.bind(this));
|
|
31
|
-
this._lastRenderedPks = null;
|
|
32
|
-
this.renderCallbacks = new Set();
|
|
33
|
-
// Register for model store changes to re-render when model data changes
|
|
34
|
-
// Only register permanent stores - temp stores are transient and should not
|
|
35
|
-
// accumulate callbacks (causes reactivity cascade when Vue creates new querysets)
|
|
36
|
-
if (!this.isTemp) {
|
|
37
|
-
const modelStore = modelStoreRegistry.getStore(this.modelClass);
|
|
38
|
-
this._modelStoreUnregister = modelStore.registerRenderCallback(() => {
|
|
39
|
-
this._emitRenderEvent();
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
// Caching
|
|
44
|
-
get cacheKey() {
|
|
45
|
-
return this.queryset.semanticKey;
|
|
46
|
-
}
|
|
47
|
-
onHydrated(hydratedData) {
|
|
48
|
-
if (this.groundTruthPks.length === 0 && this.operationsMap.size === 0) {
|
|
49
|
-
const cached = this.qsCache.get(this.cacheKey);
|
|
50
|
-
if (!isNil(cached) && !isEmpty(cached)) {
|
|
51
|
-
console.log(`[QuerysetStore] Hydrated ${this.modelClass.modelName} queryset with ${cached.length} items from cache`);
|
|
52
|
-
this.setGroundTruth(cached);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
setCache(result) {
|
|
57
|
-
let nonTempPks = [];
|
|
58
|
-
result.forEach((pk) => {
|
|
59
|
-
if (typeof pk === "string" && containsTempPk(pk)) {
|
|
60
|
-
pk = replaceTempPks(pk);
|
|
61
|
-
if (isNil(pk) || isEmpty(trim(pk))) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
nonTempPks.push(pk);
|
|
66
|
-
});
|
|
67
|
-
this.qsCache.set(this.cacheKey, nonTempPks);
|
|
68
|
-
}
|
|
69
|
-
clearCache() {
|
|
70
|
-
this.qsCache.delete(this.cacheKey);
|
|
71
|
-
}
|
|
72
|
-
// -- Core methods --
|
|
73
|
-
get operations() {
|
|
74
|
-
return Array.from(this.operationsMap.values());
|
|
75
|
-
}
|
|
76
|
-
get pkField() {
|
|
77
|
-
return this.modelClass.primaryKeyField;
|
|
78
|
-
}
|
|
79
|
-
get groundTruthSet() {
|
|
80
|
-
return new Set(this.groundTruthPks);
|
|
81
|
-
}
|
|
82
|
-
_emitRenderEvent() {
|
|
83
|
-
const newPks = this.render(true, false);
|
|
84
|
-
// 1. Always notify direct child stores to trigger their own re-evaluation.
|
|
85
|
-
// They will perform their own check to see if their own results have changed.
|
|
86
|
-
this.renderCallbacks.forEach((callback) => {
|
|
87
|
-
try {
|
|
88
|
-
callback();
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
console.warn("Error in render callback:", error);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
// 2. Only emit the global event for UI components if the final list of PKs has actually changed.
|
|
95
|
-
if (!isEqual(newPks, this._lastRenderedPks)) {
|
|
96
|
-
this._lastRenderedPks = newPks; // Update the cache with the new state
|
|
97
|
-
querysetEventEmitter.emit(`${this.modelClass.configKey}::${this.modelClass.modelName}::queryset::render`, { ast: this.queryset.build(), ModelClass: this.modelClass });
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
async addOperation(operation) {
|
|
101
|
-
this.operationsMap.set(operation.operationId, operation);
|
|
102
|
-
if (this.operationsMap.size > this.pruneThreshold) {
|
|
103
|
-
this.prune();
|
|
104
|
-
}
|
|
105
|
-
this._emitRenderEvent();
|
|
106
|
-
}
|
|
107
|
-
async updateOperation(operation) {
|
|
108
|
-
if (!this.operationsMap.has(operation.operationId))
|
|
109
|
-
return;
|
|
110
|
-
this.operationsMap.set(operation.operationId, operation);
|
|
111
|
-
this._emitRenderEvent();
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
async confirm(operation) {
|
|
115
|
-
if (!this.operationsMap.has(operation.operationId))
|
|
116
|
-
return;
|
|
117
|
-
this.operationsMap.set(operation.operationId, operation);
|
|
118
|
-
this._emitRenderEvent();
|
|
119
|
-
}
|
|
120
|
-
async reject(operation) {
|
|
121
|
-
if (!this.operationsMap.has(operation.operationId))
|
|
122
|
-
return;
|
|
123
|
-
this.operationsMap.set(operation.operationId, operation);
|
|
124
|
-
this._emitRenderEvent();
|
|
125
|
-
}
|
|
126
|
-
async setGroundTruth(groundTruthPks) {
|
|
127
|
-
this.groundTruthPks = Array.isArray(groundTruthPks) ? groundTruthPks : [];
|
|
128
|
-
this.lastSync = Date.now();
|
|
129
|
-
this._emitRenderEvent();
|
|
130
|
-
}
|
|
131
|
-
async setOperations(operations) {
|
|
132
|
-
this.operationsMap.clear();
|
|
133
|
-
if (Array.isArray(operations)) {
|
|
134
|
-
for (const op of operations) {
|
|
135
|
-
this.operationsMap.set(op.operationId, op);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
this._emitRenderEvent();
|
|
139
|
-
}
|
|
140
|
-
getTrimmedOperations() {
|
|
141
|
-
const cutoff = Date.now() - 1000 * 60 * 2;
|
|
142
|
-
return this.operations.filter((op) => op.timestamp > cutoff);
|
|
143
|
-
}
|
|
144
|
-
getInflightOperations() {
|
|
145
|
-
return this.operations.filter((operation) => operation.status != Status.CONFIRMED &&
|
|
146
|
-
operation.status != Status.REJECTED);
|
|
147
|
-
}
|
|
148
|
-
prune() {
|
|
149
|
-
const renderedPks = this.render(false);
|
|
150
|
-
this.setGroundTruth(renderedPks);
|
|
151
|
-
this.setOperations(this.getInflightOperations());
|
|
152
|
-
}
|
|
153
|
-
registerRenderCallback(callback) {
|
|
154
|
-
this.renderCallbacks.add(callback);
|
|
155
|
-
return () => this.renderCallbacks.delete(callback);
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Register this store with the model store for change notifications.
|
|
159
|
-
* Called when a temp store is promoted to permanent.
|
|
160
|
-
*/
|
|
161
|
-
registerWithModelStore() {
|
|
162
|
-
if (this._modelStoreUnregister)
|
|
163
|
-
return; // Already registered
|
|
164
|
-
const modelStore = modelStoreRegistry.getStore(this.modelClass);
|
|
165
|
-
this._modelStoreUnregister = modelStore.registerRenderCallback(() => {
|
|
166
|
-
this._emitRenderEvent();
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Helper to validate PKs against the model store and apply local filtering/sorting.
|
|
171
|
-
* This is the core of the rendering logic.
|
|
172
|
-
* @private
|
|
173
|
-
*/
|
|
174
|
-
_getValidatedAndFilteredPks(pks) {
|
|
175
|
-
// 1. Convert PKs to instances, filtering out any that are null (deleted).
|
|
176
|
-
const instances = Array.from(pks)
|
|
177
|
-
.map((pk) => this.modelClass.fromPk(pk, this.queryset))
|
|
178
|
-
.filter((instance) => modelStoreRegistry.getEntity(this.modelClass, instance.pk) !== null);
|
|
179
|
-
// 2. Apply the queryset's AST (filters, ordering) to the validated instances.
|
|
180
|
-
const ast = this.queryset.build();
|
|
181
|
-
const finalPks = filter(instances, ast, this.modelClass, false); // false = return PKs
|
|
182
|
-
return finalPks;
|
|
183
|
-
}
|
|
184
|
-
render(optimistic = true, fromCache = false) {
|
|
185
|
-
// Check cache first if requested
|
|
186
|
-
if (fromCache) {
|
|
187
|
-
const cachedResult = this.qsCache.get(this.cacheKey);
|
|
188
|
-
if (Array.isArray(cachedResult)) {
|
|
189
|
-
return cachedResult;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
// If no ground truth AND hasn't been synced, render from model store
|
|
193
|
-
// This handles chained optimistic filters, newly created stores, etc.
|
|
194
|
-
// (If synced with empty results, that's valid ground truth)
|
|
195
|
-
const pks = this.groundTruthPks.length === 0 && this.lastSync === null
|
|
196
|
-
? this.renderFromModelStore()
|
|
197
|
-
: this.renderFromData(optimistic);
|
|
198
|
-
// Validate against model store and apply local filtering/sorting
|
|
199
|
-
let result = this._getValidatedAndFilteredPks(pks);
|
|
200
|
-
// Apply pagination limit
|
|
201
|
-
const limit = this.queryset.build().serializerOptions?.limit;
|
|
202
|
-
if (limit) {
|
|
203
|
-
result = result.slice(0, limit);
|
|
204
|
-
}
|
|
205
|
-
this.setCache(result);
|
|
206
|
-
return result;
|
|
207
|
-
}
|
|
208
|
-
renderFromData(optimistic = true) {
|
|
209
|
-
const renderedPks = this.groundTruthSet;
|
|
210
|
-
for (const op of this.operations) {
|
|
211
|
-
if (op.status !== Status.REJECTED &&
|
|
212
|
-
(optimistic || op.status === Status.CONFIRMED)) {
|
|
213
|
-
this.applyOperation(op, renderedPks);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
let result = Array.from(renderedPks);
|
|
217
|
-
return result;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Render by getting all instances from the model store and filtering locally.
|
|
221
|
-
* Used when a queryset has no ground truth (temp stores, newly created stores, etc.)
|
|
222
|
-
*/
|
|
223
|
-
renderFromModelStore() {
|
|
224
|
-
const modelStore = modelStoreRegistry.getStore(this.modelClass);
|
|
225
|
-
const allPks = modelStore.groundTruthPks;
|
|
226
|
-
const allInstances = allPks.map((pk) => this.modelClass.fromPk(pk, this.queryset));
|
|
227
|
-
const ast = this.queryset.build();
|
|
228
|
-
return filter(allInstances, ast, this.modelClass, false);
|
|
229
|
-
}
|
|
230
|
-
applyOperation(operation, currentPks) {
|
|
231
|
-
const pkField = this.pkField;
|
|
232
|
-
for (const instance of operation.instances) {
|
|
233
|
-
if (!instance || typeof instance !== "object" || !(pkField in instance)) {
|
|
234
|
-
console.warn(`[QuerysetStore ${this.modelClass.modelName}] Skipping instance in operation ${operation.operationId} due to missing PK '${String(pkField)}' or invalid format.`);
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
let pk = instance[pkField];
|
|
238
|
-
switch (operation.type) {
|
|
239
|
-
case Type.CREATE:
|
|
240
|
-
case Type.BULK_CREATE:
|
|
241
|
-
currentPks.add(pk);
|
|
242
|
-
break;
|
|
243
|
-
case Type.CHECKPOINT:
|
|
244
|
-
case Type.UPDATE:
|
|
245
|
-
case Type.UPDATE_INSTANCE:
|
|
246
|
-
break;
|
|
247
|
-
case Type.DELETE:
|
|
248
|
-
case Type.DELETE_INSTANCE:
|
|
249
|
-
currentPks.delete(pk);
|
|
250
|
-
break;
|
|
251
|
-
default:
|
|
252
|
-
console.error(`[QuerysetStore ${this.modelClass.modelName}] Unknown operation type: ${operation.type}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return currentPks;
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Sync this queryset with the database.
|
|
259
|
-
* Fetches from DB and sets ground truth.
|
|
260
|
-
*/
|
|
261
|
-
async sync() {
|
|
262
|
-
const id = this.modelClass.modelName;
|
|
263
|
-
if (this.isSyncing) {
|
|
264
|
-
console.warn(`[QuerysetStore ${id}] Already syncing, request ignored.`);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
this.isSyncing = true;
|
|
268
|
-
console.log(`[${id}] Starting sync...`);
|
|
269
|
-
try {
|
|
270
|
-
const response = await this.fetchFn({
|
|
271
|
-
ast: this.queryset.build(),
|
|
272
|
-
modelClass: this.modelClass,
|
|
273
|
-
});
|
|
274
|
-
const { data, included } = response;
|
|
275
|
-
if (!isNil(data)) {
|
|
276
|
-
console.log(`[${id}] Sync fetch completed. Received: ${JSON.stringify(data)}.`);
|
|
277
|
-
// Clear previous included PKs tracking before processing new data
|
|
278
|
-
this.includedPks.clear();
|
|
279
|
-
// Persist all instances (including nested) to the model store
|
|
280
|
-
processIncludedEntities(modelStoreRegistry, included, this.modelClass, this.queryset);
|
|
281
|
-
this.setGroundTruth(data);
|
|
282
|
-
}
|
|
283
|
-
this.setOperations(this.getInflightOperations());
|
|
284
|
-
this.lastSync = Date.now();
|
|
285
|
-
console.log(`[${id}] Sync completed.`);
|
|
286
|
-
}
|
|
287
|
-
catch (e) {
|
|
288
|
-
console.error(`[${id}] Failed to sync ground truth:`, e);
|
|
289
|
-
}
|
|
290
|
-
finally {
|
|
291
|
-
this.isSyncing = false;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|