@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.
@@ -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 "__#5@#customStrategies": Map<any, any>;
52
- static "__#5@#defaultStrategies": Map<string, () => CountStrategy>;
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 "__#5@#generateStrategyKey";
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
- Array.from(querysetStoreRegistry._stores.entries()).forEach(([queryset, store]) => {
25
- if (store.modelClass !== modelClass)
26
- return;
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
- if (ancestorNodes.some(nodes => isEqual(nodes, store.queryset.nodes))) {
29
- result.set(store.queryset, store);
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 comparing nodes for related querysets', e);
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.18",
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",