@statezero/core 0.1.38 → 0.1.41
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/composables.js +6 -18
- package/dist/adaptors/vue/reactivity.d.ts +0 -1
- package/dist/adaptors/vue/reactivity.js +0 -1
- package/dist/syncEngine/registries/querysetStoreRegistry.js +2 -4
- package/dist/syncEngine/stores/modelStore.d.ts +2 -0
- package/dist/syncEngine/stores/modelStore.js +53 -29
- package/dist/syncEngine/stores/operationEventHandlers.js +4 -36
- package/dist/syncEngine/stores/querysetStore.d.ts +7 -0
- package/dist/syncEngine/stores/querysetStore.js +24 -4
- package/package.json +1 -1
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { computed, getCurrentInstance, onBeforeUnmount } from
|
|
2
|
-
import { querysetStoreRegistry } from
|
|
3
|
-
import { metricRegistry } from
|
|
4
|
-
import { syncManager } from
|
|
5
|
-
import {
|
|
6
|
-
import { v7 } from "uuid";
|
|
1
|
+
import { computed, getCurrentInstance, onBeforeUnmount } from 'vue';
|
|
2
|
+
import { querysetStoreRegistry } from '../../syncEngine/registries/querysetStoreRegistry';
|
|
3
|
+
import { metricRegistry } from '../../syncEngine/registries/metricRegistry';
|
|
4
|
+
import { syncManager } from '../../syncEngine/sync';
|
|
5
|
+
import { v7 } from 'uuid';
|
|
7
6
|
syncManager.followAllQuerysets = false;
|
|
8
7
|
export const querysets = new Map(); // Map of composableId -> queryset
|
|
9
8
|
function updateSyncManager() {
|
|
@@ -14,16 +13,11 @@ function updateSyncManager() {
|
|
|
14
13
|
export function useQueryset(querysetFactory) {
|
|
15
14
|
const instance = getCurrentInstance();
|
|
16
15
|
if (!instance) {
|
|
17
|
-
throw new Error(
|
|
16
|
+
throw new Error('useQueryset must be called within a component setup function');
|
|
18
17
|
}
|
|
19
18
|
const composableId = v7();
|
|
20
19
|
let lastQueryset = null;
|
|
21
20
|
onBeforeUnmount(() => {
|
|
22
|
-
// Clear cache when component unmounts
|
|
23
|
-
if (lastQueryset?.semanticKey &&
|
|
24
|
-
wrappedQuerysetCache.has(lastQueryset.semanticKey)) {
|
|
25
|
-
wrappedQuerysetCache.delete(lastQueryset.semanticKey);
|
|
26
|
-
}
|
|
27
21
|
querysets.delete(composableId);
|
|
28
22
|
updateSyncManager();
|
|
29
23
|
});
|
|
@@ -33,12 +27,6 @@ export function useQueryset(querysetFactory) {
|
|
|
33
27
|
const queryset = original?.queryset || original;
|
|
34
28
|
// Only update if queryset actually changed
|
|
35
29
|
if (lastQueryset !== queryset) {
|
|
36
|
-
// Clear cache for previous queryset if it changed
|
|
37
|
-
if (lastQueryset?.semanticKey &&
|
|
38
|
-
lastQueryset !== queryset &&
|
|
39
|
-
wrappedQuerysetCache.has(lastQueryset.semanticKey)) {
|
|
40
|
-
wrappedQuerysetCache.delete(lastQueryset.semanticKey);
|
|
41
|
-
}
|
|
42
30
|
querysets.set(composableId, queryset);
|
|
43
31
|
updateSyncManager();
|
|
44
32
|
lastQueryset = queryset;
|
|
@@ -16,4 +16,3 @@ export function ModelAdaptor(modelInstance: Object, reactivityFn?: Function): an
|
|
|
16
16
|
*/
|
|
17
17
|
export function QuerySetAdaptor(liveQuerySet: Object, reactivityFn?: Function): any | import("vue").Ref;
|
|
18
18
|
export function MetricAdaptor(metric: any): any;
|
|
19
|
-
export const wrappedQuerysetCache: Map<any, any>;
|
|
@@ -102,7 +102,7 @@ export class LiveQueryset {
|
|
|
102
102
|
* @private
|
|
103
103
|
* @returns {Array} The current items in the queryset
|
|
104
104
|
*/
|
|
105
|
-
getCurrentItems(
|
|
105
|
+
getCurrentItems() {
|
|
106
106
|
const store = querysetStoreRegistry.getStore(__classPrivateFieldGet(this, _LiveQueryset_queryset, "f"));
|
|
107
107
|
// Get the current primary keys from the store
|
|
108
108
|
const pks = store.render();
|
|
@@ -113,9 +113,7 @@ export class LiveQueryset {
|
|
|
113
113
|
const pkField = __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").primaryKeyField;
|
|
114
114
|
return __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f").fromPk(pk, __classPrivateFieldGet(this, _LiveQueryset_queryset, "f"));
|
|
115
115
|
});
|
|
116
|
-
|
|
117
|
-
return instances;
|
|
118
|
-
return filter(instances, __classPrivateFieldGet(this, _LiveQueryset_queryset, "f").build(), __classPrivateFieldGet(this, _LiveQueryset_ModelClass, "f"), true);
|
|
116
|
+
return instances;
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
119
|
_LiveQueryset_queryset = new WeakMap(), _LiveQueryset_ModelClass = new WeakMap(), _LiveQueryset_proxy = new WeakMap(), _LiveQueryset_array = new WeakMap();
|
|
@@ -8,6 +8,8 @@ export class ModelStore {
|
|
|
8
8
|
pruneThreshold: any;
|
|
9
9
|
modelCache: Cache;
|
|
10
10
|
_lastRenderedData: Map<any, any>;
|
|
11
|
+
renderCallbacks: Set<any>;
|
|
12
|
+
registerRenderCallback(callback: any): () => boolean;
|
|
11
13
|
/**
|
|
12
14
|
* Load operations from data and add them to the operations map,
|
|
13
15
|
* reusing existing operations from the registry if they exist
|
|
@@ -16,6 +16,14 @@ const emitEvents = (store, events) => {
|
|
|
16
16
|
if (!isEqual(newRenderedData, lastRenderedData)) {
|
|
17
17
|
store._lastRenderedData.set(pk, newRenderedData);
|
|
18
18
|
modelEventEmitter.emit(`${store.modelClass.configKey}::${store.modelClass.modelName}::render`, event);
|
|
19
|
+
store.renderCallbacks.forEach((callback) => {
|
|
20
|
+
try {
|
|
21
|
+
callback();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.warn("Error in model store render callback:", error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
19
27
|
}
|
|
20
28
|
});
|
|
21
29
|
};
|
|
@@ -71,15 +79,20 @@ export class ModelStore {
|
|
|
71
79
|
if (initialOperations && initialOperations.length > 0) {
|
|
72
80
|
this._loadOperations(initialOperations);
|
|
73
81
|
}
|
|
74
|
-
this.modelCache = new Cache(
|
|
82
|
+
this.modelCache = new Cache("model-cache", {}, this.onHydrated.bind(this));
|
|
75
83
|
this._lastRenderedData = new Map();
|
|
84
|
+
this.renderCallbacks = new Set();
|
|
85
|
+
}
|
|
86
|
+
registerRenderCallback(callback) {
|
|
87
|
+
this.renderCallbacks.add(callback);
|
|
88
|
+
return () => this.renderCallbacks.delete(callback);
|
|
76
89
|
}
|
|
77
90
|
/**
|
|
78
91
|
* Load operations from data and add them to the operations map,
|
|
79
92
|
* reusing existing operations from the registry if they exist
|
|
80
93
|
*/
|
|
81
94
|
_loadOperations(operationsData) {
|
|
82
|
-
operationsData.forEach(opData => {
|
|
95
|
+
operationsData.forEach((opData) => {
|
|
83
96
|
const existingOp = operationRegistry.get(opData.operationId);
|
|
84
97
|
if (existingOp) {
|
|
85
98
|
// If the operation exists in the registry, use it
|
|
@@ -109,7 +122,7 @@ export class ModelStore {
|
|
|
109
122
|
let nonTempPkItems = [];
|
|
110
123
|
result.forEach((item) => {
|
|
111
124
|
let pk = item[pkField];
|
|
112
|
-
if (typeof pk ===
|
|
125
|
+
if (typeof pk === "string" && containsTempPk(pk)) {
|
|
113
126
|
pk = replaceTempPks(item[pkField]);
|
|
114
127
|
if (isNil(pk) || isEmpty(trim(pk))) {
|
|
115
128
|
return;
|
|
@@ -128,7 +141,7 @@ export class ModelStore {
|
|
|
128
141
|
let nonTempPkItems = [];
|
|
129
142
|
items.forEach((item) => {
|
|
130
143
|
let pk = item[pkField];
|
|
131
|
-
if (typeof pk ===
|
|
144
|
+
if (typeof pk === "string" && containsTempPk(pk)) {
|
|
132
145
|
pk = replaceTempPks(item[pkField]);
|
|
133
146
|
if (isNil(pk) || isEmpty(trim(pk))) {
|
|
134
147
|
return;
|
|
@@ -145,16 +158,16 @@ export class ModelStore {
|
|
|
145
158
|
// Otherwise, we're rendering specific items - update only those items
|
|
146
159
|
const currentCache = this.modelCache.get(this.cacheKey) || [];
|
|
147
160
|
// Filter out items that were requested but not in the result (they were deleted)
|
|
148
|
-
const filteredCache = currentCache.filter(item => item &&
|
|
149
|
-
typeof item ===
|
|
161
|
+
const filteredCache = currentCache.filter((item) => item &&
|
|
162
|
+
typeof item === "object" &&
|
|
150
163
|
pkField in item &&
|
|
151
164
|
(!requestedPks.has(item[pkField]) ||
|
|
152
|
-
nonTempPkItems.some(newItem => newItem[pkField] === item[pkField])));
|
|
165
|
+
nonTempPkItems.some((newItem) => newItem[pkField] === item[pkField])));
|
|
153
166
|
// Create a map for faster lookups
|
|
154
|
-
const cacheMap = new Map(filteredCache.map(item => [item[pkField], item]));
|
|
167
|
+
const cacheMap = new Map(filteredCache.map((item) => [item[pkField], item]));
|
|
155
168
|
// Add or update items from the result
|
|
156
169
|
for (const item of nonTempPkItems) {
|
|
157
|
-
if (item && typeof item ===
|
|
170
|
+
if (item && typeof item === "object" && pkField in item) {
|
|
158
171
|
cacheMap.set(item[pkField], item);
|
|
159
172
|
}
|
|
160
173
|
}
|
|
@@ -199,7 +212,7 @@ export class ModelStore {
|
|
|
199
212
|
setOperations(operations = []) {
|
|
200
213
|
const prevOps = this.operations;
|
|
201
214
|
this.operationsMap.clear();
|
|
202
|
-
operations.forEach(op => {
|
|
215
|
+
operations.forEach((op) => {
|
|
203
216
|
this.operationsMap.set(op.operationId, op);
|
|
204
217
|
});
|
|
205
218
|
const allOps = [...prevOps, ...this.operations];
|
|
@@ -219,16 +232,16 @@ export class ModelStore {
|
|
|
219
232
|
get groundTruthPks() {
|
|
220
233
|
const pk = this.pkField;
|
|
221
234
|
return this.groundTruthArray
|
|
222
|
-
.filter(instance => instance && typeof instance ===
|
|
223
|
-
.map(instance => instance[pk]);
|
|
235
|
+
.filter((instance) => instance && typeof instance === "object" && pk in instance)
|
|
236
|
+
.map((instance) => instance[pk]);
|
|
224
237
|
}
|
|
225
238
|
addToGroundTruth(instances) {
|
|
226
239
|
if (!Array.isArray(instances) || instances.length === 0)
|
|
227
240
|
return;
|
|
228
241
|
const pkField = this.pkField;
|
|
229
242
|
const pkMap = new Map();
|
|
230
|
-
instances.forEach(inst => {
|
|
231
|
-
if (inst && typeof inst ===
|
|
243
|
+
instances.forEach((inst) => {
|
|
244
|
+
if (inst && typeof inst === "object" && pkField in inst) {
|
|
232
245
|
pkMap.set(inst[pkField], inst);
|
|
233
246
|
}
|
|
234
247
|
else {
|
|
@@ -241,7 +254,9 @@ export class ModelStore {
|
|
|
241
254
|
const processedPks = new Set();
|
|
242
255
|
const checkpointInstances = []; // Track instances that need CHECKPOINT operations
|
|
243
256
|
for (const existingItem of this.groundTruthArray) {
|
|
244
|
-
if (!existingItem ||
|
|
257
|
+
if (!existingItem ||
|
|
258
|
+
typeof existingItem !== "object" ||
|
|
259
|
+
!(pkField in existingItem)) {
|
|
245
260
|
continue;
|
|
246
261
|
}
|
|
247
262
|
const pk = existingItem[pkField];
|
|
@@ -263,12 +278,14 @@ export class ModelStore {
|
|
|
263
278
|
// Create CHECKPOINT operation for instances that already existed
|
|
264
279
|
if (checkpointInstances.length > 0) {
|
|
265
280
|
const checkpointOperation = new Operation({
|
|
266
|
-
operationId: `checkpoint_${Date.now()}_${Math.random()
|
|
281
|
+
operationId: `checkpoint_${Date.now()}_${Math.random()
|
|
282
|
+
.toString(36)
|
|
283
|
+
.substr(2, 9)}`,
|
|
267
284
|
type: Type.CHECKPOINT,
|
|
268
285
|
instances: checkpointInstances,
|
|
269
286
|
status: Status.CONFIRMED,
|
|
270
287
|
timestamp: Date.now(),
|
|
271
|
-
queryset: this.modelClass.objects.all()
|
|
288
|
+
queryset: this.modelClass.objects.all(),
|
|
272
289
|
});
|
|
273
290
|
this.operationsMap.set(checkpointOperation.operationId, checkpointOperation);
|
|
274
291
|
console.log(`[ModelStore ${this.modelClass.modelName}] Created CHECKPOINT operation for ${checkpointInstances.length} existing instances`);
|
|
@@ -282,7 +299,7 @@ export class ModelStore {
|
|
|
282
299
|
const pkField = this.pkField;
|
|
283
300
|
let filteredOps = [];
|
|
284
301
|
for (const op of operations) {
|
|
285
|
-
let relevantInstances = op.instances.filter(instance => pks.has(instance[pkField] || instance));
|
|
302
|
+
let relevantInstances = op.instances.filter((instance) => pks.has(instance[pkField] || instance));
|
|
286
303
|
if (relevantInstances.length > 0) {
|
|
287
304
|
filteredOps.push({
|
|
288
305
|
operationId: op.operationId,
|
|
@@ -291,7 +308,7 @@ export class ModelStore {
|
|
|
291
308
|
queryset: op.queryset,
|
|
292
309
|
type: op.type,
|
|
293
310
|
status: op.status,
|
|
294
|
-
args: op.args
|
|
311
|
+
args: op.args,
|
|
295
312
|
});
|
|
296
313
|
}
|
|
297
314
|
}
|
|
@@ -301,7 +318,7 @@ export class ModelStore {
|
|
|
301
318
|
const pkField = this.pkField;
|
|
302
319
|
let groundTruthMap = new Map();
|
|
303
320
|
for (const instance of groundTruthArray) {
|
|
304
|
-
if (!instance || typeof instance !==
|
|
321
|
+
if (!instance || typeof instance !== "object" || !(pkField in instance)) {
|
|
305
322
|
continue;
|
|
306
323
|
}
|
|
307
324
|
const pk = instance[pkField];
|
|
@@ -314,7 +331,7 @@ export class ModelStore {
|
|
|
314
331
|
applyOperation(operation, currentInstances) {
|
|
315
332
|
const pkField = this.pkField;
|
|
316
333
|
for (const instance of operation.instances) {
|
|
317
|
-
if (!instance || typeof instance !==
|
|
334
|
+
if (!instance || typeof instance !== "object" || !(pkField in instance)) {
|
|
318
335
|
console.warn(`[ModelStore ${this.modelClass.modelName}] Skipping instance ${instance} in operation ${operation.operationId} during applyOperation due to missing PK field '${String(pkField)}' or invalid format.`);
|
|
319
336
|
continue;
|
|
320
337
|
}
|
|
@@ -333,9 +350,9 @@ export class ModelStore {
|
|
|
333
350
|
currentInstances.set(pk, { ...existing, ...instance });
|
|
334
351
|
}
|
|
335
352
|
else {
|
|
336
|
-
const wasDeletedLocally = this.operations.some(op => op.type === Type.DELETE &&
|
|
353
|
+
const wasDeletedLocally = this.operations.some((op) => op.type === Type.DELETE &&
|
|
337
354
|
op.status !== Status.REJECTED &&
|
|
338
|
-
op.instances.some(inst => inst && inst[pkField] === pk));
|
|
355
|
+
op.instances.some((inst) => inst && inst[pkField] === pk));
|
|
339
356
|
if (!wasDeletedLocally) {
|
|
340
357
|
currentInstances.set(pk, instance);
|
|
341
358
|
}
|
|
@@ -354,10 +371,10 @@ export class ModelStore {
|
|
|
354
371
|
}
|
|
355
372
|
getTrimmedOperations() {
|
|
356
373
|
const twoMinutesAgo = Date.now() - 1000 * 60 * 2;
|
|
357
|
-
return this.operations.filter(operation => operation.timestamp > twoMinutesAgo);
|
|
374
|
+
return this.operations.filter((operation) => operation.timestamp > twoMinutesAgo);
|
|
358
375
|
}
|
|
359
376
|
getInflightOperations() {
|
|
360
|
-
return this.operations.filter(operation => operation.status != Status.CONFIRMED &&
|
|
377
|
+
return this.operations.filter((operation) => operation.status != Status.CONFIRMED &&
|
|
361
378
|
operation.status != Status.REJECTED);
|
|
362
379
|
}
|
|
363
380
|
// Pruning
|
|
@@ -369,12 +386,16 @@ export class ModelStore {
|
|
|
369
386
|
}
|
|
370
387
|
// Render methods
|
|
371
388
|
render(pks = null, optimistic = true) {
|
|
372
|
-
const pksSet = pks === null
|
|
373
|
-
|
|
389
|
+
const pksSet = pks === null
|
|
390
|
+
? null
|
|
391
|
+
: pks instanceof Set
|
|
392
|
+
? pks
|
|
393
|
+
: new Set(Array.isArray(pks) ? pks : [pks]);
|
|
374
394
|
const renderedInstancesMap = this._filteredGroundTruth(pksSet, this.groundTruthArray);
|
|
375
395
|
const relevantOperations = this._filteredOperations(pksSet, this.operations);
|
|
376
396
|
for (const op of relevantOperations) {
|
|
377
|
-
if (op.status !== Status.REJECTED &&
|
|
397
|
+
if (op.status !== Status.REJECTED &&
|
|
398
|
+
(optimistic || op.status === Status.CONFIRMED)) {
|
|
378
399
|
this.applyOperation(op, renderedInstancesMap);
|
|
379
400
|
}
|
|
380
401
|
}
|
|
@@ -397,7 +418,10 @@ export class ModelStore {
|
|
|
397
418
|
this.setOperations(trimmedOps);
|
|
398
419
|
return;
|
|
399
420
|
}
|
|
400
|
-
const newGroundTruth = await this.fetchFn({
|
|
421
|
+
const newGroundTruth = await this.fetchFn({
|
|
422
|
+
pks: currentPks,
|
|
423
|
+
modelClass: this.modelClass,
|
|
424
|
+
});
|
|
401
425
|
if (pks) {
|
|
402
426
|
this.addToGroundTruth(newGroundTruth);
|
|
403
427
|
return;
|
|
@@ -35,38 +35,6 @@ function relatedQuerysets(queryset) {
|
|
|
35
35
|
});
|
|
36
36
|
return result;
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Returns querysets that contain any of the specified instances
|
|
40
|
-
* @param {Operation} operation - The operation containing instances to check
|
|
41
|
-
* @returns {Map<QuerySet, Store>}
|
|
42
|
-
*/
|
|
43
|
-
function querysetsContainingInstances(operation) {
|
|
44
|
-
const ModelClass = operation.queryset.ModelClass;
|
|
45
|
-
const pkField = ModelClass.primaryKeyField;
|
|
46
|
-
const instancePks = new Set(operation.instances
|
|
47
|
-
.filter(instance => instance && typeof instance === 'object' && pkField in instance)
|
|
48
|
-
.map(instance => instance[pkField]));
|
|
49
|
-
if (instancePks.size === 0) {
|
|
50
|
-
return new Map();
|
|
51
|
-
}
|
|
52
|
-
const result = new Map();
|
|
53
|
-
Array.from(querysetStoreRegistry._stores.entries()).forEach(([queryset, store]) => {
|
|
54
|
-
if (store.modelClass !== ModelClass)
|
|
55
|
-
return;
|
|
56
|
-
try {
|
|
57
|
-
// Check if this queryset contains any of the instances
|
|
58
|
-
const renderedPks = new Set(store.render(false)); // Get without optimistic updates
|
|
59
|
-
const hasIntersection = [...instancePks].some(pk => renderedPks.has(pk));
|
|
60
|
-
if (hasIntersection) {
|
|
61
|
-
result.set(store.queryset, store);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
catch (e) {
|
|
65
|
-
console.warn('Error checking queryset for instances', e);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
38
|
/**
|
|
71
39
|
* Process an operation in the model store
|
|
72
40
|
*
|
|
@@ -133,12 +101,12 @@ function processQuerysetStores(operation, actionType) {
|
|
|
133
101
|
case Type.UPDATE_INSTANCE:
|
|
134
102
|
case Type.DELETE:
|
|
135
103
|
case Type.DELETE_INSTANCE:
|
|
136
|
-
//
|
|
137
|
-
querysetStoreMap =
|
|
104
|
+
// No need to do anything, the model will change the queryset local filtering will handle it
|
|
105
|
+
querysetStoreMap = new Map();
|
|
138
106
|
break;
|
|
139
107
|
case Type.CHECKPOINT:
|
|
140
|
-
//
|
|
141
|
-
querysetStoreMap =
|
|
108
|
+
// No need to do anything, the model will change the queryset local filtering will handle it
|
|
109
|
+
querysetStoreMap = new Map();
|
|
142
110
|
break;
|
|
143
111
|
default:
|
|
144
112
|
// For other operation types, use the existing related querysets logic
|
|
@@ -16,6 +16,7 @@ export class QuerysetStore {
|
|
|
16
16
|
renderCallbacks: Set<any>;
|
|
17
17
|
_rootUnregister: any;
|
|
18
18
|
_currentRootStore: any;
|
|
19
|
+
_modelStoreUnregister: any;
|
|
19
20
|
get cacheKey(): any;
|
|
20
21
|
onHydrated(hydratedData: any): void;
|
|
21
22
|
setCache(result: any): void;
|
|
@@ -35,6 +36,12 @@ export class QuerysetStore {
|
|
|
35
36
|
prune(): void;
|
|
36
37
|
registerRenderCallback(callback: any): () => boolean;
|
|
37
38
|
_ensureRootRegistration(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Helper to validate PKs against the model store and apply local filtering/sorting.
|
|
41
|
+
* This is the core of the rendering logic.
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
private _getValidatedAndFilteredPks;
|
|
38
45
|
render(optimistic?: boolean, fromCache?: boolean): any[];
|
|
39
46
|
renderFromRoot(optimistic: boolean | undefined, rootStore: any): any[];
|
|
40
47
|
renderFromData(optimistic?: boolean): any[];
|
|
@@ -34,6 +34,10 @@ export class QuerysetStore {
|
|
|
34
34
|
this._rootUnregister = null;
|
|
35
35
|
this._currentRootStore = null;
|
|
36
36
|
this._ensureRootRegistration();
|
|
37
|
+
const modelStore = modelStoreRegistry.getStore(this.modelClass);
|
|
38
|
+
this._modelStoreUnregister = modelStore.registerRenderCallback(() => {
|
|
39
|
+
this._emitRenderEvent();
|
|
40
|
+
});
|
|
37
41
|
}
|
|
38
42
|
// Caching
|
|
39
43
|
get cacheKey() {
|
|
@@ -170,6 +174,21 @@ export class QuerysetStore {
|
|
|
170
174
|
// Update current root store reference (could be null now)
|
|
171
175
|
this._currentRootStore = rootStore;
|
|
172
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Helper to validate PKs against the model store and apply local filtering/sorting.
|
|
179
|
+
* This is the core of the rendering logic.
|
|
180
|
+
* @private
|
|
181
|
+
*/
|
|
182
|
+
_getValidatedAndFilteredPks(pks) {
|
|
183
|
+
// 1. Convert PKs to instances, filtering out any that are null (deleted).
|
|
184
|
+
const instances = Array.from(pks)
|
|
185
|
+
.map((pk) => this.modelClass.fromPk(pk, this.queryset))
|
|
186
|
+
.filter((instance) => modelStoreRegistry.getEntity(this.modelClass, instance.pk) !== null);
|
|
187
|
+
// 2. Apply the queryset's AST (filters, ordering) to the validated instances.
|
|
188
|
+
const ast = this.queryset.build();
|
|
189
|
+
const finalPks = filter(instances, ast, this.modelClass, false); // false = return PKs
|
|
190
|
+
return finalPks;
|
|
191
|
+
}
|
|
173
192
|
render(optimistic = true, fromCache = false) {
|
|
174
193
|
this._ensureRootRegistration();
|
|
175
194
|
if (fromCache) {
|
|
@@ -178,18 +197,19 @@ export class QuerysetStore {
|
|
|
178
197
|
return cachedResult;
|
|
179
198
|
}
|
|
180
199
|
}
|
|
181
|
-
let
|
|
200
|
+
let pks;
|
|
182
201
|
if (this.getRootStore &&
|
|
183
202
|
typeof this.getRootStore === "function" &&
|
|
184
203
|
!this.isTemp) {
|
|
185
204
|
const { isRoot, rootStore } = this.getRootStore(this.queryset);
|
|
186
205
|
if (!isRoot && rootStore) {
|
|
187
|
-
|
|
206
|
+
pks = this.renderFromRoot(optimistic, rootStore);
|
|
188
207
|
}
|
|
189
208
|
}
|
|
190
|
-
if (isNil(
|
|
191
|
-
|
|
209
|
+
if (isNil(pks)) {
|
|
210
|
+
pks = this.renderFromData(optimistic);
|
|
192
211
|
}
|
|
212
|
+
let result = this._getValidatedAndFilteredPks(pks);
|
|
193
213
|
let limit = this.queryset.build().serializerOptions?.limit;
|
|
194
214
|
if (limit) {
|
|
195
215
|
result = result.slice(0, limit);
|
package/package.json
CHANGED