@statezero/core 0.1.3-9.1 → 0.1.3-9.2

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.
@@ -8,6 +8,8 @@ export class ModelStore {
8
8
  pruneThreshold: any;
9
9
  modelCache: Cache;
10
10
  _lastRenderedData: Map<any, any>;
11
+ renderCallbacks: Set<any>;
12
+ registerRenderCallback(callback: any): () => boolean;
11
13
  /**
12
14
  * Load operations from data and add them to the operations map,
13
15
  * reusing existing operations from the registry if they exist
@@ -16,6 +16,14 @@ const emitEvents = (store, events) => {
16
16
  if (!isEqual(newRenderedData, lastRenderedData)) {
17
17
  store._lastRenderedData.set(pk, newRenderedData);
18
18
  modelEventEmitter.emit(`${store.modelClass.configKey}::${store.modelClass.modelName}::render`, event);
19
+ store.renderCallbacks.forEach((callback) => {
20
+ try {
21
+ callback();
22
+ }
23
+ catch (error) {
24
+ console.warn("Error in model store render callback:", error);
25
+ }
26
+ });
19
27
  }
20
28
  });
21
29
  };
@@ -71,15 +79,20 @@ export class ModelStore {
71
79
  if (initialOperations && initialOperations.length > 0) {
72
80
  this._loadOperations(initialOperations);
73
81
  }
74
- this.modelCache = new Cache('model-cache', {}, this.onHydrated.bind(this));
82
+ this.modelCache = new Cache("model-cache", {}, this.onHydrated.bind(this));
75
83
  this._lastRenderedData = new Map();
84
+ this.renderCallbacks = new Set();
85
+ }
86
+ registerRenderCallback(callback) {
87
+ this.renderCallbacks.add(callback);
88
+ return () => this.renderCallbacks.delete(callback);
76
89
  }
77
90
  /**
78
91
  * Load operations from data and add them to the operations map,
79
92
  * reusing existing operations from the registry if they exist
80
93
  */
81
94
  _loadOperations(operationsData) {
82
- operationsData.forEach(opData => {
95
+ operationsData.forEach((opData) => {
83
96
  const existingOp = operationRegistry.get(opData.operationId);
84
97
  if (existingOp) {
85
98
  // If the operation exists in the registry, use it
@@ -109,7 +122,7 @@ export class ModelStore {
109
122
  let nonTempPkItems = [];
110
123
  result.forEach((item) => {
111
124
  let pk = item[pkField];
112
- if (typeof pk === 'string' && containsTempPk(pk)) {
125
+ if (typeof pk === "string" && containsTempPk(pk)) {
113
126
  pk = replaceTempPks(item[pkField]);
114
127
  if (isNil(pk) || isEmpty(trim(pk))) {
115
128
  return;
@@ -128,7 +141,7 @@ export class ModelStore {
128
141
  let nonTempPkItems = [];
129
142
  items.forEach((item) => {
130
143
  let pk = item[pkField];
131
- if (typeof pk === 'string' && containsTempPk(pk)) {
144
+ if (typeof pk === "string" && containsTempPk(pk)) {
132
145
  pk = replaceTempPks(item[pkField]);
133
146
  if (isNil(pk) || isEmpty(trim(pk))) {
134
147
  return;
@@ -145,16 +158,16 @@ export class ModelStore {
145
158
  // Otherwise, we're rendering specific items - update only those items
146
159
  const currentCache = this.modelCache.get(this.cacheKey) || [];
147
160
  // Filter out items that were requested but not in the result (they were deleted)
148
- const filteredCache = currentCache.filter(item => item &&
149
- typeof item === 'object' &&
161
+ const filteredCache = currentCache.filter((item) => item &&
162
+ typeof item === "object" &&
150
163
  pkField in item &&
151
164
  (!requestedPks.has(item[pkField]) ||
152
- nonTempPkItems.some(newItem => newItem[pkField] === item[pkField])));
165
+ nonTempPkItems.some((newItem) => newItem[pkField] === item[pkField])));
153
166
  // Create a map for faster lookups
154
- const cacheMap = new Map(filteredCache.map(item => [item[pkField], item]));
167
+ const cacheMap = new Map(filteredCache.map((item) => [item[pkField], item]));
155
168
  // Add or update items from the result
156
169
  for (const item of nonTempPkItems) {
157
- if (item && typeof item === 'object' && pkField in item) {
170
+ if (item && typeof item === "object" && pkField in item) {
158
171
  cacheMap.set(item[pkField], item);
159
172
  }
160
173
  }
@@ -199,7 +212,7 @@ export class ModelStore {
199
212
  setOperations(operations = []) {
200
213
  const prevOps = this.operations;
201
214
  this.operationsMap.clear();
202
- operations.forEach(op => {
215
+ operations.forEach((op) => {
203
216
  this.operationsMap.set(op.operationId, op);
204
217
  });
205
218
  const allOps = [...prevOps, ...this.operations];
@@ -219,16 +232,16 @@ export class ModelStore {
219
232
  get groundTruthPks() {
220
233
  const pk = this.pkField;
221
234
  return this.groundTruthArray
222
- .filter(instance => instance && typeof instance === 'object' && pk in instance)
223
- .map(instance => instance[pk]);
235
+ .filter((instance) => instance && typeof instance === "object" && pk in instance)
236
+ .map((instance) => instance[pk]);
224
237
  }
225
238
  addToGroundTruth(instances) {
226
239
  if (!Array.isArray(instances) || instances.length === 0)
227
240
  return;
228
241
  const pkField = this.pkField;
229
242
  const pkMap = new Map();
230
- instances.forEach(inst => {
231
- if (inst && typeof inst === 'object' && pkField in inst) {
243
+ instances.forEach((inst) => {
244
+ if (inst && typeof inst === "object" && pkField in inst) {
232
245
  pkMap.set(inst[pkField], inst);
233
246
  }
234
247
  else {
@@ -241,7 +254,9 @@ export class ModelStore {
241
254
  const processedPks = new Set();
242
255
  const checkpointInstances = []; // Track instances that need CHECKPOINT operations
243
256
  for (const existingItem of this.groundTruthArray) {
244
- if (!existingItem || typeof existingItem !== 'object' || !(pkField in existingItem)) {
257
+ if (!existingItem ||
258
+ typeof existingItem !== "object" ||
259
+ !(pkField in existingItem)) {
245
260
  continue;
246
261
  }
247
262
  const pk = existingItem[pkField];
@@ -263,12 +278,14 @@ export class ModelStore {
263
278
  // Create CHECKPOINT operation for instances that already existed
264
279
  if (checkpointInstances.length > 0) {
265
280
  const checkpointOperation = new Operation({
266
- operationId: `checkpoint_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
281
+ operationId: `checkpoint_${Date.now()}_${Math.random()
282
+ .toString(36)
283
+ .substr(2, 9)}`,
267
284
  type: Type.CHECKPOINT,
268
285
  instances: checkpointInstances,
269
286
  status: Status.CONFIRMED,
270
287
  timestamp: Date.now(),
271
- queryset: this.modelClass.objects.all()
288
+ queryset: this.modelClass.objects.all(),
272
289
  });
273
290
  this.operationsMap.set(checkpointOperation.operationId, checkpointOperation);
274
291
  console.log(`[ModelStore ${this.modelClass.modelName}] Created CHECKPOINT operation for ${checkpointInstances.length} existing instances`);
@@ -282,7 +299,7 @@ export class ModelStore {
282
299
  const pkField = this.pkField;
283
300
  let filteredOps = [];
284
301
  for (const op of operations) {
285
- let relevantInstances = op.instances.filter(instance => pks.has(instance[pkField] || instance));
302
+ let relevantInstances = op.instances.filter((instance) => pks.has(instance[pkField] || instance));
286
303
  if (relevantInstances.length > 0) {
287
304
  filteredOps.push({
288
305
  operationId: op.operationId,
@@ -291,7 +308,7 @@ export class ModelStore {
291
308
  queryset: op.queryset,
292
309
  type: op.type,
293
310
  status: op.status,
294
- args: op.args
311
+ args: op.args,
295
312
  });
296
313
  }
297
314
  }
@@ -301,7 +318,7 @@ export class ModelStore {
301
318
  const pkField = this.pkField;
302
319
  let groundTruthMap = new Map();
303
320
  for (const instance of groundTruthArray) {
304
- if (!instance || typeof instance !== 'object' || !(pkField in instance)) {
321
+ if (!instance || typeof instance !== "object" || !(pkField in instance)) {
305
322
  continue;
306
323
  }
307
324
  const pk = instance[pkField];
@@ -314,7 +331,7 @@ export class ModelStore {
314
331
  applyOperation(operation, currentInstances) {
315
332
  const pkField = this.pkField;
316
333
  for (const instance of operation.instances) {
317
- if (!instance || typeof instance !== 'object' || !(pkField in instance)) {
334
+ if (!instance || typeof instance !== "object" || !(pkField in instance)) {
318
335
  console.warn(`[ModelStore ${this.modelClass.modelName}] Skipping instance ${instance} in operation ${operation.operationId} during applyOperation due to missing PK field '${String(pkField)}' or invalid format.`);
319
336
  continue;
320
337
  }
@@ -333,9 +350,9 @@ export class ModelStore {
333
350
  currentInstances.set(pk, { ...existing, ...instance });
334
351
  }
335
352
  else {
336
- const wasDeletedLocally = this.operations.some(op => op.type === Type.DELETE &&
353
+ const wasDeletedLocally = this.operations.some((op) => op.type === Type.DELETE &&
337
354
  op.status !== Status.REJECTED &&
338
- op.instances.some(inst => inst && inst[pkField] === pk));
355
+ op.instances.some((inst) => inst && inst[pkField] === pk));
339
356
  if (!wasDeletedLocally) {
340
357
  currentInstances.set(pk, instance);
341
358
  }
@@ -354,10 +371,10 @@ export class ModelStore {
354
371
  }
355
372
  getTrimmedOperations() {
356
373
  const twoMinutesAgo = Date.now() - 1000 * 60 * 2;
357
- return this.operations.filter(operation => operation.timestamp > twoMinutesAgo);
374
+ return this.operations.filter((operation) => operation.timestamp > twoMinutesAgo);
358
375
  }
359
376
  getInflightOperations() {
360
- return this.operations.filter(operation => operation.status != Status.CONFIRMED &&
377
+ return this.operations.filter((operation) => operation.status != Status.CONFIRMED &&
361
378
  operation.status != Status.REJECTED);
362
379
  }
363
380
  // Pruning
@@ -369,12 +386,16 @@ export class ModelStore {
369
386
  }
370
387
  // Render methods
371
388
  render(pks = null, optimistic = true) {
372
- const pksSet = pks === null ? null :
373
- (pks instanceof Set ? pks : new Set(Array.isArray(pks) ? pks : [pks]));
389
+ const pksSet = pks === null
390
+ ? null
391
+ : pks instanceof Set
392
+ ? pks
393
+ : new Set(Array.isArray(pks) ? pks : [pks]);
374
394
  const renderedInstancesMap = this._filteredGroundTruth(pksSet, this.groundTruthArray);
375
395
  const relevantOperations = this._filteredOperations(pksSet, this.operations);
376
396
  for (const op of relevantOperations) {
377
- if (op.status !== Status.REJECTED && (optimistic || op.status === Status.CONFIRMED)) {
397
+ if (op.status !== Status.REJECTED &&
398
+ (optimistic || op.status === Status.CONFIRMED)) {
378
399
  this.applyOperation(op, renderedInstancesMap);
379
400
  }
380
401
  }
@@ -397,7 +418,10 @@ export class ModelStore {
397
418
  this.setOperations(trimmedOps);
398
419
  return;
399
420
  }
400
- const newGroundTruth = await this.fetchFn({ pks: currentPks, modelClass: this.modelClass });
421
+ const newGroundTruth = await this.fetchFn({
422
+ pks: currentPks,
423
+ modelClass: this.modelClass,
424
+ });
401
425
  if (pks) {
402
426
  this.addToGroundTruth(newGroundTruth);
403
427
  return;
@@ -16,6 +16,7 @@ export class QuerysetStore {
16
16
  renderCallbacks: Set<any>;
17
17
  _rootUnregister: any;
18
18
  _currentRootStore: any;
19
+ _modelStoreUnregister: any;
19
20
  get cacheKey(): any;
20
21
  onHydrated(hydratedData: any): void;
21
22
  setCache(result: any): void;
@@ -34,6 +34,10 @@ export class QuerysetStore {
34
34
  this._rootUnregister = null;
35
35
  this._currentRootStore = null;
36
36
  this._ensureRootRegistration();
37
+ const modelStore = modelStoreRegistry.getStore(this.modelClass);
38
+ this._modelStoreUnregister = modelStore.registerRenderCallback(() => {
39
+ this._emitRenderEvent();
40
+ });
37
41
  }
38
42
  // Caching
39
43
  get cacheKey() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statezero/core",
3
- "version": "0.1.39.1",
3
+ "version": "0.1.39.2",
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",