@statezero/core 0.1.18 → 0.1.19
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/syncEngine/metrics/metricOptCalcs.d.ts +3 -3
- package/dist/syncEngine/registries/querysetStoreRegistry.js +2 -0
- package/dist/syncEngine/stores/operationEventHandlers.js +10 -15
- package/dist/syncEngine/stores/querysetSemanticGraph.d.ts +21 -0
- package/dist/syncEngine/stores/querysetSemanticGraph.js +58 -0
- package/dist/syncEngine/stores/querysetStore.js +2 -0
- package/package.json +2 -1
|
@@ -48,8 +48,8 @@ export class MaxStrategy extends MetricCalculationStrategy {
|
|
|
48
48
|
* Factory class for creating the appropriate strategy
|
|
49
49
|
*/
|
|
50
50
|
export class MetricStrategyFactory {
|
|
51
|
-
static "__#
|
|
52
|
-
static "__#
|
|
51
|
+
static "__#7@#customStrategies": Map<any, any>;
|
|
52
|
+
static "__#7@#defaultStrategies": Map<string, () => CountStrategy>;
|
|
53
53
|
/**
|
|
54
54
|
* Clear all custom strategy overrides
|
|
55
55
|
*/
|
|
@@ -61,7 +61,7 @@ export class MetricStrategyFactory {
|
|
|
61
61
|
* @param {Function} ModelClass - The model class
|
|
62
62
|
* @returns {string} A unique key
|
|
63
63
|
*/
|
|
64
|
-
private static "__#
|
|
64
|
+
private static "__#7@#generateStrategyKey";
|
|
65
65
|
/**
|
|
66
66
|
* Override a strategy for a specific metric type and model class
|
|
67
67
|
* @param {string} metricType - The type of metric (count, sum, min, max)
|
|
@@ -20,6 +20,7 @@ import { isNil, pick } from 'lodash-es';
|
|
|
20
20
|
import hash from 'object-hash';
|
|
21
21
|
import { Operation } from '../stores/operation.js';
|
|
22
22
|
import { Cache } from '../cache/cache.js';
|
|
23
|
+
import { querysetGraph } from '../stores/querysetSemanticGraph.js';
|
|
23
24
|
/**
|
|
24
25
|
* A dynamic wrapper that always returns the latest queryset results
|
|
25
26
|
* This class proxies array operations to always reflect the current state
|
|
@@ -113,6 +114,7 @@ class QuerysetStoreRegistry {
|
|
|
113
114
|
});
|
|
114
115
|
this._stores = new Map();
|
|
115
116
|
this.followingQuerysets = new Map();
|
|
117
|
+
querysetGraph.clear();
|
|
116
118
|
}
|
|
117
119
|
setSyncManager(syncManager) {
|
|
118
120
|
this.syncManager = syncManager;
|
|
@@ -4,6 +4,7 @@ import { querysetStoreRegistry } from '../registries/querysetStoreRegistry.js';
|
|
|
4
4
|
import { metricRegistry } from '../registries/metricRegistry.js';
|
|
5
5
|
import { getFingerprint } from './utils.js';
|
|
6
6
|
import { QuerySet } from '../../flavours/django/querySet.js';
|
|
7
|
+
import { querysetGraph } from './querysetSemanticGraph.js';
|
|
7
8
|
import { isEqual, isNil } from 'lodash-es';
|
|
8
9
|
import hash from 'object-hash';
|
|
9
10
|
/**
|
|
@@ -12,27 +13,21 @@ import hash from 'object-hash';
|
|
|
12
13
|
* @returns {Map<QuerySet, Store>}
|
|
13
14
|
*/
|
|
14
15
|
function relatedQuerysets(queryset) {
|
|
15
|
-
// Collect ancestor nodes for comparison
|
|
16
|
-
let ancestorNodes = [];
|
|
17
|
-
let current = queryset;
|
|
18
|
-
while (current) {
|
|
19
|
-
ancestorNodes.push(current.nodes);
|
|
20
|
-
current = current.__parent;
|
|
21
|
-
}
|
|
22
|
-
const modelClass = queryset.ModelClass;
|
|
23
16
|
const result = new Map();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
// Use the graph to get semantic ancestors instead of manual traversal
|
|
18
|
+
const semanticAncestors = querysetGraph.getSemanticAncestors(queryset);
|
|
19
|
+
// Convert querysets to Map<QuerySet, Store>
|
|
20
|
+
for (const qs of semanticAncestors) {
|
|
27
21
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
const store = querysetStoreRegistry.getStore(qs);
|
|
23
|
+
if (store) {
|
|
24
|
+
result.set(qs, store);
|
|
30
25
|
}
|
|
31
26
|
}
|
|
32
27
|
catch (e) {
|
|
33
|
-
console.warn('Error
|
|
28
|
+
console.warn('Error getting store for related queryset', e);
|
|
34
29
|
}
|
|
35
|
-
}
|
|
30
|
+
}
|
|
36
31
|
return result;
|
|
37
32
|
}
|
|
38
33
|
/**
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const querysetGraph: QuerysetGraph;
|
|
2
|
+
/**
|
|
3
|
+
* Simple graph using graphlib: AST nodes with parent edges
|
|
4
|
+
*/
|
|
5
|
+
declare class QuerysetGraph {
|
|
6
|
+
graph: any;
|
|
7
|
+
clear(): void;
|
|
8
|
+
add(queryset: any): void;
|
|
9
|
+
/**
|
|
10
|
+
* Gets all querysets that share semantic equivalence with ancestors of the given queryset.
|
|
11
|
+
*
|
|
12
|
+
* This traverses up the parent chain to find all ancestor nodes (based on semanticId),
|
|
13
|
+
* then returns all querysets stored in those nodes that match the same model class.
|
|
14
|
+
* Querysets with the same semanticId are considered semantically equivalent (same AST structure).
|
|
15
|
+
*
|
|
16
|
+
* @param {QuerySet} queryset - The queryset to find semantic ancestors for
|
|
17
|
+
* @returns {QuerySet[]} Array of querysets that are semantically equivalent to ancestors
|
|
18
|
+
*/
|
|
19
|
+
getSemanticAncestors(queryset: QuerySet): QuerySet[];
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Graph, alg } from "graphlib";
|
|
2
|
+
/**
|
|
3
|
+
* Simple graph using graphlib: AST nodes with parent edges
|
|
4
|
+
*/
|
|
5
|
+
class QuerysetGraph {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.graph = new Graph({ directed: true });
|
|
8
|
+
}
|
|
9
|
+
clear() {
|
|
10
|
+
this.graph = new Graph({ directed: true });
|
|
11
|
+
}
|
|
12
|
+
add(queryset) {
|
|
13
|
+
const astId = queryset.semanticId;
|
|
14
|
+
// Get or create node data with Set
|
|
15
|
+
let nodeData = this.graph.node(astId);
|
|
16
|
+
if (!nodeData) {
|
|
17
|
+
nodeData = { querysets: new Set() };
|
|
18
|
+
this.graph.setNode(astId, nodeData);
|
|
19
|
+
}
|
|
20
|
+
// Add queryset to Set (automatically handles duplicates)
|
|
21
|
+
nodeData.querysets.add(queryset);
|
|
22
|
+
// Add parent edge if exists
|
|
23
|
+
if (queryset.__parent) {
|
|
24
|
+
const parentAstId = queryset.__parent.semanticId;
|
|
25
|
+
this.graph.setEdge(astId, parentAstId);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Gets all querysets that share semantic equivalence with ancestors of the given queryset.
|
|
30
|
+
*
|
|
31
|
+
* This traverses up the parent chain to find all ancestor nodes (based on semanticId),
|
|
32
|
+
* then returns all querysets stored in those nodes that match the same model class.
|
|
33
|
+
* Querysets with the same semanticId are considered semantically equivalent (same AST structure).
|
|
34
|
+
*
|
|
35
|
+
* @param {QuerySet} queryset - The queryset to find semantic ancestors for
|
|
36
|
+
* @returns {QuerySet[]} Array of querysets that are semantically equivalent to ancestors
|
|
37
|
+
*/
|
|
38
|
+
getSemanticAncestors(queryset) {
|
|
39
|
+
const modelClass = queryset.ModelClass;
|
|
40
|
+
const result = [];
|
|
41
|
+
// Use graphlib to get all ancestors (including the node itself)
|
|
42
|
+
const ancestorNodeIds = alg.postorder(this.graph, [queryset.semanticId]);
|
|
43
|
+
// Collect all querysets from ancestor nodes
|
|
44
|
+
for (const nodeId of ancestorNodeIds) {
|
|
45
|
+
const nodeData = this.graph.node(nodeId);
|
|
46
|
+
if (!nodeData || !nodeData.querysets)
|
|
47
|
+
continue;
|
|
48
|
+
// Add all querysets from this node that match the model class
|
|
49
|
+
for (const qs of nodeData.querysets) {
|
|
50
|
+
if (qs.ModelClass === modelClass) {
|
|
51
|
+
result.push(qs);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export const querysetGraph = new QuerysetGraph();
|
|
@@ -4,6 +4,7 @@ import { isNil, isEmpty, trim } from 'lodash-es';
|
|
|
4
4
|
import { replaceTempPks, containsTempPk } from '../../flavours/django/tempPk.js';
|
|
5
5
|
import { modelStoreRegistry } from '../registries/modelStoreRegistry.js';
|
|
6
6
|
import { processIncludedEntities } from '../../flavours/django/makeApiCall.js';
|
|
7
|
+
import { querysetGraph } from "./querysetSemanticGraph.js";
|
|
7
8
|
import hash from 'object-hash';
|
|
8
9
|
import { Cache } from '../cache/cache.js';
|
|
9
10
|
export class QuerysetStore {
|
|
@@ -23,6 +24,7 @@ export class QuerysetStore {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
this.qsCache = new Cache('queryset-cache', {}, this.onHydrated.bind(this));
|
|
27
|
+
querysetGraph.add(queryset);
|
|
26
28
|
}
|
|
27
29
|
// Caching
|
|
28
30
|
get cacheKey() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statezero/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "ESNext",
|
|
6
6
|
"description": "The type-safe frontend client for StateZero - connect directly to your backend models with zero boilerplate",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"date-fns": "^4.1.0",
|
|
75
75
|
"dotenv": "^16.4.7",
|
|
76
76
|
"fs-extra": "^11.3.0",
|
|
77
|
+
"graphlib": "^2.1.8",
|
|
77
78
|
"handlebars": "^4.7.8",
|
|
78
79
|
"idb": "^8.0.2",
|
|
79
80
|
"inquirer": "^12.4.2",
|