@statezero/core 0.1.24 → 0.1.25

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.
@@ -7,6 +7,7 @@ export class ModelStore {
7
7
  isSyncing: boolean;
8
8
  pruneThreshold: any;
9
9
  modelCache: Cache;
10
+ _lastRenderedData: Map<any, any>;
10
11
  /**
11
12
  * Load operations from data and add them to the operations map,
12
13
  * reusing existing operations from the registry if they exist
@@ -1,12 +1,22 @@
1
1
  import { Operation, Status, Type, operationRegistry } from './operation.js';
2
- import { isNil, isEmpty, trim } from 'lodash-es';
2
+ import { isNil, isEmpty, trim, isEqual } from 'lodash-es';
3
3
  import { modelEventEmitter } from './reactivity.js';
4
4
  import { Cache } from '../cache/cache.js';
5
5
  import { replaceTempPks, containsTempPk } from '../../flavours/django/tempPk.js';
6
- const emitEvents = (modelClass, events) => {
7
- // helper method to emit every event
8
- events.forEach(event => {
9
- modelEventEmitter.emit(`${modelClass.configKey}::${modelClass.modelName}::render`, event);
6
+ const emitEvents = (store, events) => {
7
+ if (!Array.isArray(events))
8
+ return;
9
+ events.forEach((event) => {
10
+ const pk = event.pk;
11
+ if (isNil(pk))
12
+ return;
13
+ const newRenderedDataArray = store.render([pk], true);
14
+ const newRenderedData = newRenderedDataArray.length > 0 ? newRenderedDataArray[0] : null;
15
+ const lastRenderedData = store._lastRenderedData.get(pk);
16
+ if (!isEqual(newRenderedData, lastRenderedData)) {
17
+ store._lastRenderedData.set(pk, newRenderedData);
18
+ modelEventEmitter.emit(`${store.modelClass.configKey}::${store.modelClass.modelName}::render`, event);
19
+ }
10
20
  });
11
21
  };
12
22
  class EventData {
@@ -62,6 +72,7 @@ export class ModelStore {
62
72
  this._loadOperations(initialOperations);
63
73
  }
64
74
  this.modelCache = new Cache('model-cache', {}, this.onHydrated.bind(this));
75
+ this._lastRenderedData = new Map();
65
76
  }
66
77
  /**
67
78
  * Load operations from data and add them to the operations map,
@@ -164,26 +175,26 @@ export class ModelStore {
164
175
  if (this.operationsMap.size > this.pruneThreshold) {
165
176
  this.prune();
166
177
  }
167
- emitEvents(this.modelClass, EventData.fromOperation(operation));
178
+ emitEvents(this, EventData.fromOperation(operation));
168
179
  }
169
180
  updateOperation(operation) {
170
181
  if (!this.operationsMap.has(operation.operationId))
171
182
  return false;
172
183
  this.operationsMap.set(operation.operationId, operation);
173
- emitEvents(this.modelClass, EventData.fromOperation(operation));
184
+ emitEvents(this, EventData.fromOperation(operation));
174
185
  return true;
175
186
  }
176
187
  confirm(operation) {
177
188
  if (!this.operationsMap.has(operation.operationId))
178
189
  return;
179
190
  this.operationsMap.set(operation.operationId, operation);
180
- emitEvents(this.modelClass, EventData.fromOperation(operation));
191
+ emitEvents(this, EventData.fromOperation(operation));
181
192
  }
182
193
  reject(operation) {
183
194
  if (!this.operationsMap.has(operation.operationId))
184
195
  return;
185
196
  this.operationsMap.set(operation.operationId, operation);
186
- emitEvents(this.modelClass, EventData.fromOperation(operation));
197
+ emitEvents(this, EventData.fromOperation(operation));
187
198
  }
188
199
  setOperations(operations = []) {
189
200
  const prevOps = this.operations;
@@ -192,7 +203,7 @@ export class ModelStore {
192
203
  this.operationsMap.set(op.operationId, op);
193
204
  });
194
205
  const allOps = [...prevOps, ...this.operations];
195
- emitEvents(this.modelClass, EventData.fromOperations(allOps));
206
+ emitEvents(this, EventData.fromOperations(allOps));
196
207
  }
197
208
  // Ground truth data methods
198
209
  setGroundTruth(groundTruth) {
@@ -200,7 +211,7 @@ export class ModelStore {
200
211
  this.groundTruthArray = Array.isArray(groundTruth) ? groundTruth : [];
201
212
  // reactivity - gather all ops
202
213
  const allOps = [...prevGroundTruth, ...this.groundTruthArray];
203
- emitEvents(this.modelClass, EventData.fromInstances(allOps, this.modelClass));
214
+ emitEvents(this, EventData.fromInstances(allOps, this.modelClass));
204
215
  }
205
216
  getGroundTruth() {
206
217
  return this.groundTruthArray;
@@ -263,7 +274,7 @@ export class ModelStore {
263
274
  console.log(`[ModelStore ${this.modelClass.modelName}] Created CHECKPOINT operation for ${checkpointInstances.length} existing instances`);
264
275
  }
265
276
  // reactivity - use all the newly added instances (both new and updated)
266
- emitEvents(this.modelClass, EventData.fromInstances([...checkpointInstances, ...Array.from(pkMap.values())], this.modelClass));
277
+ emitEvents(this, EventData.fromInstances([...checkpointInstances, ...Array.from(pkMap.values())], this.modelClass));
267
278
  }
268
279
  _filteredOperations(pks, operations) {
269
280
  if (!pks)
@@ -12,6 +12,7 @@ export class QuerysetStore {
12
12
  pruneThreshold: any;
13
13
  getRootStore: any;
14
14
  qsCache: Cache;
15
+ _lastRenderedPks: any[] | null;
15
16
  get cacheKey(): any;
16
17
  onHydrated(hydratedData: any): void;
17
18
  setCache(result: any): void;
@@ -1,6 +1,6 @@
1
1
  import { Operation, Status, Type, operationRegistry } from './operation.js';
2
2
  import { querysetEventEmitter } from './reactivity.js';
3
- import { isNil, isEmpty, trim } from 'lodash-es';
3
+ import { isNil, isEmpty, trim, isEqual } 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';
@@ -29,6 +29,7 @@ export class QuerysetStore {
29
29
  }
30
30
  }
31
31
  this.qsCache = new Cache("queryset-cache", {}, this.onHydrated.bind(this));
32
+ this._lastRenderedPks = null;
32
33
  }
33
34
  // Caching
34
35
  get cacheKey() {
@@ -70,7 +71,12 @@ export class QuerysetStore {
70
71
  return new Set(this.groundTruthPks);
71
72
  }
72
73
  _emitRenderEvent() {
73
- querysetEventEmitter.emit(`${this.modelClass.configKey}::${this.modelClass.modelName}::queryset::render`, { ast: this.queryset.build(), ModelClass: this.modelClass });
74
+ const newPks = this.render(true, false); // Get current state without using cache
75
+ // Directly compare PK lists. isEqual performs a deep, order-sensitive comparison.
76
+ if (!isEqual(newPks, this._lastRenderedPks)) {
77
+ this._lastRenderedPks = newPks; // Update the cache with the new state
78
+ querysetEventEmitter.emit(`${this.modelClass.configKey}::${this.modelClass.modelName}::queryset::render`, { ast: this.queryset.build(), ModelClass: this.modelClass });
79
+ }
74
80
  }
75
81
  async addOperation(operation) {
76
82
  this.operationsMap.set(operation.operationId, operation);
@@ -132,7 +138,9 @@ export class QuerysetStore {
132
138
  }
133
139
  }
134
140
  let result;
135
- if (this.getRootStore && typeof this.getRootStore === "function" && !this.isTemp) {
141
+ if (this.getRootStore &&
142
+ typeof this.getRootStore === "function" &&
143
+ !this.isTemp) {
136
144
  const { isRoot, rootStore } = this.getRootStore(this.queryset);
137
145
  if (!isRoot && rootStore && rootStore.lastSync) {
138
146
  result = this.renderFromRoot(optimistic, rootStore);
@@ -201,7 +209,9 @@ export class QuerysetStore {
201
209
  return;
202
210
  }
203
211
  // Check if we're delegating to a root store
204
- if (this.getRootStore && typeof this.getRootStore === "function" && !this.isTemp) {
212
+ if (this.getRootStore &&
213
+ typeof this.getRootStore === "function" &&
214
+ !this.isTemp) {
205
215
  const { isRoot, rootStore } = this.getRootStore(this.queryset);
206
216
  if (!isRoot && rootStore && rootStore.lastSync) {
207
217
  // We're delegating to a root store - don't sync, just mark as needing sync
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statezero/core",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
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",