@statezero/core 0.2.45 → 0.2.47
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/adaptors/vue/components/LayoutRenderer.d.ts +1 -0
- package/dist/adaptors/vue/components/StateZeroDebugPanel.d.ts +1 -0
- package/dist/adaptors/vue/components/StateZeroDebugPanel.js +781 -0
- package/dist/adaptors/vue/components/defaults/AlertElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/DisplayElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/DividerElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/ErrorBlock.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/GroupElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/LabelElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/TabsElement.d.ts +1 -0
- package/dist/adaptors/vue/components/defaults/index.d.ts +7 -0
- package/dist/adaptors/vue/components/index.d.ts +2 -0
- package/dist/adaptors/vue/components/index.js +1 -0
- package/dist/adaptors/vue/composables.js +0 -1
- package/dist/adaptors/vue/index.d.ts +1 -1
- package/dist/adaptors/vue/index.js +1 -1
- package/dist/config.js +11 -1
- package/dist/core/eventReceivers.d.ts +3 -3
- package/dist/core/eventReceivers.js +6 -3
- package/dist/core.css +1 -0
- package/dist/debug/statezeroDebug.d.ts +8 -0
- package/dist/debug/statezeroDebug.js +118 -0
- package/dist/flavours/django/errors.js +0 -1
- package/dist/flavours/django/makeApiCall.js +71 -0
- package/dist/reset.js +0 -2
- package/dist/syncEngine/cache/cache.js +0 -1
- package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +1 -1
- package/dist/syncEngine/registries/querysetStoreRegistry.js +45 -3
- package/dist/syncEngine/stores/metricStore.js +0 -4
- package/dist/syncEngine/stores/modelStore.js +0 -3
- package/dist/syncEngine/stores/operation.js +0 -1
- package/dist/syncEngine/stores/operationEventHandlers.js +87 -2
- package/dist/syncEngine/stores/querysetStore.d.ts +1 -1
- package/dist/syncEngine/stores/querysetStore.js +58 -5
- package/dist/syncEngine/sync.d.ts +2 -0
- package/dist/syncEngine/sync.js +17 -11
- package/dist/vue-entry.d.ts +2 -1
- package/dist/vue-entry.js +2 -2
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { QuerySet } from '../../flavours/django/querySet.js';
|
|
|
7
7
|
import { isEqual, isNil } from 'lodash-es';
|
|
8
8
|
import hash from 'object-hash';
|
|
9
9
|
import { filter, applyOrderBy } from '../../filtering/localFiltering.js';
|
|
10
|
+
import { recordDebugEvent } from '../../debug/statezeroDebug.js';
|
|
10
11
|
/**
|
|
11
12
|
* Check if two model classes represent the same model.
|
|
12
13
|
* Uses property comparison instead of reference equality to handle
|
|
@@ -38,11 +39,31 @@ function routeCreateOperation(operation, applyAction) {
|
|
|
38
39
|
Array.from(querysetStoreRegistry._stores.entries()).forEach(([semanticKey, store]) => {
|
|
39
40
|
if (!isSameModel(store.modelClass, modelClass))
|
|
40
41
|
return;
|
|
42
|
+
recordDebugEvent({
|
|
43
|
+
type: "routing",
|
|
44
|
+
operationId: operation.operationId,
|
|
45
|
+
operationType: operation.type,
|
|
46
|
+
semanticKey,
|
|
47
|
+
modelName: store.modelClass?.modelName,
|
|
48
|
+
configKey: store.modelClass?.configKey,
|
|
49
|
+
phase: "start",
|
|
50
|
+
});
|
|
41
51
|
const serializerOptions = store.queryset?._serializerOptions || {};
|
|
42
52
|
const { offset, limit } = serializerOptions;
|
|
43
53
|
// Offset > 0: can't determine position based on slicing
|
|
44
54
|
if (offset != null && offset > 0) {
|
|
45
55
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, true));
|
|
56
|
+
recordDebugEvent({
|
|
57
|
+
type: "routing",
|
|
58
|
+
operationId: operation.operationId,
|
|
59
|
+
operationType: operation.type,
|
|
60
|
+
semanticKey,
|
|
61
|
+
modelName: store.modelClass?.modelName,
|
|
62
|
+
configKey: store.modelClass?.configKey,
|
|
63
|
+
decision: "offset>0",
|
|
64
|
+
optimistic: false,
|
|
65
|
+
verify: true,
|
|
66
|
+
});
|
|
46
67
|
return;
|
|
47
68
|
}
|
|
48
69
|
// Evaluate if instances match this queryset's filter
|
|
@@ -53,6 +74,17 @@ function routeCreateOperation(operation, applyAction) {
|
|
|
53
74
|
if (matchingInstances.length === 0) {
|
|
54
75
|
// Local filter says no match - trust it (local filtering is robust)
|
|
55
76
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, false));
|
|
77
|
+
recordDebugEvent({
|
|
78
|
+
type: "routing",
|
|
79
|
+
operationId: operation.operationId,
|
|
80
|
+
operationType: operation.type,
|
|
81
|
+
semanticKey,
|
|
82
|
+
modelName: store.modelClass?.modelName,
|
|
83
|
+
configKey: store.modelClass?.configKey,
|
|
84
|
+
decision: "filter:no-match",
|
|
85
|
+
optimistic: false,
|
|
86
|
+
verify: false,
|
|
87
|
+
});
|
|
56
88
|
return;
|
|
57
89
|
}
|
|
58
90
|
// Item matches filter - check limit
|
|
@@ -75,6 +107,17 @@ function routeCreateOperation(operation, applyAction) {
|
|
|
75
107
|
// If some ground truth items couldn't be rendered, we can't trust local sort
|
|
76
108
|
if (currentItems.length < store.groundTruthPks.length) {
|
|
77
109
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, true));
|
|
110
|
+
recordDebugEvent({
|
|
111
|
+
type: "routing",
|
|
112
|
+
operationId: operation.operationId,
|
|
113
|
+
operationType: operation.type,
|
|
114
|
+
semanticKey,
|
|
115
|
+
modelName: store.modelClass?.modelName,
|
|
116
|
+
configKey: store.modelClass?.configKey,
|
|
117
|
+
decision: "limit:ordering:incomplete-render",
|
|
118
|
+
optimistic: false,
|
|
119
|
+
verify: true,
|
|
120
|
+
});
|
|
78
121
|
return;
|
|
79
122
|
}
|
|
80
123
|
const allItems = [...currentItems, ...matchingInstances];
|
|
@@ -88,14 +131,47 @@ function routeCreateOperation(operation, applyAction) {
|
|
|
88
131
|
if (anyInTopN) {
|
|
89
132
|
applyAction(store);
|
|
90
133
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(true, false));
|
|
134
|
+
recordDebugEvent({
|
|
135
|
+
type: "routing",
|
|
136
|
+
operationId: operation.operationId,
|
|
137
|
+
operationType: operation.type,
|
|
138
|
+
semanticKey,
|
|
139
|
+
modelName: store.modelClass?.modelName,
|
|
140
|
+
configKey: store.modelClass?.configKey,
|
|
141
|
+
decision: "limit:ordering:in-top",
|
|
142
|
+
optimistic: true,
|
|
143
|
+
verify: false,
|
|
144
|
+
});
|
|
91
145
|
}
|
|
92
146
|
else {
|
|
93
147
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, false));
|
|
148
|
+
recordDebugEvent({
|
|
149
|
+
type: "routing",
|
|
150
|
+
operationId: operation.operationId,
|
|
151
|
+
operationType: operation.type,
|
|
152
|
+
semanticKey,
|
|
153
|
+
modelName: store.modelClass?.modelName,
|
|
154
|
+
configKey: store.modelClass?.configKey,
|
|
155
|
+
decision: "limit:ordering:out",
|
|
156
|
+
optimistic: false,
|
|
157
|
+
verify: false,
|
|
158
|
+
});
|
|
94
159
|
}
|
|
95
160
|
}
|
|
96
161
|
else {
|
|
97
162
|
// No ordering - new items go at end, won't be in first N
|
|
98
163
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(false, false));
|
|
164
|
+
recordDebugEvent({
|
|
165
|
+
type: "routing",
|
|
166
|
+
operationId: operation.operationId,
|
|
167
|
+
operationType: operation.type,
|
|
168
|
+
semanticKey,
|
|
169
|
+
modelName: store.modelClass?.modelName,
|
|
170
|
+
configKey: store.modelClass?.configKey,
|
|
171
|
+
decision: "limit:no-ordering",
|
|
172
|
+
optimistic: false,
|
|
173
|
+
verify: false,
|
|
174
|
+
});
|
|
99
175
|
}
|
|
100
176
|
return;
|
|
101
177
|
}
|
|
@@ -104,6 +180,17 @@ function routeCreateOperation(operation, applyAction) {
|
|
|
104
180
|
// Matches filter, has room or no limit - DEFINITELY_YES
|
|
105
181
|
applyAction(store);
|
|
106
182
|
operationRegistry.setQuerysetState(operation.operationId, semanticKey, createMembershipState(true, false));
|
|
183
|
+
recordDebugEvent({
|
|
184
|
+
type: "routing",
|
|
185
|
+
operationId: operation.operationId,
|
|
186
|
+
operationType: operation.type,
|
|
187
|
+
semanticKey,
|
|
188
|
+
modelName: store.modelClass?.modelName,
|
|
189
|
+
configKey: store.modelClass?.configKey,
|
|
190
|
+
decision: "match:has-room",
|
|
191
|
+
optimistic: true,
|
|
192
|
+
verify: false,
|
|
193
|
+
});
|
|
107
194
|
});
|
|
108
195
|
}
|
|
109
196
|
/**
|
|
@@ -351,7 +438,6 @@ export function initEventHandler() {
|
|
|
351
438
|
operationEvents.on(Status.CONFIRMED, handleOperationConfirmed);
|
|
352
439
|
operationEvents.on(Status.REJECTED, handleOperationRejected);
|
|
353
440
|
operationEvents.on(Status.MUTATED, handleOperationMutated);
|
|
354
|
-
console.log('Operation event handler initialized');
|
|
355
441
|
}
|
|
356
442
|
/**
|
|
357
443
|
* Clean up by removing all event listeners
|
|
@@ -362,5 +448,4 @@ export function cleanupEventHandler() {
|
|
|
362
448
|
operationEvents.off(Status.CONFIRMED, handleOperationConfirmed);
|
|
363
449
|
operationEvents.off(Status.REJECTED, handleOperationRejected);
|
|
364
450
|
operationEvents.off(Status.MUTATED, handleOperationMutated);
|
|
365
|
-
console.log('Operation event handler cleaned up');
|
|
366
451
|
}
|
|
@@ -6,6 +6,7 @@ import { modelStoreRegistry } from '../registries/modelStoreRegistry.js';
|
|
|
6
6
|
import { processIncludedEntities } from '../../flavours/django/makeApiCall.js';
|
|
7
7
|
import { Cache } from '../cache/cache.js';
|
|
8
8
|
import { filter } from "../../filtering/localFiltering.js";
|
|
9
|
+
import { recordDebugEvent } from "../../debug/statezeroDebug.js";
|
|
9
10
|
export class QuerysetStore {
|
|
10
11
|
constructor(modelClass, fetchFn, queryset, initialGroundTruthPks = null, initialOperations = null, options = {}) {
|
|
11
12
|
this.modelClass = modelClass;
|
|
@@ -48,7 +49,6 @@ export class QuerysetStore {
|
|
|
48
49
|
if (this.groundTruthPks.length === 0 && this.operationsMap.size === 0) {
|
|
49
50
|
const cached = this.qsCache.get(this.cacheKey);
|
|
50
51
|
if (!isNil(cached) && !isEmpty(cached)) {
|
|
51
|
-
console.log(`[QuerysetStore] Hydrated ${this.modelClass.modelName} queryset with ${cached.length} items from cache`);
|
|
52
52
|
this.setGroundTruth(cached);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -186,6 +186,17 @@ export class QuerysetStore {
|
|
|
186
186
|
if (fromCache) {
|
|
187
187
|
const cachedResult = this.qsCache.get(this.cacheKey);
|
|
188
188
|
if (Array.isArray(cachedResult)) {
|
|
189
|
+
recordDebugEvent({
|
|
190
|
+
type: "render",
|
|
191
|
+
source: "cache",
|
|
192
|
+
semanticKey: this.queryset.semanticKey,
|
|
193
|
+
modelName: this.modelClass.modelName,
|
|
194
|
+
configKey: this.modelClass.configKey,
|
|
195
|
+
optimistic,
|
|
196
|
+
groundTruthCount: this.groundTruthPks.length,
|
|
197
|
+
operationsCount: this.operationsMap.size,
|
|
198
|
+
resultCount: cachedResult.length,
|
|
199
|
+
});
|
|
189
200
|
return cachedResult;
|
|
190
201
|
}
|
|
191
202
|
}
|
|
@@ -197,12 +208,29 @@ export class QuerysetStore {
|
|
|
197
208
|
: this.renderFromData(optimistic);
|
|
198
209
|
// Validate against model store and apply local filtering/sorting
|
|
199
210
|
let result = this._getValidatedAndFilteredPks(pks);
|
|
211
|
+
const preLimitCount = result.length;
|
|
200
212
|
// Apply pagination limit
|
|
201
213
|
const limit = this.queryset.build().serializerOptions?.limit;
|
|
202
214
|
if (limit) {
|
|
203
215
|
result = result.slice(0, limit);
|
|
204
216
|
}
|
|
205
217
|
this.setCache(result);
|
|
218
|
+
recordDebugEvent({
|
|
219
|
+
type: "render",
|
|
220
|
+
source: this.groundTruthPks.length === 0 && this.lastSync === null
|
|
221
|
+
? "modelStore"
|
|
222
|
+
: "groundTruth",
|
|
223
|
+
semanticKey: this.queryset.semanticKey,
|
|
224
|
+
modelName: this.modelClass.modelName,
|
|
225
|
+
configKey: this.modelClass.configKey,
|
|
226
|
+
optimistic,
|
|
227
|
+
groundTruthCount: this.groundTruthPks.length,
|
|
228
|
+
operationsCount: this.operationsMap.size,
|
|
229
|
+
rawCount: Array.isArray(pks) ? pks.length : 0,
|
|
230
|
+
filteredCount: preLimitCount,
|
|
231
|
+
resultCount: result.length,
|
|
232
|
+
limit,
|
|
233
|
+
});
|
|
206
234
|
return result;
|
|
207
235
|
}
|
|
208
236
|
renderFromData(optimistic = true) {
|
|
@@ -258,22 +286,29 @@ export class QuerysetStore {
|
|
|
258
286
|
* Sync this queryset with the database.
|
|
259
287
|
* Fetches from DB and sets ground truth.
|
|
260
288
|
*/
|
|
261
|
-
async sync() {
|
|
289
|
+
async sync(canonical_id = null) {
|
|
262
290
|
const id = this.modelClass.modelName;
|
|
263
291
|
if (this.isSyncing) {
|
|
264
292
|
console.warn(`[QuerysetStore ${id}] Already syncing, request ignored.`);
|
|
265
293
|
return;
|
|
266
294
|
}
|
|
267
295
|
this.isSyncing = true;
|
|
268
|
-
|
|
296
|
+
recordDebugEvent({
|
|
297
|
+
type: "sync",
|
|
298
|
+
phase: "start",
|
|
299
|
+
semanticKey: this.queryset.semanticKey,
|
|
300
|
+
modelName: this.modelClass.modelName,
|
|
301
|
+
configKey: this.modelClass.configKey,
|
|
302
|
+
canonicalId: canonical_id,
|
|
303
|
+
});
|
|
269
304
|
try {
|
|
270
305
|
const response = await this.fetchFn({
|
|
271
306
|
ast: this.queryset.build(),
|
|
272
307
|
modelClass: this.modelClass,
|
|
308
|
+
canonical_id,
|
|
273
309
|
});
|
|
274
310
|
const { data, included } = response;
|
|
275
311
|
if (!isNil(data)) {
|
|
276
|
-
console.log(`[${id}] Sync fetch completed. Received: ${JSON.stringify(data)}.`);
|
|
277
312
|
// Clear previous included PKs tracking before processing new data
|
|
278
313
|
this.includedPks.clear();
|
|
279
314
|
// Persist all instances (including nested) to the model store
|
|
@@ -282,10 +317,28 @@ export class QuerysetStore {
|
|
|
282
317
|
}
|
|
283
318
|
this.setOperations(this.getInflightOperations());
|
|
284
319
|
this.lastSync = Date.now();
|
|
285
|
-
|
|
320
|
+
recordDebugEvent({
|
|
321
|
+
type: "sync",
|
|
322
|
+
phase: "success",
|
|
323
|
+
semanticKey: this.queryset.semanticKey,
|
|
324
|
+
modelName: this.modelClass.modelName,
|
|
325
|
+
configKey: this.modelClass.configKey,
|
|
326
|
+
canonicalId: canonical_id,
|
|
327
|
+
dataCount: Array.isArray(data) ? data.length : 0,
|
|
328
|
+
includedCount: included ? Object.keys(included).length : 0,
|
|
329
|
+
});
|
|
286
330
|
}
|
|
287
331
|
catch (e) {
|
|
288
332
|
console.error(`[${id}] Failed to sync ground truth:`, e);
|
|
333
|
+
recordDebugEvent({
|
|
334
|
+
type: "sync",
|
|
335
|
+
phase: "error",
|
|
336
|
+
semanticKey: this.queryset.semanticKey,
|
|
337
|
+
modelName: this.modelClass.modelName,
|
|
338
|
+
configKey: this.modelClass.configKey,
|
|
339
|
+
canonicalId: canonical_id,
|
|
340
|
+
message: e?.message,
|
|
341
|
+
});
|
|
289
342
|
}
|
|
290
343
|
finally {
|
|
291
344
|
this.isSyncing = false;
|
package/dist/syncEngine/sync.js
CHANGED
|
@@ -2,6 +2,7 @@ import { getAllEventReceivers } from "../core/eventReceivers.js";
|
|
|
2
2
|
import { operationRegistry } from "./stores/operation.js";
|
|
3
3
|
import { initializeAllEventReceivers } from "../config.js";
|
|
4
4
|
import { getEventReceiver } from "../core/eventReceivers.js";
|
|
5
|
+
import { recordDebugEvent } from "../debug/statezeroDebug.js";
|
|
5
6
|
import { querysetStoreRegistry, QuerysetStoreRegistry, } from "./registries/querysetStoreRegistry.js";
|
|
6
7
|
import { modelStoreRegistry, ModelStoreRegistry, } from "./registries/modelStoreRegistry.js";
|
|
7
8
|
import { metricRegistry, MetricRegistry } from "./registries/metricRegistry.js";
|
|
@@ -16,6 +17,8 @@ export class EventPayload {
|
|
|
16
17
|
this.operation_id = data.operation_id;
|
|
17
18
|
this.pk_field_name = data.pk_field_name;
|
|
18
19
|
this.configKey = data.configKey;
|
|
20
|
+
this.canonical_id = data.canonical_id || null;
|
|
21
|
+
this.server_ts_ms = data.server_ts_ms || null;
|
|
19
22
|
// Parse PK fields to numbers in instances
|
|
20
23
|
this.instances = data.instances?.map(instance => {
|
|
21
24
|
if (instance && this.pk_field_name && instance[this.pk_field_name] != null) {
|
|
@@ -48,6 +51,16 @@ export class EventPayload {
|
|
|
48
51
|
export class SyncManager {
|
|
49
52
|
constructor() {
|
|
50
53
|
this.handleEvent = (event) => {
|
|
54
|
+
recordDebugEvent({
|
|
55
|
+
type: "event",
|
|
56
|
+
modelName: event.model,
|
|
57
|
+
configKey: event.configKey,
|
|
58
|
+
semanticKey: `${event.configKey || "default"}::${event.model}`,
|
|
59
|
+
eventType: event.type || event.event,
|
|
60
|
+
instanceCount: Array.isArray(event.instances) ? event.instances.length : 0,
|
|
61
|
+
operationId: event.operation_id,
|
|
62
|
+
socketId: event.socket_id,
|
|
63
|
+
});
|
|
51
64
|
let payload = new EventPayload(event);
|
|
52
65
|
let isLocalOperation = operationRegistry.has(payload.operation_id);
|
|
53
66
|
// Always process metrics immediately (they're lightweight)
|
|
@@ -141,18 +154,15 @@ export class SyncManager {
|
|
|
141
154
|
const intervalSeconds = config.periodicSyncIntervalSeconds;
|
|
142
155
|
// If null or undefined, don't start periodic sync
|
|
143
156
|
if (!intervalSeconds) {
|
|
144
|
-
console.log("[SyncManager] Periodic sync disabled (set to null)");
|
|
145
157
|
return;
|
|
146
158
|
}
|
|
147
159
|
const intervalMs = intervalSeconds * 1000;
|
|
148
160
|
this.periodicSyncTimer = setInterval(() => {
|
|
149
161
|
this.syncStaleQuerysets();
|
|
150
162
|
}, intervalMs);
|
|
151
|
-
console.log(`[SyncManager] Periodic sync started: ${intervalSeconds}s intervals`);
|
|
152
163
|
}
|
|
153
164
|
catch (error) {
|
|
154
165
|
// If no config, don't start periodic sync by default
|
|
155
|
-
console.log("[SyncManager] No config found, periodic sync disabled by default");
|
|
156
166
|
}
|
|
157
167
|
}
|
|
158
168
|
async syncStaleQuerysets() {
|
|
@@ -174,7 +184,6 @@ export class SyncManager {
|
|
|
174
184
|
}
|
|
175
185
|
}
|
|
176
186
|
if (storesToSync.length > 0) {
|
|
177
|
-
console.log(`[SyncManager] Periodic sync: syncing ${storesToSync.length} stores`);
|
|
178
187
|
// Run all groupSync calls in parallel - they coordinate via shared promise cache
|
|
179
188
|
await Promise.all(storesToSync.map(store => querysetRegistry.groupSync(store.queryset, operationId, dbSyncedKeys)));
|
|
180
189
|
}
|
|
@@ -266,7 +275,6 @@ export class SyncManager {
|
|
|
266
275
|
}
|
|
267
276
|
if (storesToSync.length === 0)
|
|
268
277
|
return;
|
|
269
|
-
console.log(`[SyncManager] Syncing ${storesToSync.length} querysets needing verification for operation ${operationId}`);
|
|
270
278
|
const syncOperationId = `maybe-sync-${uuidv7()}`;
|
|
271
279
|
const dbSyncedKeys = new Set([...this.followedQuerysets]
|
|
272
280
|
.map(qs => qs.semanticKey)
|
|
@@ -289,7 +297,6 @@ export class SyncManager {
|
|
|
289
297
|
const waitTime = Date.now() - this.batchStartTime;
|
|
290
298
|
this.eventBatch.clear();
|
|
291
299
|
this.batchStartTime = null;
|
|
292
|
-
console.log(`[SyncManager] Processing batch of ${events.length} events (reason: ${reason}, waited: ${waitTime}ms)`);
|
|
293
300
|
// Group events by model for efficient processing
|
|
294
301
|
const eventsByModel = new Map();
|
|
295
302
|
events.forEach((event) => {
|
|
@@ -345,8 +352,9 @@ export class SyncManager {
|
|
|
345
352
|
}
|
|
346
353
|
if (storesToSync.length === 0)
|
|
347
354
|
return;
|
|
348
|
-
//
|
|
349
|
-
|
|
355
|
+
// Pick canonical_id from the latest event by server_ts_ms
|
|
356
|
+
const latestEvent = allEvents.reduce((latest, evt) => (evt.server_ts_ms || 0) > (latest.server_ts_ms || 0) ? evt : latest, allEvents[0]);
|
|
357
|
+
const canonical_id = latestEvent.canonical_id;
|
|
350
358
|
// Generate operationId for this batch - querysets in same chain will coordinate
|
|
351
359
|
const operationId = `remote-event-${uuidv7()}`;
|
|
352
360
|
// Get dbSynced keys (followed querysets that have stores)
|
|
@@ -356,7 +364,7 @@ export class SyncManager {
|
|
|
356
364
|
.map(qs => qs.semanticKey)
|
|
357
365
|
.filter(key => registry._stores.has(key)));
|
|
358
366
|
// Run all groupSync calls in parallel - they coordinate via shared promise cache
|
|
359
|
-
Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, operationId, dbSyncedKeys)));
|
|
367
|
+
Promise.all(storesToSync.map(store => registry.groupSync(store.queryset, operationId, dbSyncedKeys, canonical_id)));
|
|
360
368
|
}
|
|
361
369
|
processMetrics(event) {
|
|
362
370
|
const registry = this.registries.get(MetricRegistry);
|
|
@@ -371,7 +379,6 @@ export class SyncManager {
|
|
|
371
379
|
}
|
|
372
380
|
// Check if this queryset (or any parent) is being followed
|
|
373
381
|
if (this.isQuerysetFollowed(entry.queryset)) {
|
|
374
|
-
console.log(`syncing metric store for key: ${key}`);
|
|
375
382
|
entry.store.sync();
|
|
376
383
|
}
|
|
377
384
|
}
|
|
@@ -417,7 +424,6 @@ export class SyncManager {
|
|
|
417
424
|
}
|
|
418
425
|
});
|
|
419
426
|
if (pksToSync.length > 0) {
|
|
420
|
-
console.log(`[SyncManager] Syncing ${pksToSync.length} nested-only PKs for ${event.model}: ${pksToSync}`);
|
|
421
427
|
modelStore.sync(pksToSync);
|
|
422
428
|
}
|
|
423
429
|
}
|
package/dist/vue-entry.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { MetricAdaptor } from './adaptors/vue/index.js';
|
|
|
4
4
|
import { useQueryset } from './adaptors/vue/index.js';
|
|
5
5
|
import { querysets } from './adaptors/vue/index.js';
|
|
6
6
|
import { LayoutRenderer } from './adaptors/vue/index.js';
|
|
7
|
+
import { StateZeroDebugPanel } from './adaptors/vue/index.js';
|
|
7
8
|
import { AlertElement } from './adaptors/vue/index.js';
|
|
8
9
|
import { LabelElement } from './adaptors/vue/index.js';
|
|
9
10
|
import { DividerElement } from './adaptors/vue/index.js';
|
|
@@ -12,4 +13,4 @@ import { GroupElement } from './adaptors/vue/index.js';
|
|
|
12
13
|
import { TabsElement } from './adaptors/vue/index.js';
|
|
13
14
|
import { ErrorBlock } from './adaptors/vue/index.js';
|
|
14
15
|
import { createDefaultComponents } from './adaptors/vue/index.js';
|
|
15
|
-
export { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets, LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
|
|
16
|
+
export { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets, LayoutRenderer, StateZeroDebugPanel, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
|
package/dist/vue-entry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets } from './adaptors/vue/index.js';
|
|
2
|
-
import { LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from './adaptors/vue/index.js';
|
|
2
|
+
import { LayoutRenderer, StateZeroDebugPanel, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents } from './adaptors/vue/index.js';
|
|
3
3
|
export {
|
|
4
4
|
// Reactivity
|
|
5
5
|
ModelAdaptor, QuerySetAdaptor, MetricAdaptor, useQueryset, querysets,
|
|
6
6
|
// Layout components
|
|
7
|
-
LayoutRenderer, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
|
|
7
|
+
LayoutRenderer, StateZeroDebugPanel, AlertElement, LabelElement, DividerElement, DisplayElement, GroupElement, TabsElement, ErrorBlock, createDefaultComponents };
|
package/package.json
CHANGED