@statezero/core 0.2.43 → 0.2.45
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.
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* These are minimal, unstyled implementations that follow the contracts.
|
|
5
5
|
* Users can use these as-is or as reference for custom implementations.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
import AlertElement from './AlertElement.js';
|
|
8
|
+
import LabelElement from './LabelElement.js';
|
|
9
|
+
import DividerElement from './DividerElement.js';
|
|
10
|
+
import DisplayElement from './DisplayElement.js';
|
|
11
|
+
import GroupElement from './GroupElement.js';
|
|
12
|
+
import TabsElement from './TabsElement.js';
|
|
13
|
+
import ErrorBlock from './ErrorBlock.js';
|
|
14
|
+
export { AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock };
|
|
14
15
|
/**
|
|
15
16
|
* Create a default components registry (without Control - user must provide)
|
|
16
17
|
*
|
|
@@ -17,6 +17,7 @@ import { processQuery, getRequiredFields, pickRequiredFields } from '../../filte
|
|
|
17
17
|
import { filter } from '../../filtering/localFiltering.js';
|
|
18
18
|
import { makeApiCall } from '../../flavours/django/makeApiCall.js';
|
|
19
19
|
import { QuerysetStoreGraph } from './querysetStoreGraph.js';
|
|
20
|
+
import { getConfig } from '../../config.js';
|
|
20
21
|
import { isNil, pick } from 'lodash-es';
|
|
21
22
|
import hash from 'object-hash';
|
|
22
23
|
import { Operation } from '../stores/operation.js';
|
|
@@ -291,10 +292,14 @@ export class QuerysetStoreRegistry {
|
|
|
291
292
|
return;
|
|
292
293
|
const semanticKey = queryset.semanticKey;
|
|
293
294
|
const ModelClass = queryset.ModelClass;
|
|
294
|
-
// Convert dbSyncedKeys to semanticKeys
|
|
295
|
+
// Convert dbSyncedKeys to semanticKeys, filtering to only keys that have stores
|
|
296
|
+
// A key without a store can never be a valid root (nobody would sync it)
|
|
295
297
|
const subset = new Set();
|
|
296
298
|
for (const item of dbSyncedKeys) {
|
|
297
|
-
|
|
299
|
+
const key = typeof item === 'string' ? item : item?.semanticKey;
|
|
300
|
+
if (key && this._stores.has(key)) {
|
|
301
|
+
subset.add(key);
|
|
302
|
+
}
|
|
298
303
|
}
|
|
299
304
|
// Find the dbSynced root
|
|
300
305
|
const { isRoot, root: rootKey } = this.querysetStoreGraph.findRoot(queryset, subset);
|
|
@@ -319,8 +324,34 @@ export class QuerysetStoreRegistry {
|
|
|
319
324
|
cached.resolve();
|
|
320
325
|
}
|
|
321
326
|
else {
|
|
322
|
-
// Wait for root to finish
|
|
323
|
-
|
|
327
|
+
// Wait for root to finish with timeout to prevent deadlocks
|
|
328
|
+
// Use 2x periodic sync interval (same as SyncManager.withTimeout) or 30s fallback
|
|
329
|
+
let syncTimeoutMs = 30000;
|
|
330
|
+
try {
|
|
331
|
+
const config = getConfig();
|
|
332
|
+
if (config.periodicSyncIntervalSeconds) {
|
|
333
|
+
syncTimeoutMs = config.periodicSyncIntervalSeconds * 2000;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
// Use default if no config
|
|
338
|
+
}
|
|
339
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
340
|
+
setTimeout(() => reject(new Error('timeout')), syncTimeoutMs);
|
|
341
|
+
});
|
|
342
|
+
let timedOut = false;
|
|
343
|
+
try {
|
|
344
|
+
await Promise.race([cached.promise, timeoutPromise]);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
timedOut = true;
|
|
348
|
+
}
|
|
349
|
+
// Fallback to direct sync on timeout or if root data is missing
|
|
350
|
+
if (timedOut || !cached.pks) {
|
|
351
|
+
console.warn(`[groupSync] Falling back to direct sync for: ${semanticKey.substring(0, 60)}`);
|
|
352
|
+
await store.sync();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
324
355
|
// Filter from cached root data
|
|
325
356
|
const rootInstances = cached.pks.map(pk => ModelClass.fromPk(pk, queryset));
|
|
326
357
|
const ast = queryset.build();
|
package/dist/syncEngine/sync.js
CHANGED
|
@@ -161,8 +161,10 @@ export class SyncManager {
|
|
|
161
161
|
return;
|
|
162
162
|
// Generate operationId for this sync batch - querysets in same chain will coordinate
|
|
163
163
|
const operationId = `periodic-sync-${uuidv7()}`;
|
|
164
|
-
// Get dbSynced keys (followed querysets)
|
|
165
|
-
const dbSyncedKeys = new Set([...this.followedQuerysets]
|
|
164
|
+
// Get dbSynced keys (followed querysets that have stores)
|
|
165
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets]
|
|
166
|
+
.map(qs => qs.semanticKey)
|
|
167
|
+
.filter(key => querysetRegistry._stores.has(key)));
|
|
166
168
|
// Collect all stores to sync
|
|
167
169
|
const storesToSync = [];
|
|
168
170
|
for (const [semanticKey, store] of querysetRegistry._stores.entries()) {
|
|
@@ -266,7 +268,9 @@ export class SyncManager {
|
|
|
266
268
|
return;
|
|
267
269
|
console.log(`[SyncManager] Syncing ${storesToSync.length} querysets needing verification for operation ${operationId}`);
|
|
268
270
|
const syncOperationId = `maybe-sync-${uuidv7()}`;
|
|
269
|
-
const dbSyncedKeys = new Set([...this.followedQuerysets]
|
|
271
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets]
|
|
272
|
+
.map(qs => qs.semanticKey)
|
|
273
|
+
.filter(key => registry._stores.has(key)));
|
|
270
274
|
Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, syncOperationId, dbSyncedKeys)));
|
|
271
275
|
}
|
|
272
276
|
processBatch(reason = "unknown") {
|
|
@@ -345,8 +349,12 @@ export class SyncManager {
|
|
|
345
349
|
console.log(`[SyncManager] Syncing ${storesToSync.length} queryset stores for ${representativeEvent.model}`);
|
|
346
350
|
// Generate operationId for this batch - querysets in same chain will coordinate
|
|
347
351
|
const operationId = `remote-event-${uuidv7()}`;
|
|
348
|
-
// Get dbSynced keys (followed querysets)
|
|
349
|
-
|
|
352
|
+
// Get dbSynced keys (followed querysets that have stores)
|
|
353
|
+
// Filter to only include keys with stores - a queryset can be followed but not have
|
|
354
|
+
// a store if useQueryset doesn't call .fetch() (e.g., base queryset in a chain)
|
|
355
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets]
|
|
356
|
+
.map(qs => qs.semanticKey)
|
|
357
|
+
.filter(key => registry._stores.has(key)));
|
|
350
358
|
// Run all groupSync calls in parallel - they coordinate via shared promise cache
|
|
351
359
|
Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, operationId, dbSyncedKeys)));
|
|
352
360
|
}
|
package/package.json
CHANGED