@statezero/core 0.2.28 → 0.2.30
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/flavours/django/makeApiCall.d.ts +14 -1
- package/dist/flavours/django/makeApiCall.js +31 -3
- 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 +66 -40
- 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.d.ts +1 -4
- package/dist/syncEngine/sync.js +27 -21
- package/package.json +1 -1
|
@@ -9,13 +9,10 @@ export class QuerysetStore {
|
|
|
9
9
|
lastSync: number | null;
|
|
10
10
|
isTemp: any;
|
|
11
11
|
pruneThreshold: any;
|
|
12
|
-
getRootStore: any;
|
|
13
12
|
includedPks: Map<any, any>;
|
|
14
13
|
qsCache: Cache;
|
|
15
14
|
_lastRenderedPks: any[] | null;
|
|
16
15
|
renderCallbacks: Set<any>;
|
|
17
|
-
_rootUnregister: any;
|
|
18
|
-
_currentRootStore: any;
|
|
19
16
|
_modelStoreUnregister: any;
|
|
20
17
|
get cacheKey(): any;
|
|
21
18
|
onHydrated(hydratedData: any): void;
|
|
@@ -35,7 +32,6 @@ export class QuerysetStore {
|
|
|
35
32
|
getInflightOperations(): any[];
|
|
36
33
|
prune(): void;
|
|
37
34
|
registerRenderCallback(callback: any): () => boolean;
|
|
38
|
-
_ensureRootRegistration(): void;
|
|
39
35
|
/**
|
|
40
36
|
* Helper to validate PKs against the model store and apply local filtering/sorting.
|
|
41
37
|
* This is the core of the rendering logic.
|
|
@@ -43,15 +39,17 @@ export class QuerysetStore {
|
|
|
43
39
|
*/
|
|
44
40
|
private _getValidatedAndFilteredPks;
|
|
45
41
|
render(optimistic?: boolean, fromCache?: boolean): any[];
|
|
46
|
-
|
|
42
|
+
renderFromData(optimistic?: boolean): any[];
|
|
47
43
|
/**
|
|
48
|
-
* Render by getting
|
|
49
|
-
*
|
|
50
|
-
* chained filters) that don't have their own ground truth.
|
|
44
|
+
* Render by getting all instances from the model store and filtering locally.
|
|
45
|
+
* Used when a queryset has no ground truth (temp stores, newly created stores, etc.)
|
|
51
46
|
*/
|
|
52
|
-
renderFromModelStore(
|
|
53
|
-
renderFromData(optimistic?: boolean): any[];
|
|
47
|
+
renderFromModelStore(): any[];
|
|
54
48
|
applyOperation(operation: any, currentPks: any): any;
|
|
55
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Sync this queryset with the database.
|
|
51
|
+
* Fetches from DB and sets ground truth.
|
|
52
|
+
*/
|
|
53
|
+
sync(): Promise<void>;
|
|
56
54
|
}
|
|
57
55
|
import { Cache } from '../cache/cache.js';
|
|
@@ -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.`);
|
|
@@ -22,15 +22,13 @@ export class SyncManager {
|
|
|
22
22
|
debounceMs: number;
|
|
23
23
|
maxWaitMs: number;
|
|
24
24
|
batchStartTime: number | null;
|
|
25
|
-
/** @type {PQueue} */
|
|
26
|
-
syncQueue: PQueue;
|
|
27
25
|
withTimeout(promise: any, ms: any): Promise<any>;
|
|
28
26
|
/**
|
|
29
27
|
* Initialize event handlers for all event receivers
|
|
30
28
|
*/
|
|
31
29
|
initialize(): void;
|
|
32
30
|
startPeriodicSync(): void;
|
|
33
|
-
syncStaleQuerysets(): void
|
|
31
|
+
syncStaleQuerysets(): Promise<void>;
|
|
34
32
|
pruneUnreferencedModels(): void;
|
|
35
33
|
isStoreFollowed(registry: any, semanticKey: any): boolean;
|
|
36
34
|
cleanup(): void;
|
|
@@ -45,5 +43,4 @@ export class SyncManager {
|
|
|
45
43
|
processMetrics(event: any): void;
|
|
46
44
|
processModels(event: any): void;
|
|
47
45
|
}
|
|
48
|
-
import PQueue from "p-queue";
|
|
49
46
|
export const syncManager: SyncManager;
|
package/dist/syncEngine/sync.js
CHANGED
|
@@ -8,7 +8,7 @@ import { metricRegistry, MetricRegistry } from "./registries/metricRegistry.js";
|
|
|
8
8
|
import { getModelClass, getConfig } from "../config.js";
|
|
9
9
|
import { isNil } from "lodash-es";
|
|
10
10
|
import { QuerysetStore } from "./stores/querysetStore.js";
|
|
11
|
-
import
|
|
11
|
+
import { v7 as uuidv7 } from "uuid";
|
|
12
12
|
export class EventPayload {
|
|
13
13
|
constructor(data) {
|
|
14
14
|
this.event = data.event;
|
|
@@ -95,9 +95,6 @@ export class SyncManager {
|
|
|
95
95
|
this.debounceMs = 100; // Wait for rapid events to settle
|
|
96
96
|
this.maxWaitMs = 2000; // Maximum time to hold events
|
|
97
97
|
this.batchStartTime = null;
|
|
98
|
-
// SyncQueue
|
|
99
|
-
/** @type {PQueue} */
|
|
100
|
-
this.syncQueue = new PQueue({ concurrency: 1 });
|
|
101
98
|
}
|
|
102
99
|
withTimeout(promise, ms) {
|
|
103
100
|
// If no timeout specified, use 2x the periodic sync interval, or 30s as fallback
|
|
@@ -155,22 +152,26 @@ export class SyncManager {
|
|
|
155
152
|
console.log("[SyncManager] No config found, periodic sync disabled by default");
|
|
156
153
|
}
|
|
157
154
|
}
|
|
158
|
-
syncStaleQuerysets() {
|
|
159
|
-
let syncedCount = 0;
|
|
160
|
-
// Sync all followed querysets - keep it simple
|
|
155
|
+
async syncStaleQuerysets() {
|
|
161
156
|
const querysetRegistry = this.registries.get(QuerysetStoreRegistry);
|
|
162
|
-
if (querysetRegistry)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
157
|
+
if (!querysetRegistry)
|
|
158
|
+
return;
|
|
159
|
+
// Generate operationId for this sync batch - querysets in same chain will coordinate
|
|
160
|
+
const operationId = `periodic-sync-${uuidv7()}`;
|
|
161
|
+
// Get dbSynced keys (followed querysets)
|
|
162
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets].map(qs => qs.semanticKey));
|
|
163
|
+
// Collect all stores to sync
|
|
164
|
+
const storesToSync = [];
|
|
165
|
+
for (const [semanticKey, store] of querysetRegistry._stores.entries()) {
|
|
166
|
+
const isFollowed = this.isStoreFollowed(querysetRegistry, semanticKey);
|
|
167
|
+
if (this.followAllQuerysets || isFollowed) {
|
|
168
|
+
storesToSync.push(store);
|
|
170
169
|
}
|
|
171
170
|
}
|
|
172
|
-
if (
|
|
173
|
-
console.log(`[SyncManager] Periodic sync: ${
|
|
171
|
+
if (storesToSync.length > 0) {
|
|
172
|
+
console.log(`[SyncManager] Periodic sync: syncing ${storesToSync.length} stores`);
|
|
173
|
+
// Run all groupSync calls in parallel - they coordinate via shared promise cache
|
|
174
|
+
await Promise.all(storesToSync.map(store => querysetRegistry.groupSync(store.queryset, operationId, dbSyncedKeys)));
|
|
174
175
|
}
|
|
175
176
|
// Prune unreferenced model instances
|
|
176
177
|
this.pruneUnreferencedModels();
|
|
@@ -306,11 +307,16 @@ export class SyncManager {
|
|
|
306
307
|
}
|
|
307
308
|
}
|
|
308
309
|
}
|
|
310
|
+
if (storesToSync.length === 0)
|
|
311
|
+
return;
|
|
309
312
|
// Sync all relevant stores for this model
|
|
310
313
|
console.log(`[SyncManager] Syncing ${storesToSync.length} queryset stores for ${representativeEvent.model}`);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
+
// Generate operationId for this batch - querysets in same chain will coordinate
|
|
315
|
+
const operationId = `remote-event-${uuidv7()}`;
|
|
316
|
+
// Get dbSynced keys (followed querysets)
|
|
317
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets].map(qs => qs.semanticKey));
|
|
318
|
+
// Run all groupSync calls in parallel - they coordinate via shared promise cache
|
|
319
|
+
Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, operationId, dbSyncedKeys)));
|
|
314
320
|
}
|
|
315
321
|
processMetrics(event) {
|
|
316
322
|
const registry = this.registries.get(MetricRegistry);
|
|
@@ -372,7 +378,7 @@ export class SyncManager {
|
|
|
372
378
|
});
|
|
373
379
|
if (pksToSync.length > 0) {
|
|
374
380
|
console.log(`[SyncManager] Syncing ${pksToSync.length} nested-only PKs for ${event.model}: ${pksToSync}`);
|
|
375
|
-
|
|
381
|
+
modelStore.sync(pksToSync);
|
|
376
382
|
}
|
|
377
383
|
}
|
|
378
384
|
}
|
package/package.json
CHANGED