@statezero/core 0.1.70 → 0.1.72

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.
@@ -22,6 +22,8 @@ export class SyncManager {
22
22
  debounceMs: number;
23
23
  maxWaitMs: number;
24
24
  batchStartTime: number | null;
25
+ syncQueue: PQueue<import("p-queue/dist/priority-queue").default, import("p-queue").QueueAddOptions>;
26
+ withTimeout(promise: any, ms: any): Promise<any>;
25
27
  /**
26
28
  * Initialize event handlers for all event receivers
27
29
  */
@@ -41,4 +43,5 @@ export class SyncManager {
41
43
  processMetrics(event: any): void;
42
44
  processModels(event: any): void;
43
45
  }
46
+ import PQueue from "p-queue";
44
47
  export const syncManager: SyncManager;
@@ -8,6 +8,7 @@ import { metricRegistry, MetricRegistry } from "./registries/metricRegistry";
8
8
  import { getModelClass, getConfig } from "../config";
9
9
  import { isNil } from "lodash-es";
10
10
  import { QuerysetStore } from "./stores/querysetStore";
11
+ import PQueue from "p-queue";
11
12
  export class EventPayload {
12
13
  constructor(data) {
13
14
  this.event = data.event;
@@ -85,6 +86,25 @@ export class SyncManager {
85
86
  this.debounceMs = 100; // Wait for rapid events to settle
86
87
  this.maxWaitMs = 2000; // Maximum time to hold events
87
88
  this.batchStartTime = null;
89
+ // SyncQueue
90
+ this.syncQueue = new PQueue({ concurrency: 3 });
91
+ }
92
+ withTimeout(promise, ms) {
93
+ // If no timeout specified, use 2x the periodic sync interval, or 30s as fallback
94
+ if (!ms) {
95
+ try {
96
+ const config = getConfig();
97
+ const intervalSeconds = config.periodicSyncIntervalSeconds;
98
+ ms = intervalSeconds ? intervalSeconds * 2000 : 30000; // 2x interval in ms, or 30s default
99
+ }
100
+ catch {
101
+ ms = 30000; // 30s fallback if no config
102
+ }
103
+ }
104
+ return Promise.race([
105
+ promise,
106
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Sync timeout after ${ms}ms`)), ms)),
107
+ ]);
88
108
  }
89
109
  /**
90
110
  * Initialize event handlers for all event receivers
@@ -134,13 +154,13 @@ export class SyncManager {
134
154
  // Only sync if this store is actually being followed
135
155
  const isFollowed = this.isStoreFollowed(querysetRegistry, semanticKey);
136
156
  if (this.followAllQuerysets || isFollowed) {
137
- store.sync();
157
+ this.syncQueue.add(() => this.withTimeout(store.sync()));
138
158
  syncedCount++;
139
159
  }
140
160
  }
141
161
  }
142
162
  if (syncedCount > 0) {
143
- console.log(`[SyncManager] Periodic sync: ${syncedCount} stores synced`);
163
+ console.log(`[SyncManager] Periodic sync: ${syncedCount} stores pushed to the sync queue`);
144
164
  }
145
165
  }
146
166
  isStoreFollowed(registry, semanticKey) {
@@ -267,10 +287,7 @@ export class SyncManager {
267
287
  // Sync all relevant stores for this model
268
288
  console.log(`[SyncManager] Syncing ${storesToSync.length} queryset stores for ${representativeEvent.model}`);
269
289
  storesToSync.forEach((store) => {
270
- // Don't await - let them run in parallel
271
- store.sync().catch((error) => {
272
- console.error(`[SyncManager] Failed to sync queryset store:`, error);
273
- });
290
+ this.syncQueue.add(() => this.withTimeout(store.sync()));
274
291
  });
275
292
  }
276
293
  processMetrics(event) {
@@ -293,32 +310,7 @@ export class SyncManager {
293
310
  }
294
311
  }
295
312
  processModels(event) {
296
- const registry = this.registries.get(ModelStoreRegistry);
297
- if (!registry)
298
- return;
299
- // Get the model class for this event
300
- const modelClass = event.modelClass;
301
- if (!modelClass)
302
- return;
303
- // Get the model store for this model class
304
- const modelStore = registry.getStore(modelClass);
305
- if (!modelStore)
306
- return;
307
- // Event instances are just PKs - find which ones we have locally
308
- const eventPks = event.instances || [];
309
- // Get all currently rendered instances (includes ground truth + operations)
310
- const renderedInstances = modelStore.render();
311
- const localPks = new Set(renderedInstances.map((instance) => instance[modelClass.primaryKeyField]));
312
- const pksToSync = eventPks.filter((pk) => localPks.has(pk));
313
- if (pksToSync.length === 0) {
314
- console.log(`[SyncManager] No locally cached instances to sync for ${event.model}`);
315
- return;
316
- }
317
- // Sync only the PKs that are both in the event and locally cached
318
- console.log(`[SyncManager] Syncing ${pksToSync.length} model instances for ${event.model}`);
319
- modelStore.sync(pksToSync).catch((error) => {
320
- console.error(`[SyncManager] Failed to sync model store for ${event.model}:`, error);
321
- });
313
+ return;
322
314
  }
323
315
  }
324
316
  const syncManager = new SyncManager();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statezero/core",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
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",