@statezero/core 0.2.28 → 0.2.29
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.js +1 -1
- package/dist/actions/backend1/django_app/calculate-hash.schema.json +1 -1
- package/dist/actions/backend1/django_app/get-current-username.js +1 -1
- package/dist/actions/backend1/django_app/get-current-username.schema.json +1 -1
- package/dist/actions/backend1/django_app/get-user-info.js +1 -1
- package/dist/actions/backend1/django_app/get-user-info.schema.json +1 -1
- package/dist/actions/backend1/django_app/process-data.js +1 -1
- package/dist/actions/backend1/django_app/process-data.schema.json +1 -1
- package/dist/actions/backend1/django_app/send-notification.js +1 -1
- package/dist/actions/backend1/django_app/send-notification.schema.json +1 -1
- package/dist/actions/default/django_app/calculate-hash.js +1 -1
- package/dist/actions/default/django_app/calculate-hash.schema.json +1 -1
- package/dist/actions/default/django_app/get-current-username.js +1 -1
- package/dist/actions/default/django_app/get-current-username.schema.json +1 -1
- package/dist/actions/default/django_app/get-user-info.js +1 -1
- package/dist/actions/default/django_app/get-user-info.schema.json +1 -1
- package/dist/actions/default/django_app/process-data.js +1 -1
- package/dist/actions/default/django_app/process-data.schema.json +1 -1
- package/dist/actions/default/django_app/send-notification.js +1 -1
- package/dist/actions/default/django_app/send-notification.schema.json +1 -1
- package/dist/models/backend1/django_app/comprehensivemodel.schema.json +1 -1
- package/dist/models/backend1/django_app/custompkmodel.schema.json +4 -4
- package/dist/models/backend1/django_app/dailyrate.schema.json +8 -8
- package/dist/models/backend1/django_app/dummymodel.schema.json +2 -2
- package/dist/models/backend1/django_app/m2mdepthtestlevel1.schema.json +2 -2
- package/dist/models/backend1/django_app/m2mdepthtestlevel2.schema.json +1 -1
- package/dist/models/backend1/django_app/m2mdepthtestlevel3.schema.json +5 -5
- package/dist/models/backend1/django_app/modelwithrestrictedfields.schema.json +1 -1
- package/dist/models/backend1/django_app/namefiltercustompkmodel.schema.json +4 -4
- package/dist/models/backend1/django_app/order.schema.json +8 -8
- package/dist/models/backend1/django_app/orderitem.schema.json +1 -1
- package/dist/models/backend1/django_app/product.schema.json +9 -9
- package/dist/models/backend1/django_app/productcategory.schema.json +2 -2
- package/dist/models/backend1/django_app/rateplan.schema.json +2 -2
- package/dist/models/backend1/django_app/restrictedfieldrelatedmodel.schema.json +2 -2
- package/dist/models/default/django_app/comprehensivemodel.schema.json +1 -1
- package/dist/models/default/django_app/custompkmodel.schema.json +4 -4
- package/dist/models/default/django_app/dailyrate.schema.json +8 -8
- package/dist/models/default/django_app/dummymodel.schema.json +2 -2
- package/dist/models/default/django_app/m2mdepthtestlevel1.schema.json +2 -2
- package/dist/models/default/django_app/m2mdepthtestlevel2.schema.json +1 -1
- package/dist/models/default/django_app/m2mdepthtestlevel3.schema.json +5 -5
- package/dist/models/default/django_app/modelwithrestrictedfields.schema.json +1 -1
- package/dist/models/default/django_app/namefiltercustompkmodel.schema.json +4 -4
- package/dist/models/default/django_app/order.schema.json +8 -8
- package/dist/models/default/django_app/orderitem.schema.json +1 -1
- package/dist/models/default/django_app/product.schema.json +9 -9
- package/dist/models/default/django_app/productcategory.schema.json +2 -2
- package/dist/models/default/django_app/rateplan.schema.json +2 -2
- package/dist/models/default/django_app/restrictedfieldrelatedmodel.schema.json +2 -2
- package/dist/syncEngine/registries/querysetStoreGraph.d.ts +15 -5
- package/dist/syncEngine/registries/querysetStoreGraph.js +64 -22
- package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +14 -10
- package/dist/syncEngine/registries/querysetStoreRegistry.js +64 -39
- package/dist/syncEngine/stores/operationEventHandlers.js +12 -20
- package/dist/syncEngine/stores/querysetStore.d.ts +9 -11
- package/dist/syncEngine/stores/querysetStore.js +34 -100
- package/dist/syncEngine/sync.js +18 -10
- package/package.json +1 -1
|
@@ -4,10 +4,8 @@ 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';
|
|
7
|
-
import hash from 'object-hash';
|
|
8
7
|
import { Cache } from '../cache/cache.js';
|
|
9
8
|
import { filter } from "../../filtering/localFiltering.js";
|
|
10
|
-
import { mod } from 'mathjs';
|
|
11
9
|
export class QuerysetStore {
|
|
12
10
|
constructor(modelClass, fetchFn, queryset, initialGroundTruthPks = null, initialOperations = null, options = {}) {
|
|
13
11
|
this.modelClass = modelClass;
|
|
@@ -17,7 +15,6 @@ export class QuerysetStore {
|
|
|
17
15
|
this.lastSync = null;
|
|
18
16
|
this.isTemp = options.isTemp || false;
|
|
19
17
|
this.pruneThreshold = options.pruneThreshold || 10;
|
|
20
|
-
this.getRootStore = options.getRootStore || null;
|
|
21
18
|
this.groundTruthPks = initialGroundTruthPks || [];
|
|
22
19
|
this.operationsMap = new Map();
|
|
23
20
|
// Track which model PKs are in this queryset's included data
|
|
@@ -33,9 +30,7 @@ export class QuerysetStore {
|
|
|
33
30
|
this.qsCache = new Cache("queryset-cache", {}, this.onHydrated.bind(this));
|
|
34
31
|
this._lastRenderedPks = null;
|
|
35
32
|
this.renderCallbacks = new Set();
|
|
36
|
-
|
|
37
|
-
this._currentRootStore = null;
|
|
38
|
-
this._ensureRootRegistration();
|
|
33
|
+
// Register for model store changes to re-render when model data changes
|
|
39
34
|
const modelStore = modelStoreRegistry.getStore(this.modelClass);
|
|
40
35
|
this._modelStoreUnregister = modelStore.registerRenderCallback(() => {
|
|
41
36
|
this._emitRenderEvent();
|
|
@@ -155,28 +150,6 @@ export class QuerysetStore {
|
|
|
155
150
|
this.renderCallbacks.add(callback);
|
|
156
151
|
return () => this.renderCallbacks.delete(callback);
|
|
157
152
|
}
|
|
158
|
-
_ensureRootRegistration() {
|
|
159
|
-
if (this.isTemp)
|
|
160
|
-
return;
|
|
161
|
-
const { isRoot, rootStore } = this.getRootStore(this.queryset);
|
|
162
|
-
// If the root store hasn't changed, nothing to do
|
|
163
|
-
if (this._currentRootStore === rootStore) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
// Root store changed - clean up old registration if it exists
|
|
167
|
-
if (this._rootUnregister) {
|
|
168
|
-
this._rootUnregister();
|
|
169
|
-
this._rootUnregister = null;
|
|
170
|
-
}
|
|
171
|
-
// Set up new registration if we're derived and have a root store
|
|
172
|
-
if (!isRoot && rootStore) {
|
|
173
|
-
this._rootUnregister = rootStore.registerRenderCallback(() => {
|
|
174
|
-
this._emitRenderEvent();
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
// Update current root store reference (could be null now)
|
|
178
|
-
this._currentRootStore = rootStore;
|
|
179
|
-
}
|
|
180
153
|
/**
|
|
181
154
|
* Helper to validate PKs against the model store and apply local filtering/sorting.
|
|
182
155
|
* This is the core of the rendering logic.
|
|
@@ -193,67 +166,29 @@ export class QuerysetStore {
|
|
|
193
166
|
return finalPks;
|
|
194
167
|
}
|
|
195
168
|
render(optimistic = true, fromCache = false) {
|
|
196
|
-
|
|
169
|
+
// Check cache first if requested
|
|
197
170
|
if (fromCache) {
|
|
198
171
|
const cachedResult = this.qsCache.get(this.cacheKey);
|
|
199
172
|
if (Array.isArray(cachedResult)) {
|
|
200
173
|
return cachedResult;
|
|
201
174
|
}
|
|
202
175
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!isRoot && rootStore && rootStore.lastSync !== null) {
|
|
211
|
-
pks = this.renderFromRoot(optimistic, rootStore);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
// For temp stores with no ground truth (e.g., chained optimistic filters),
|
|
215
|
-
// render from the model store instead of empty ground truth
|
|
216
|
-
if (isNil(pks) && this.isTemp && this.groundTruthPks.length === 0) {
|
|
217
|
-
pks = this.renderFromModelStore(optimistic);
|
|
218
|
-
}
|
|
219
|
-
if (isNil(pks)) {
|
|
220
|
-
pks = this.renderFromData(optimistic);
|
|
221
|
-
}
|
|
176
|
+
// If no ground truth AND hasn't been synced, render from model store
|
|
177
|
+
// This handles chained optimistic filters, newly created stores, etc.
|
|
178
|
+
// (If synced with empty results, that's valid ground truth)
|
|
179
|
+
const pks = this.groundTruthPks.length === 0 && this.lastSync === null
|
|
180
|
+
? this.renderFromModelStore()
|
|
181
|
+
: this.renderFromData(optimistic);
|
|
182
|
+
// Validate against model store and apply local filtering/sorting
|
|
222
183
|
let result = this._getValidatedAndFilteredPks(pks);
|
|
223
|
-
|
|
184
|
+
// Apply pagination limit
|
|
185
|
+
const limit = this.queryset.build().serializerOptions?.limit;
|
|
224
186
|
if (limit) {
|
|
225
187
|
result = result.slice(0, limit);
|
|
226
188
|
}
|
|
227
189
|
this.setCache(result);
|
|
228
190
|
return result;
|
|
229
191
|
}
|
|
230
|
-
renderFromRoot(optimistic = true, rootStore) {
|
|
231
|
-
let renderedPks = rootStore.render(optimistic);
|
|
232
|
-
let renderedData = renderedPks.map((pk) => {
|
|
233
|
-
return this.modelClass.fromPk(pk, this.queryset);
|
|
234
|
-
});
|
|
235
|
-
let ast = this.queryset.build();
|
|
236
|
-
let result = filter(renderedData, ast, this.modelClass, false);
|
|
237
|
-
return result;
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Render by getting ALL instances from the model store and applying
|
|
241
|
-
* the queryset's filters locally. Used for temp stores (e.g., optimistic
|
|
242
|
-
* chained filters) that don't have their own ground truth.
|
|
243
|
-
*/
|
|
244
|
-
renderFromModelStore(optimistic = true) {
|
|
245
|
-
const modelStore = modelStoreRegistry.getStore(this.modelClass);
|
|
246
|
-
// Get all PKs from the model store
|
|
247
|
-
const allPks = modelStore.groundTruthPks;
|
|
248
|
-
// Convert to model instances (like renderFromRoot does)
|
|
249
|
-
const allInstances = allPks.map((pk) => {
|
|
250
|
-
return this.modelClass.fromPk(pk, this.queryset);
|
|
251
|
-
});
|
|
252
|
-
// Apply the queryset's AST filters locally
|
|
253
|
-
const ast = this.queryset.build();
|
|
254
|
-
const result = filter(allInstances, ast, this.modelClass, false);
|
|
255
|
-
return result;
|
|
256
|
-
}
|
|
257
192
|
renderFromData(optimistic = true) {
|
|
258
193
|
const renderedPks = this.groundTruthSet;
|
|
259
194
|
for (const op of this.operations) {
|
|
@@ -265,6 +200,17 @@ export class QuerysetStore {
|
|
|
265
200
|
let result = Array.from(renderedPks);
|
|
266
201
|
return result;
|
|
267
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Render by getting all instances from the model store and filtering locally.
|
|
205
|
+
* Used when a queryset has no ground truth (temp stores, newly created stores, etc.)
|
|
206
|
+
*/
|
|
207
|
+
renderFromModelStore() {
|
|
208
|
+
const modelStore = modelStoreRegistry.getStore(this.modelClass);
|
|
209
|
+
const allPks = modelStore.groundTruthPks;
|
|
210
|
+
const allInstances = allPks.map((pk) => this.modelClass.fromPk(pk, this.queryset));
|
|
211
|
+
const ast = this.queryset.build();
|
|
212
|
+
return filter(allInstances, ast, this.modelClass, false);
|
|
213
|
+
}
|
|
268
214
|
applyOperation(operation, currentPks) {
|
|
269
215
|
const pkField = this.pkField;
|
|
270
216
|
for (const instance of operation.instances) {
|
|
@@ -292,26 +238,16 @@ export class QuerysetStore {
|
|
|
292
238
|
}
|
|
293
239
|
return currentPks;
|
|
294
240
|
}
|
|
295
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Sync this queryset with the database.
|
|
243
|
+
* Fetches from DB and sets ground truth.
|
|
244
|
+
*/
|
|
245
|
+
async sync() {
|
|
296
246
|
const id = this.modelClass.modelName;
|
|
297
247
|
if (this.isSyncing) {
|
|
298
248
|
console.warn(`[QuerysetStore ${id}] Already syncing, request ignored.`);
|
|
299
249
|
return;
|
|
300
250
|
}
|
|
301
|
-
// Check if we're delegating to a root store
|
|
302
|
-
if (!forceFromDb &&
|
|
303
|
-
this.getRootStore &&
|
|
304
|
-
typeof this.getRootStore === "function" &&
|
|
305
|
-
!this.isTemp) {
|
|
306
|
-
const { isRoot, rootStore } = this.getRootStore(this.queryset);
|
|
307
|
-
if (!isRoot && rootStore) {
|
|
308
|
-
// We're delegating to a root store - don't sync, just mark as needing sync
|
|
309
|
-
console.log(`[${id}] Delegating to root store.`);
|
|
310
|
-
this.setOperations(this.getInflightOperations());
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
// We're in independent mode - proceed with normal sync
|
|
315
251
|
this.isSyncing = true;
|
|
316
252
|
console.log(`[${id}] Starting sync...`);
|
|
317
253
|
try {
|
|
@@ -320,16 +256,14 @@ export class QuerysetStore {
|
|
|
320
256
|
modelClass: this.modelClass,
|
|
321
257
|
});
|
|
322
258
|
const { data, included } = response;
|
|
323
|
-
if (isNil(data)) {
|
|
324
|
-
|
|
259
|
+
if (!isNil(data)) {
|
|
260
|
+
console.log(`[${id}] Sync fetch completed. Received: ${JSON.stringify(data)}.`);
|
|
261
|
+
// Clear previous included PKs tracking before processing new data
|
|
262
|
+
this.includedPks.clear();
|
|
263
|
+
// Persist all instances (including nested) to the model store
|
|
264
|
+
processIncludedEntities(modelStoreRegistry, included, this.modelClass, this.queryset);
|
|
265
|
+
this.setGroundTruth(data);
|
|
325
266
|
}
|
|
326
|
-
console.log(`[${id}] Sync fetch completed. Received: ${JSON.stringify(data)}.`);
|
|
327
|
-
// Clear previous included PKs tracking before processing new data
|
|
328
|
-
this.includedPks.clear();
|
|
329
|
-
// Persists all the instances (including nested instances) to the model store
|
|
330
|
-
// Pass this queryset to track which PKs are in the included data
|
|
331
|
-
processIncludedEntities(modelStoreRegistry, included, this.modelClass, this.queryset);
|
|
332
|
-
this.setGroundTruth(data);
|
|
333
267
|
this.setOperations(this.getInflightOperations());
|
|
334
268
|
this.lastSync = Date.now();
|
|
335
269
|
console.log(`[${id}] Sync completed.`);
|
package/dist/syncEngine/sync.js
CHANGED
|
@@ -9,6 +9,7 @@ import { getModelClass, getConfig } from "../config.js";
|
|
|
9
9
|
import { isNil } from "lodash-es";
|
|
10
10
|
import { QuerysetStore } from "./stores/querysetStore.js";
|
|
11
11
|
import PQueue from "p-queue";
|
|
12
|
+
import { v7 as uuidv7 } from "uuid";
|
|
12
13
|
export class EventPayload {
|
|
13
14
|
constructor(data) {
|
|
14
15
|
this.event = data.event;
|
|
@@ -157,16 +158,19 @@ export class SyncManager {
|
|
|
157
158
|
}
|
|
158
159
|
syncStaleQuerysets() {
|
|
159
160
|
let syncedCount = 0;
|
|
160
|
-
// Sync all followed querysets - keep it simple
|
|
161
161
|
const querysetRegistry = this.registries.get(QuerysetStoreRegistry);
|
|
162
|
-
if (querysetRegistry)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
if (!querysetRegistry)
|
|
163
|
+
return;
|
|
164
|
+
// Generate operationId for this sync batch - querysets in same chain will coordinate
|
|
165
|
+
const operationId = `periodic-sync-${uuidv7()}`;
|
|
166
|
+
// Get dbSynced keys (followed querysets)
|
|
167
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets].map(qs => qs.semanticKey));
|
|
168
|
+
for (const [semanticKey, store] of querysetRegistry._stores.entries()) {
|
|
169
|
+
// Only sync if this store is actually being followed
|
|
170
|
+
const isFollowed = this.isStoreFollowed(querysetRegistry, semanticKey);
|
|
171
|
+
if (this.followAllQuerysets || isFollowed) {
|
|
172
|
+
this.syncQueue.add(() => this.withTimeout(querysetRegistry.groupSync(store.queryset, operationId, dbSyncedKeys)));
|
|
173
|
+
syncedCount++;
|
|
170
174
|
}
|
|
171
175
|
}
|
|
172
176
|
if (syncedCount > 0) {
|
|
@@ -308,8 +312,12 @@ export class SyncManager {
|
|
|
308
312
|
}
|
|
309
313
|
// Sync all relevant stores for this model
|
|
310
314
|
console.log(`[SyncManager] Syncing ${storesToSync.length} queryset stores for ${representativeEvent.model}`);
|
|
315
|
+
// Generate operationId for this batch - querysets in same chain will coordinate
|
|
316
|
+
const operationId = `remote-event-${uuidv7()}`;
|
|
317
|
+
// Get dbSynced keys (followed querysets)
|
|
318
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets].map(qs => qs.semanticKey));
|
|
311
319
|
storesToSync.forEach((store) => {
|
|
312
|
-
this.syncQueue.add(() => this.withTimeout(store.
|
|
320
|
+
this.syncQueue.add(() => this.withTimeout(registry.groupSync(store.queryset, operationId, dbSyncedKeys)));
|
|
313
321
|
});
|
|
314
322
|
}
|
|
315
323
|
processMetrics(event) {
|
package/package.json
CHANGED