@statezero/core 0.2.39 → 0.2.40
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.
|
@@ -6,7 +6,7 @@ import { getFingerprint } from './utils.js';
|
|
|
6
6
|
import { QuerySet } from '../../flavours/django/querySet.js';
|
|
7
7
|
import { isEqual, isNil } from 'lodash-es';
|
|
8
8
|
import hash from 'object-hash';
|
|
9
|
-
import { filter } from '../../filtering/localFiltering.js';
|
|
9
|
+
import { filter, applyOrderBy } from '../../filtering/localFiltering.js';
|
|
10
10
|
/**
|
|
11
11
|
* Evaluates and routes a CREATE operation to querysets, tracking membership state.
|
|
12
12
|
*
|
|
@@ -34,7 +34,7 @@ function routeCreateOperation(operation, applyAction) {
|
|
|
34
34
|
}
|
|
35
35
|
// Evaluate if instances match this queryset's filter
|
|
36
36
|
const ast = store.queryset.build();
|
|
37
|
-
const matchingInstances = filter(instances, ast, modelClass,
|
|
37
|
+
const matchingInstances = filter(instances, ast, modelClass, true);
|
|
38
38
|
if (matchingInstances.length === 0) {
|
|
39
39
|
// No instances match - mark DEFINITELY_NO (no need to sync this queryset)
|
|
40
40
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_NO);
|
|
@@ -49,8 +49,34 @@ function routeCreateOperation(operation, applyAction) {
|
|
|
49
49
|
const hasExplicitOrdering = store.queryset._orderBy && store.queryset._orderBy.length > 0;
|
|
50
50
|
const hasImplicitOrdering = (modelClass.schema?.default_ordering?.length || 0) > 0;
|
|
51
51
|
if (hasExplicitOrdering || hasImplicitOrdering) {
|
|
52
|
-
//
|
|
53
|
-
|
|
52
|
+
// Use local sorting to determine if new items would be in top N
|
|
53
|
+
const orderBy = hasExplicitOrdering
|
|
54
|
+
? store.queryset._orderBy
|
|
55
|
+
: modelClass.schema.default_ordering;
|
|
56
|
+
// Get current items from model store
|
|
57
|
+
const modelStore = modelStoreRegistry.getStore(modelClass);
|
|
58
|
+
const renderedItems = modelStore.render(store.groundTruthPks, true, false) || [];
|
|
59
|
+
const currentItems = renderedItems.filter(Boolean);
|
|
60
|
+
// If some ground truth items couldn't be rendered, we can't trust local sort
|
|
61
|
+
if (currentItems.length < store.groundTruthPks.length) {
|
|
62
|
+
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.MAYBE);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const allItems = [...currentItems, ...matchingInstances];
|
|
66
|
+
// Sort and take top N
|
|
67
|
+
const sorted = applyOrderBy(allItems, orderBy, modelClass);
|
|
68
|
+
const topN = sorted.slice(0, limit);
|
|
69
|
+
const topNPks = new Set(topN.map(item => item[modelClass.primaryKeyField]));
|
|
70
|
+
// Check if any new items made it into the top N
|
|
71
|
+
const newItemPks = matchingInstances.map(item => item[modelClass.primaryKeyField]);
|
|
72
|
+
const anyInTopN = newItemPks.some(pk => topNPks.has(pk));
|
|
73
|
+
if (anyInTopN) {
|
|
74
|
+
applyAction(store);
|
|
75
|
+
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_YES);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
operationRegistry.setQuerysetState(operation.operationId, semanticKey, OperationMembership.DEFINITELY_NO);
|
|
79
|
+
}
|
|
54
80
|
}
|
|
55
81
|
else {
|
|
56
82
|
// No ordering - new items go at end, won't be in first N
|
|
@@ -36,6 +36,11 @@ export class SyncManager {
|
|
|
36
36
|
unfollowModel(registry: any, modelClass: any): void;
|
|
37
37
|
manageRegistry(registry: any): void;
|
|
38
38
|
removeRegistry(registry: any): void;
|
|
39
|
+
/**
|
|
40
|
+
* Sync querysets that were marked MAYBE for a local operation.
|
|
41
|
+
* Called when a backend event arrives for an operation we initiated locally.
|
|
42
|
+
*/
|
|
43
|
+
syncMaybeQuerysets(operationId: any): void;
|
|
39
44
|
handleEvent: (event: any) => void;
|
|
40
45
|
processBatch(reason?: string): void;
|
|
41
46
|
isQuerysetFollowed(queryset: any): boolean;
|
package/dist/syncEngine/sync.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getAllEventReceivers } from "../core/eventReceivers.js";
|
|
2
|
-
import { operationRegistry } from "./stores/operation.js";
|
|
2
|
+
import { operationRegistry, OperationMembership } from "./stores/operation.js";
|
|
3
3
|
import { initializeAllEventReceivers } from "../config.js";
|
|
4
4
|
import { getEventReceiver } from "../core/eventReceivers.js";
|
|
5
5
|
import { querysetStoreRegistry, QuerysetStoreRegistry, } from "./registries/querysetStoreRegistry.js";
|
|
@@ -55,6 +55,9 @@ export class SyncManager {
|
|
|
55
55
|
this.processMetrics(payload);
|
|
56
56
|
}
|
|
57
57
|
if (isLocalOperation) {
|
|
58
|
+
// Check if any querysets were marked MAYBE for this operation
|
|
59
|
+
// These need to be synced since we couldn't determine membership client-side
|
|
60
|
+
this.syncMaybeQuerysets(payload.operation_id);
|
|
58
61
|
return;
|
|
59
62
|
}
|
|
60
63
|
// Add to batch for queryset/model processing
|
|
@@ -237,6 +240,33 @@ export class SyncManager {
|
|
|
237
240
|
removeRegistry(registry) {
|
|
238
241
|
this.registries.delete(registry.constructor);
|
|
239
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Sync querysets that were marked MAYBE for a local operation.
|
|
245
|
+
* Called when a backend event arrives for an operation we initiated locally.
|
|
246
|
+
*/
|
|
247
|
+
syncMaybeQuerysets(operationId) {
|
|
248
|
+
const states = operationRegistry.getQuerysetStates(operationId);
|
|
249
|
+
if (!states)
|
|
250
|
+
return;
|
|
251
|
+
const registry = this.registries.get(QuerysetStoreRegistry);
|
|
252
|
+
if (!registry)
|
|
253
|
+
return;
|
|
254
|
+
const storesToSync = [];
|
|
255
|
+
for (const [semanticKey, membership] of states.entries()) {
|
|
256
|
+
if (membership === OperationMembership.MAYBE) {
|
|
257
|
+
const store = registry._stores.get(semanticKey);
|
|
258
|
+
if (store) {
|
|
259
|
+
storesToSync.push(store);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (storesToSync.length === 0)
|
|
264
|
+
return;
|
|
265
|
+
console.log(`[SyncManager] Syncing ${storesToSync.length} MAYBE querysets for local operation ${operationId}`);
|
|
266
|
+
const syncOperationId = `maybe-sync-${uuidv7()}`;
|
|
267
|
+
const dbSyncedKeys = new Set([...this.followedQuerysets].map(qs => qs.semanticKey));
|
|
268
|
+
Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, syncOperationId, dbSyncedKeys)));
|
|
269
|
+
}
|
|
240
270
|
processBatch(reason = "unknown") {
|
|
241
271
|
if (this.eventBatch.size === 0)
|
|
242
272
|
return;
|
package/package.json
CHANGED