@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,174 @@
|
|
|
1
|
+
import { Graph } from "graphlib";
|
|
2
|
+
/**
|
|
3
|
+
* Simple graph for tracking queryset store ancestry
|
|
4
|
+
*/
|
|
5
|
+
export class QuerysetStoreGraph {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.graph = new Graph({ directed: true });
|
|
8
|
+
this.processedQuerysets = new Set(); // Track UUIDs of processed querysets
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Add a queryset and its parent relationship to the graph
|
|
12
|
+
*/
|
|
13
|
+
addQueryset(queryset) {
|
|
14
|
+
if (!queryset)
|
|
15
|
+
return;
|
|
16
|
+
if (this.processedQuerysets.has(queryset.key)) {
|
|
17
|
+
return; // Already processed, skip
|
|
18
|
+
}
|
|
19
|
+
let current = queryset;
|
|
20
|
+
while (current && !this.processedQuerysets.has(current.key)) {
|
|
21
|
+
const currentKey = current.semanticKey;
|
|
22
|
+
const currentUuid = current.key;
|
|
23
|
+
this.processedQuerysets.add(currentUuid);
|
|
24
|
+
this.graph.setNode(currentKey);
|
|
25
|
+
if (current.__parent) {
|
|
26
|
+
const parentKey = current.__parent.semanticKey;
|
|
27
|
+
this.graph.setNode(parentKey);
|
|
28
|
+
// Determine if we can create an edge to parent
|
|
29
|
+
// Parent must be a valid data source (superset of child's data needs)
|
|
30
|
+
const canLinkToParent = currentKey !== parentKey &&
|
|
31
|
+
this._isValidParentForEdge(current.__parent._serializerOptions, current._serializerOptions, current.__parent._orderBy, current._orderBy);
|
|
32
|
+
if (canLinkToParent) {
|
|
33
|
+
this.graph.setEdge(currentKey, parentKey);
|
|
34
|
+
}
|
|
35
|
+
current = current.__parent;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Find the root (topmost ancestor) of a queryset chain within a subset.
|
|
44
|
+
* Traverses up the graph but only considers nodes that are in the subset.
|
|
45
|
+
* Uses the graph to "jump" through nodes not in subset to find connections.
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} queryset - The queryset to analyze
|
|
48
|
+
* @param {Set|null} subset - Set of semanticKeys to consider. If null, considers all nodes in graph.
|
|
49
|
+
* @returns {Object} { isRoot: boolean, root: semanticKey|null }
|
|
50
|
+
*/
|
|
51
|
+
findRoot(queryset, subset = null) {
|
|
52
|
+
// Validate input - null/undefined is a programming error
|
|
53
|
+
if (!queryset) {
|
|
54
|
+
throw new Error("findRoot was called with a null object, instead of a queryset");
|
|
55
|
+
}
|
|
56
|
+
// Handle queryset without semanticKey
|
|
57
|
+
if (!queryset.semanticKey) {
|
|
58
|
+
throw new Error("findRoot was called on an object without a semanticKey, which means its not a queryset. findRoot only works on querysets");
|
|
59
|
+
}
|
|
60
|
+
const semanticKey = queryset.semanticKey;
|
|
61
|
+
if (!this.graph.hasNode(semanticKey)) {
|
|
62
|
+
this.addQueryset(queryset);
|
|
63
|
+
}
|
|
64
|
+
// If no subset provided, consider all nodes in the graph
|
|
65
|
+
subset = subset || new Set(this.graph.nodes());
|
|
66
|
+
// Traverse ALL the way up to find the HIGHEST ancestor in the subset
|
|
67
|
+
const visited = new Set();
|
|
68
|
+
let current = semanticKey;
|
|
69
|
+
let highestInSubset = null;
|
|
70
|
+
while (current && !visited.has(current)) {
|
|
71
|
+
visited.add(current);
|
|
72
|
+
// Check if current node is in subset
|
|
73
|
+
if (subset.has(current)) {
|
|
74
|
+
highestInSubset = current;
|
|
75
|
+
}
|
|
76
|
+
// Move to parent (continue jumping even if current not in subset)
|
|
77
|
+
const parents = this.graph.successors(current) || [];
|
|
78
|
+
if (parents.length > 0) {
|
|
79
|
+
current = parents[0];
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (highestInSubset) {
|
|
86
|
+
if (highestInSubset === semanticKey) {
|
|
87
|
+
// This queryset itself is the highest in subset
|
|
88
|
+
return { isRoot: true, root: semanticKey };
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Found a higher ancestor in subset
|
|
92
|
+
return { isRoot: false, root: highestInSubset };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// No nodes found in subset
|
|
96
|
+
return { isRoot: true, root: null };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Check if parent queryset is a valid data source for creating an edge.
|
|
100
|
+
* Parent must have data that is a superset of what child needs.
|
|
101
|
+
* Child with offset must sync independently (can't derive window from parent).
|
|
102
|
+
*
|
|
103
|
+
* @param {Object} parentOpts - Parent's serializerOptions
|
|
104
|
+
* @param {Object} childOpts - Child's serializerOptions
|
|
105
|
+
* @param {Array|undefined} parentOrderBy - Parent's ordering
|
|
106
|
+
* @param {Array|undefined} childOrderBy - Child's ordering
|
|
107
|
+
* @returns {boolean} - True if parent is valid for edge creation
|
|
108
|
+
*/
|
|
109
|
+
_isValidParentForEdge(parentOpts = {}, childOpts = {}, parentOrderBy, childOrderBy) {
|
|
110
|
+
// Cannot link if parent has offset > 0 (paginated parent has subset of data)
|
|
111
|
+
// Note: offset: 0 is treated as no offset (start from beginning)
|
|
112
|
+
if (parentOpts.offset != null && parentOpts.offset > 0) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
// Cannot link if parent has limit - child may need items beyond parent's limit window
|
|
116
|
+
// (filtered items matching child could exist beyond parent's limit cutoff)
|
|
117
|
+
// If child has same filter + same limit, they'd have same semanticKey (no edge needed)
|
|
118
|
+
if (parentOpts.limit != null) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
// Cannot link if child has offset > 0 - must sync independently
|
|
122
|
+
// We can't derive which items are at positions N-M from the parent's full data
|
|
123
|
+
// because that requires the server's ordering logic
|
|
124
|
+
// Note: offset: 0 is treated as no offset (start from beginning)
|
|
125
|
+
if (childOpts.offset != null && childOpts.offset > 0) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
// Note: ordering doesn't matter for linking since parent has no limit (checked above)
|
|
129
|
+
// Parent has all items, so child can re-sort locally regardless of ordering
|
|
130
|
+
// Cannot link if parent has different depth
|
|
131
|
+
// Different depth means different nested data structure
|
|
132
|
+
if (parentOpts.depth != null && parentOpts.depth !== childOpts.depth) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
// Cannot link if parent has fields that are not a superset of child's fields
|
|
136
|
+
// Parent must have all fields that child needs
|
|
137
|
+
if (parentOpts.fields != null) {
|
|
138
|
+
// If child needs all fields (null) but parent restricts fields, cannot link
|
|
139
|
+
if (childOpts.fields == null) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
// Check if parent's fields contain all of child's fields
|
|
143
|
+
const parentFields = new Set(parentOpts.fields);
|
|
144
|
+
const childHasFieldsNotInParent = childOpts.fields.some(f => !parentFields.has(f));
|
|
145
|
+
if (childHasFieldsNotInParent) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check if two orderings are equivalent.
|
|
153
|
+
* @param {Array|undefined} orderBy1
|
|
154
|
+
* @param {Array|undefined} orderBy2
|
|
155
|
+
* @returns {boolean}
|
|
156
|
+
*/
|
|
157
|
+
_orderingsMatch(orderBy1, orderBy2) {
|
|
158
|
+
// Both undefined/null = match
|
|
159
|
+
if (!orderBy1 && !orderBy2)
|
|
160
|
+
return true;
|
|
161
|
+
// One defined, one not = no match
|
|
162
|
+
if (!orderBy1 || !orderBy2)
|
|
163
|
+
return false;
|
|
164
|
+
// Different lengths = no match
|
|
165
|
+
if (orderBy1.length !== orderBy2.length)
|
|
166
|
+
return false;
|
|
167
|
+
// Compare each field
|
|
168
|
+
return orderBy1.every((field, i) => field === orderBy2[i]);
|
|
169
|
+
}
|
|
170
|
+
clear() {
|
|
171
|
+
this.graph = new Graph({ directed: true });
|
|
172
|
+
this.processedQuerysets = new Set();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dynamic wrapper that always returns the latest queryset results
|
|
3
|
+
* This class proxies array operations to always reflect the current state
|
|
4
|
+
* of the underlying QuerysetStore.
|
|
5
|
+
*/
|
|
6
|
+
export class LiveQueryset {
|
|
7
|
+
constructor(queryset: any);
|
|
8
|
+
/**
|
|
9
|
+
* Serializes the lqs as a simple array of objects, for freezing e.g in the metric stores
|
|
10
|
+
*/
|
|
11
|
+
serialize(): any;
|
|
12
|
+
/**
|
|
13
|
+
* Refresh the queryset data from the database
|
|
14
|
+
* Delegates to the underlying store's sync method
|
|
15
|
+
*/
|
|
16
|
+
refreshFromDb(): any;
|
|
17
|
+
/**
|
|
18
|
+
* Get the current items from the store
|
|
19
|
+
* @private
|
|
20
|
+
* @returns {Array} The current items in the queryset
|
|
21
|
+
*/
|
|
22
|
+
private getCurrentItems;
|
|
23
|
+
#private;
|
|
24
|
+
}
|
|
25
|
+
export class QuerysetStoreRegistry {
|
|
26
|
+
_stores: Map<any, any>;
|
|
27
|
+
_tempStores: WeakMap<object, any>;
|
|
28
|
+
followingQuerysets: Map<any, any>;
|
|
29
|
+
syncManager: () => void;
|
|
30
|
+
querysetStoreGraph: QuerysetStoreGraph;
|
|
31
|
+
_groupSyncCache: Map<any, any>;
|
|
32
|
+
clear(): void;
|
|
33
|
+
setSyncManager(syncManager: any): void;
|
|
34
|
+
/**
|
|
35
|
+
* Add a queryset to the following set for a semantic key
|
|
36
|
+
*/
|
|
37
|
+
addFollowingQueryset(semanticKey: any, queryset: any): void;
|
|
38
|
+
getStore(queryset: any): any;
|
|
39
|
+
/**
|
|
40
|
+
* Get the current state of the queryset, wrapped in a LiveQueryset
|
|
41
|
+
* @param {Object} queryset - The queryset
|
|
42
|
+
* @param {Boolean} sync - Schedule a sync of the queryset with the backend
|
|
43
|
+
* @returns {LiveQueryset} - A live view of the queryset
|
|
44
|
+
*/
|
|
45
|
+
getEntity(queryset: Object, sync?: boolean): LiveQueryset;
|
|
46
|
+
/**
|
|
47
|
+
* Set ground truth for a queryset
|
|
48
|
+
* @param {Object} queryset - The queryset
|
|
49
|
+
* @param {Array} instances - Array of instances to set as ground truth
|
|
50
|
+
* @returns {Array} - The set instances
|
|
51
|
+
*/
|
|
52
|
+
setEntity(queryset: Object, instances: any[]): any[];
|
|
53
|
+
/**
|
|
54
|
+
* Get all queryset stores for a specific model class
|
|
55
|
+
* @param {ModelClass} ModelClass - The model class to get stores for
|
|
56
|
+
* @returns {Array} - Array of queryset stores for this model
|
|
57
|
+
*/
|
|
58
|
+
getAllStoresForModel(ModelClass: any): any[];
|
|
59
|
+
/**
|
|
60
|
+
* Sync a queryset, coordinating with its chain to minimize DB calls.
|
|
61
|
+
* The root fetches from DB, children filter from cached results.
|
|
62
|
+
* Uses operationId to coordinate - whoever arrives first creates the promise,
|
|
63
|
+
* the root takes over and resolves it.
|
|
64
|
+
*
|
|
65
|
+
* @param {Object} queryset - The queryset to sync
|
|
66
|
+
* @param {string} operationId - Unique ID for this sync operation (for coordination)
|
|
67
|
+
* @param {Set} dbSyncedKeys - Set of semanticKeys that are dbSynced (followedQuerysets)
|
|
68
|
+
*/
|
|
69
|
+
groupSync(queryset: Object, operationId: string, dbSyncedKeys: Set<any>): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
export const querysetStoreRegistry: QuerysetStoreRegistry;
|
|
72
|
+
import { QuerysetStoreGraph } from './querysetStoreGraph.js';
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _LiveQueryset_queryset, _LiveQueryset_ModelClass, _LiveQueryset_proxy, _LiveQueryset_array;
|
|
13
|
+
import { QuerysetStore } from '../stores/querysetStore.js';
|
|
14
|
+
import { modelStoreRegistry } from '../registries/modelStoreRegistry.js';
|
|
15
|
+
import { wrapReactiveQuerySet } from '../../reactiveAdaptor.js';
|
|
16
|
+
import { processQuery, getRequiredFields, pickRequiredFields } from '../../filtering/localFiltering.js';
|
|
17
|
+
import { filter } from '../../filtering/localFiltering.js';
|
|
18
|
+
import { makeApiCall } from '../../flavours/django/makeApiCall.js';
|
|
19
|
+
import { QuerysetStoreGraph } from './querysetStoreGraph.js';
|
|
20
|
+
import { isNil, pick } from 'lodash-es';
|
|
21
|
+
import hash from 'object-hash';
|
|
22
|
+
import { Operation } from '../stores/operation.js';
|
|
23
|
+
import { Cache } from '../cache/cache.js';
|
|
24
|
+
/**
|
|
25
|
+
* A dynamic wrapper that always returns the latest queryset results
|
|
26
|
+
* This class proxies array operations to always reflect the current state
|
|
27
|
+
* of the underlying QuerysetStore.
|
|
28
|
+
*/
|
|
29
|
+
export class LiveQueryset {
|
|
30
|
+
constructor(queryset) {
|
|
31
|
+
_LiveQueryset_queryset.set(this, void 0);
|
|
32
|
+
_LiveQueryset_ModelClass.set(this, void 0);
|
|
33
|
+
_LiveQueryset_proxy.set(this, void 0);
|
|
34
|
+
_LiveQueryset_array.set(this, []);
|
|
35
|
+
// used internally
|
|
36
|
+
__classPrivateFieldSet(this, _LiveQueryset_queryset, queryset, "f");
|
|
37
|
+
__classPrivateFieldSet(this, _LiveQueryset_ModelClass, queryset.ModelClass, "f");
|
|
38
|
+
__classPrivateFieldGet(this, _LiveQueryset_array, "f").queryset = queryset;
|
|
39
|
+
__classPrivateFieldGet(this, _LiveQueryset_array, "f").ModelClass = queryset.ModelClass;
|
|
40
|
+
// Create a proxy that intercepts all array access
|
|
41
|
+
__classPrivateFieldSet(this, _LiveQueryset_proxy, new Proxy(__classPrivateFieldGet(this, _LiveQueryset_array, "f"), {
|
|
42
|
+
get: (target, prop, receiver) => {
|
|
43
|
+
// Expose the touch method through the proxy
|
|
44
|
+
if (prop === "touch") {
|
|
45
|
+
return () => this.touch();
|
|
46
|
+
}
|
|
47
|
+
if (prop === "serialize") {
|
|
48
|
+
return () => this.serialize();
|
|
49
|
+
}
|
|
50
|
+
// Special handling for iterators and common array methods
|
|
51
|
+
if (prop === Symbol.iterator) {
|
|
52
|
+
return () => this.getCurrentItems()[Symbol.iterator]();
|
|
53
|
+
}
|
|
54
|
+
else if (typeof prop === "string" &&
|
|
55
|
+
[
|
|
56
|
+
"forEach",
|
|
57
|
+
"map",
|
|
58
|
+
"filter",
|
|
59
|
+
"reduce",
|
|
60
|
+
"some",
|
|
61
|
+
"every",
|
|
62
|
+
"find",
|
|
63
|
+
].includes(prop)) {
|
|
64
|
+
return (...args) => this.getCurrentItems()[prop](...args);
|
|
65
|
+
}
|
|
66
|
+
else if (prop === "length") {
|
|
67
|
+
return this.getCurrentItems().length;
|
|
68
|
+
}
|
|
69
|
+
else if (typeof prop === "string" && !isNaN(parseInt(prop))) {
|
|
70
|
+
// Handle numeric indices
|
|
71
|
+
return this.getCurrentItems()[prop];
|
|
72
|
+
}
|
|
73
|
+
return target[prop];
|
|
74
|
+
},
|
|
75
|
+
}), "f");
|
|
76
|
+
return __classPrivateFieldGet(this, _LiveQueryset_proxy, "f");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Serializes the lqs as a simple array of objects, for freezing e.g in the metric stores
|
|
80
|
+
*/
|
|
81
|
+
serialize() {
|
|
82
|
+
const store = querysetStoreRegistry.getStore(__classPrivateFieldGet(this, _LiveQueryset_queryset, "f"));
|
|
83
|
+
// Get the current primary keys from the store
|
|
84
|
+
const pks = store.render();
|
|
85
|
+
// Map primary keys to full model objects
|
|
86
|
+
return pks.map((pk) => {
|
|
87
|
+
// Get the full model instance from the model store
|
|
88
|
+
const pkField = __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").primaryKeyField;
|
|
89
|
+
return __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").fromPk(pk, __classPrivateFieldGet(this, _LiveQueryset_queryset, "f")).serialize();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Refresh the queryset data from the database
|
|
94
|
+
* Delegates to the underlying store's sync method
|
|
95
|
+
*/
|
|
96
|
+
refreshFromDb() {
|
|
97
|
+
const store = querysetStoreRegistry.getStore(__classPrivateFieldGet(this, _LiveQueryset_queryset, "f"));
|
|
98
|
+
return store.sync(true);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get the current items from the store
|
|
102
|
+
* @private
|
|
103
|
+
* @returns {Array} The current items in the queryset
|
|
104
|
+
*/
|
|
105
|
+
getCurrentItems() {
|
|
106
|
+
const store = querysetStoreRegistry.getStore(__classPrivateFieldGet(this, _LiveQueryset_queryset, "f"));
|
|
107
|
+
// Get the current primary keys from the store
|
|
108
|
+
const pks = store.render();
|
|
109
|
+
// Map primary keys to full model objects
|
|
110
|
+
const instances = pks
|
|
111
|
+
.map((pk) => {
|
|
112
|
+
// Get the full model instance from the model store
|
|
113
|
+
const pkField = __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").primaryKeyField;
|
|
114
|
+
return __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").fromPk(pk, __classPrivateFieldGet(this, _LiveQueryset_queryset, "f"));
|
|
115
|
+
});
|
|
116
|
+
return instances;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
_LiveQueryset_queryset = new WeakMap(), _LiveQueryset_ModelClass = new WeakMap(), _LiveQueryset_proxy = new WeakMap(), _LiveQueryset_array = new WeakMap();
|
|
120
|
+
export class QuerysetStoreRegistry {
|
|
121
|
+
constructor() {
|
|
122
|
+
this._stores = new Map(); // Map<semanticKey, Store>
|
|
123
|
+
this._tempStores = new WeakMap(); // WeakMap<Queryset, Store>
|
|
124
|
+
this.followingQuerysets = new Map(); // Map<semanticKey, Set<queryset>>
|
|
125
|
+
this.syncManager = () => { console.warn("SyncManager not set for QuerysetStoreRegistry"); };
|
|
126
|
+
this.querysetStoreGraph = new QuerysetStoreGraph();
|
|
127
|
+
// Cache for groupSync coordination
|
|
128
|
+
// Map<operationId, { promise, resolve, rootKey, pks, ModelClass }>
|
|
129
|
+
this._groupSyncCache = new Map();
|
|
130
|
+
}
|
|
131
|
+
clear() {
|
|
132
|
+
this._stores.forEach((store) => {
|
|
133
|
+
this.syncManager.unfollowModel(this, store.modelClass);
|
|
134
|
+
});
|
|
135
|
+
this._stores = new Map();
|
|
136
|
+
this.followingQuerysets = new Map();
|
|
137
|
+
this.querysetStoreGraph.clear();
|
|
138
|
+
}
|
|
139
|
+
setSyncManager(syncManager) {
|
|
140
|
+
this.syncManager = syncManager;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Add a queryset to the following set for a semantic key
|
|
144
|
+
*/
|
|
145
|
+
addFollowingQueryset(semanticKey, queryset) {
|
|
146
|
+
if (!this.followingQuerysets.has(semanticKey)) {
|
|
147
|
+
this.followingQuerysets.set(semanticKey, new Set());
|
|
148
|
+
}
|
|
149
|
+
this.followingQuerysets.get(semanticKey).add(queryset);
|
|
150
|
+
}
|
|
151
|
+
getStore(queryset) {
|
|
152
|
+
if (isNil(queryset) || isNil(queryset.ModelClass)) {
|
|
153
|
+
throw new Error("QuerysetStoreRegistry.getStore requires a valid queryset");
|
|
154
|
+
}
|
|
155
|
+
this.querysetStoreGraph.addQueryset(queryset);
|
|
156
|
+
// Check if we already have a temporary store for this exact QuerySet instance
|
|
157
|
+
if (this._tempStores.has(queryset)) {
|
|
158
|
+
return this._tempStores.get(queryset);
|
|
159
|
+
}
|
|
160
|
+
// Get the semanticKey
|
|
161
|
+
const semanticKey = queryset.semanticKey;
|
|
162
|
+
// Check if we have a permanent store with this semanticKey
|
|
163
|
+
if (this._stores.has(semanticKey)) {
|
|
164
|
+
this.addFollowingQueryset(semanticKey, queryset);
|
|
165
|
+
return this._stores.get(semanticKey);
|
|
166
|
+
}
|
|
167
|
+
// Check if any parent in the chain has a temp store with the same semantic key
|
|
168
|
+
// This ensures that materialized querysets (created via fetch/create/etc) share
|
|
169
|
+
// the same store as their parent queryset when they have the same semantic key
|
|
170
|
+
let current = queryset.__parent;
|
|
171
|
+
while (current) {
|
|
172
|
+
if (this._tempStores.has(current) && current.semanticKey === semanticKey) {
|
|
173
|
+
return this._tempStores.get(current);
|
|
174
|
+
}
|
|
175
|
+
current = current.__parent;
|
|
176
|
+
}
|
|
177
|
+
// Create a new temporary store
|
|
178
|
+
const fetchQueryset = async ({ ast, modelClass, canonical_id }) => {
|
|
179
|
+
// Directly assemble the request and call the API to avoid recursive logic from the
|
|
180
|
+
// queryset back to the registry / store
|
|
181
|
+
const payload = {
|
|
182
|
+
...ast,
|
|
183
|
+
type: 'list'
|
|
184
|
+
};
|
|
185
|
+
const response = await makeApiCall(queryset, 'list', payload, null, // operationId
|
|
186
|
+
null, // beforeExit
|
|
187
|
+
canonical_id, // canonical_id for caching
|
|
188
|
+
{ namespace: 'sync', timeout: 30000 } // Sync ops on separate queue
|
|
189
|
+
);
|
|
190
|
+
return response.data;
|
|
191
|
+
};
|
|
192
|
+
const store = new QuerysetStore(queryset.ModelClass, fetchQueryset, queryset, null, // No initial ground truth - will render from model store if needed
|
|
193
|
+
null, // Initial operations
|
|
194
|
+
{ isTemp: true });
|
|
195
|
+
// Store it in the temp store map
|
|
196
|
+
this._tempStores.set(queryset, store);
|
|
197
|
+
return store;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get the current state of the queryset, wrapped in a LiveQueryset
|
|
201
|
+
* @param {Object} queryset - The queryset
|
|
202
|
+
* @param {Boolean} sync - Schedule a sync of the queryset with the backend
|
|
203
|
+
* @returns {LiveQueryset} - A live view of the queryset
|
|
204
|
+
*/
|
|
205
|
+
getEntity(queryset, sync = false) {
|
|
206
|
+
if (isNil(queryset))
|
|
207
|
+
throw new Error(`qsStoreRegistry: getEntity cannot be called without a queryset`);
|
|
208
|
+
const semanticKey = queryset.semanticKey;
|
|
209
|
+
this.addFollowingQueryset(semanticKey, queryset);
|
|
210
|
+
let store;
|
|
211
|
+
// If we have a temporary store, promote it
|
|
212
|
+
if (this._tempStores.has(queryset)) {
|
|
213
|
+
store = this._tempStores.get(queryset);
|
|
214
|
+
store.isTemp = false; // Promote to permanent store
|
|
215
|
+
store.registerWithModelStore(); // Register for model store changes now that it's permanent
|
|
216
|
+
this._stores.set(semanticKey, store);
|
|
217
|
+
this.syncManager.followModel(this, queryset.ModelClass);
|
|
218
|
+
}
|
|
219
|
+
// Otherwise, ensure we have a permanent store
|
|
220
|
+
else if (!this._stores.has(semanticKey)) {
|
|
221
|
+
store = this.getStore(queryset);
|
|
222
|
+
store.isTemp = false;
|
|
223
|
+
store.registerWithModelStore(); // Register for model store changes now that it's permanent
|
|
224
|
+
this._stores.set(semanticKey, store);
|
|
225
|
+
this.syncManager.followModel(this, queryset.ModelClass);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
store = this._stores.get(semanticKey);
|
|
229
|
+
}
|
|
230
|
+
const liveQueryset = new LiveQueryset(queryset);
|
|
231
|
+
if (sync)
|
|
232
|
+
store.sync();
|
|
233
|
+
return wrapReactiveQuerySet(liveQueryset);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Set ground truth for a queryset
|
|
237
|
+
* @param {Object} queryset - The queryset
|
|
238
|
+
* @param {Array} instances - Array of instances to set as ground truth
|
|
239
|
+
* @returns {Array} - The set instances
|
|
240
|
+
*/
|
|
241
|
+
setEntity(queryset, instances) {
|
|
242
|
+
if (isNil(queryset) || isNil(instances))
|
|
243
|
+
return [];
|
|
244
|
+
const semanticKey = queryset.semanticKey;
|
|
245
|
+
this.addFollowingQueryset(semanticKey, queryset);
|
|
246
|
+
let store;
|
|
247
|
+
if (this._stores.has(semanticKey)) {
|
|
248
|
+
store = this._stores.get(semanticKey);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
// If we have a temp store, promote it
|
|
252
|
+
if (this._tempStores.has(queryset)) {
|
|
253
|
+
store = this._tempStores.get(queryset);
|
|
254
|
+
store.isTemp = false; // Promote to permanent store
|
|
255
|
+
store.registerWithModelStore(); // Register for model store changes now that it's permanent
|
|
256
|
+
this._stores.set(semanticKey, store);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
// Create a new permanent store
|
|
260
|
+
store = this.getStore(queryset);
|
|
261
|
+
store.isTemp = false;
|
|
262
|
+
store.registerWithModelStore(); // Register for model store changes now that it's permanent
|
|
263
|
+
this._stores.set(semanticKey, store);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
store.setGroundTruth(instances.map(instance => instance[queryset.ModelClass.primaryKeyField] || instance));
|
|
267
|
+
return instances;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get all queryset stores for a specific model class
|
|
271
|
+
* @param {ModelClass} ModelClass - The model class to get stores for
|
|
272
|
+
* @returns {Array} - Array of queryset stores for this model
|
|
273
|
+
*/
|
|
274
|
+
getAllStoresForModel(ModelClass) {
|
|
275
|
+
if (!ModelClass)
|
|
276
|
+
return [];
|
|
277
|
+
return Array.from(this._stores.values()).filter(store => store.modelClass === ModelClass);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Sync a queryset, coordinating with its chain to minimize DB calls.
|
|
281
|
+
* The root fetches from DB, children filter from cached results.
|
|
282
|
+
* Uses operationId to coordinate - whoever arrives first creates the promise,
|
|
283
|
+
* the root takes over and resolves it.
|
|
284
|
+
*
|
|
285
|
+
* @param {Object} queryset - The queryset to sync
|
|
286
|
+
* @param {string} operationId - Unique ID for this sync operation (for coordination)
|
|
287
|
+
* @param {Set} dbSyncedKeys - Set of semanticKeys that are dbSynced (followedQuerysets)
|
|
288
|
+
*/
|
|
289
|
+
async groupSync(queryset, operationId, dbSyncedKeys) {
|
|
290
|
+
if (isNil(queryset))
|
|
291
|
+
return;
|
|
292
|
+
const semanticKey = queryset.semanticKey;
|
|
293
|
+
const ModelClass = queryset.ModelClass;
|
|
294
|
+
// Convert dbSyncedKeys to semanticKeys if needed
|
|
295
|
+
const subset = new Set();
|
|
296
|
+
for (const item of dbSyncedKeys) {
|
|
297
|
+
subset.add(typeof item === 'string' ? item : item?.semanticKey);
|
|
298
|
+
}
|
|
299
|
+
// Find the dbSynced root
|
|
300
|
+
const { isRoot, root: rootKey } = this.querysetStoreGraph.findRoot(queryset, subset);
|
|
301
|
+
const iAmRoot = isRoot || rootKey === semanticKey;
|
|
302
|
+
// Get or create cache entry - whoever arrives first creates it
|
|
303
|
+
if (!this._groupSyncCache.has(operationId)) {
|
|
304
|
+
let resolve;
|
|
305
|
+
const promise = new Promise(r => { resolve = r; });
|
|
306
|
+
this._groupSyncCache.set(operationId, { promise, resolve, pks: null, ModelClass });
|
|
307
|
+
setTimeout(() => this._groupSyncCache.delete(operationId), 5000);
|
|
308
|
+
}
|
|
309
|
+
const cached = this._groupSyncCache.get(operationId);
|
|
310
|
+
const store = this._stores.get(semanticKey);
|
|
311
|
+
if (!store) {
|
|
312
|
+
console.warn(`[groupSync] No store found for queryset: ${semanticKey}`);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (iAmRoot) {
|
|
316
|
+
// I'm the root - sync from DB (store handles everything)
|
|
317
|
+
await store.sync();
|
|
318
|
+
cached.pks = store.groundTruthPks;
|
|
319
|
+
cached.resolve();
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
// Wait for root to finish
|
|
323
|
+
await cached.promise;
|
|
324
|
+
// Filter from cached root data
|
|
325
|
+
const rootInstances = cached.pks.map(pk => ModelClass.fromPk(pk, queryset));
|
|
326
|
+
const ast = queryset.build();
|
|
327
|
+
const filteredPks = filter(rootInstances, ast, ModelClass, false);
|
|
328
|
+
// Set ground truth and clean up inflight ops (like sync does)
|
|
329
|
+
store.setGroundTruth(filteredPks);
|
|
330
|
+
store.setOperations(store.getInflightOperations());
|
|
331
|
+
store.lastSync = Date.now();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
export const querysetStoreRegistry = new QuerysetStoreRegistry();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store for managing a single metric with optimistic updates
|
|
3
|
+
*/
|
|
4
|
+
export class MetricStore {
|
|
5
|
+
constructor(metricType: any, modelClass: any, queryset: any, field: null | undefined, ast: null | undefined, fetchFn: any);
|
|
6
|
+
metricType: any;
|
|
7
|
+
modelClass: any;
|
|
8
|
+
queryset: any;
|
|
9
|
+
field: any;
|
|
10
|
+
ast: any;
|
|
11
|
+
fetchFn: any;
|
|
12
|
+
groundTruthValue: any;
|
|
13
|
+
isSyncing: boolean;
|
|
14
|
+
strategy: import("../metrics/metricOptCalcs.js").MetricCalculationStrategy;
|
|
15
|
+
operations: any[];
|
|
16
|
+
confirmedOps: Set<any>;
|
|
17
|
+
metricCache: Cache;
|
|
18
|
+
_lastCalculatedValue: any;
|
|
19
|
+
reset(): void;
|
|
20
|
+
get cacheKey(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Add an operation to this metric store
|
|
23
|
+
* @param {Operation} operation - The operation to add
|
|
24
|
+
*/
|
|
25
|
+
addOperation(operation: Operation): void;
|
|
26
|
+
/**
|
|
27
|
+
* Update an operation in this metric store
|
|
28
|
+
* @param {Operation} operation - The operation to update
|
|
29
|
+
*/
|
|
30
|
+
updateOperation(operation: Operation): void;
|
|
31
|
+
/**
|
|
32
|
+
* Confirm an operation in this metric store
|
|
33
|
+
* @param {Operation} operation - The operation to confirm
|
|
34
|
+
*/
|
|
35
|
+
confirm(operation: Operation): void;
|
|
36
|
+
/**
|
|
37
|
+
* Reject an operation in this metric store
|
|
38
|
+
* @param {Operation} operation - The operation to reject
|
|
39
|
+
*/
|
|
40
|
+
reject(operation: Operation): void;
|
|
41
|
+
onHydrated(): void;
|
|
42
|
+
setCache(): void;
|
|
43
|
+
clearCache(): void;
|
|
44
|
+
setGroundTruth(value: any): void;
|
|
45
|
+
/**
|
|
46
|
+
* Render the metric with current operations
|
|
47
|
+
* @returns {any} Calculated metric value
|
|
48
|
+
*/
|
|
49
|
+
render(): any;
|
|
50
|
+
/**
|
|
51
|
+
* Sync metric with server
|
|
52
|
+
*/
|
|
53
|
+
sync(): Promise<any>;
|
|
54
|
+
}
|
|
55
|
+
import { Cache } from '../cache/cache.js';
|