@tanstack/query-db-collection 1.0.30 → 1.0.31
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/cjs/query.cjs +442 -48
- package/dist/cjs/query.cjs.map +1 -1
- package/dist/cjs/query.d.cts +1 -0
- package/dist/esm/query.d.ts +1 -0
- package/dist/esm/query.js +442 -48
- package/dist/esm/query.js.map +1 -1
- package/package.json +4 -3
- package/src/query.ts +654 -72
package/dist/cjs/query.cjs
CHANGED
|
@@ -5,6 +5,7 @@ const db = require("@tanstack/db");
|
|
|
5
5
|
const errors = require("./errors.cjs");
|
|
6
6
|
const manualSync = require("./manual-sync.cjs");
|
|
7
7
|
const serialization = require("./serialization.cjs");
|
|
8
|
+
const QUERY_COLLECTION_GC_PREFIX = `queryCollection:gc:`;
|
|
8
9
|
class QueryCollectionUtilsImpl {
|
|
9
10
|
constructor(state, refetch, writeUtils) {
|
|
10
11
|
this.state = state;
|
|
@@ -73,6 +74,7 @@ function queryCollectionOptions(config) {
|
|
|
73
74
|
retry,
|
|
74
75
|
retryDelay,
|
|
75
76
|
staleTime,
|
|
77
|
+
persistedGcTime,
|
|
76
78
|
getKey,
|
|
77
79
|
onInsert,
|
|
78
80
|
onUpdate,
|
|
@@ -81,6 +83,16 @@ function queryCollectionOptions(config) {
|
|
|
81
83
|
...baseCollectionConfig
|
|
82
84
|
} = config;
|
|
83
85
|
const syncMode = baseCollectionConfig.syncMode ?? `eager`;
|
|
86
|
+
const baseKey = typeof queryKey === `function` ? queryKey({}) : queryKey;
|
|
87
|
+
const validateQueryKeyPrefix = (key) => {
|
|
88
|
+
if (typeof queryKey !== `function`) return;
|
|
89
|
+
const isValidPrefix = key.length >= baseKey.length && baseKey.every((segment, i) => db.deepEquals(segment, key[i]));
|
|
90
|
+
if (!isValidPrefix) {
|
|
91
|
+
console.warn(
|
|
92
|
+
`[QueryCollection] queryKey function must return keys that extend the base key prefix. Base: ${JSON.stringify(baseKey)}, Got: ${JSON.stringify(key)}. This can cause stale cache issues.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
84
96
|
if (!queryKey) {
|
|
85
97
|
throw new errors.QueryKeyRequiredError();
|
|
86
98
|
}
|
|
@@ -122,8 +134,244 @@ function queryCollectionOptions(config) {
|
|
|
122
134
|
return rowToQueriesSet.size === 0;
|
|
123
135
|
};
|
|
124
136
|
const internalSync = (params) => {
|
|
125
|
-
const { begin, write, commit, markReady, collection } = params;
|
|
137
|
+
const { begin, write, commit, markReady, collection, metadata } = params;
|
|
138
|
+
const persistedMetadata = metadata;
|
|
126
139
|
let syncStarted = false;
|
|
140
|
+
let startupRetentionSettled = false;
|
|
141
|
+
const retainedQueriesPendingRevalidation = /* @__PURE__ */ new Set();
|
|
142
|
+
const effectivePersistedGcTimes = /* @__PURE__ */ new Map();
|
|
143
|
+
const persistedRetentionTimers = /* @__PURE__ */ new Map();
|
|
144
|
+
let persistedRetentionMaintenance = Promise.resolve();
|
|
145
|
+
const getRowMetadata = (rowKey) => {
|
|
146
|
+
return metadata?.row.get(rowKey) ?? collection._state.syncedMetadata.get(rowKey);
|
|
147
|
+
};
|
|
148
|
+
const getPersistedOwners = (rowKey) => {
|
|
149
|
+
const rowMetadata = getRowMetadata(rowKey);
|
|
150
|
+
const queryMetadata = rowMetadata?.queryCollection;
|
|
151
|
+
if (!queryMetadata || typeof queryMetadata !== `object`) {
|
|
152
|
+
return /* @__PURE__ */ new Set();
|
|
153
|
+
}
|
|
154
|
+
const owners = queryMetadata.owners;
|
|
155
|
+
if (!owners || typeof owners !== `object`) {
|
|
156
|
+
return /* @__PURE__ */ new Set();
|
|
157
|
+
}
|
|
158
|
+
return new Set(Object.keys(owners));
|
|
159
|
+
};
|
|
160
|
+
const setPersistedOwners = (rowKey, owners) => {
|
|
161
|
+
if (!metadata) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const currentMetadata = { ...getRowMetadata(rowKey) ?? {} };
|
|
165
|
+
if (owners.size === 0) {
|
|
166
|
+
delete currentMetadata.queryCollection;
|
|
167
|
+
if (Object.keys(currentMetadata).length === 0) {
|
|
168
|
+
metadata.row.delete(rowKey);
|
|
169
|
+
} else {
|
|
170
|
+
metadata.row.set(rowKey, currentMetadata);
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
metadata.row.set(rowKey, {
|
|
175
|
+
...currentMetadata,
|
|
176
|
+
queryCollection: {
|
|
177
|
+
owners: Object.fromEntries(
|
|
178
|
+
Array.from(owners.values()).map((owner) => [owner, true])
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
const parsePersistedQueryRetentionEntry = (value, expectedHash) => {
|
|
184
|
+
if (!value || typeof value !== `object`) {
|
|
185
|
+
return void 0;
|
|
186
|
+
}
|
|
187
|
+
const record = value;
|
|
188
|
+
if (record.queryHash !== expectedHash) {
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
if (record.mode === `until-revalidated`) {
|
|
192
|
+
return {
|
|
193
|
+
queryHash: expectedHash,
|
|
194
|
+
mode: `until-revalidated`
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (record.mode === `ttl` && typeof record.expiresAt === `number` && Number.isFinite(record.expiresAt)) {
|
|
198
|
+
return {
|
|
199
|
+
queryHash: expectedHash,
|
|
200
|
+
mode: `ttl`,
|
|
201
|
+
expiresAt: record.expiresAt
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return void 0;
|
|
205
|
+
};
|
|
206
|
+
const runPersistedRetentionMaintenance = (task) => {
|
|
207
|
+
persistedRetentionMaintenance = persistedRetentionMaintenance.then(
|
|
208
|
+
task,
|
|
209
|
+
task
|
|
210
|
+
);
|
|
211
|
+
return persistedRetentionMaintenance;
|
|
212
|
+
};
|
|
213
|
+
const cancelPersistedRetentionExpiry = (hashedQueryKey) => {
|
|
214
|
+
const timer = persistedRetentionTimers.get(hashedQueryKey);
|
|
215
|
+
if (timer) {
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
persistedRetentionTimers.delete(hashedQueryKey);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const getHydratedOwnedRowsForQueryBaseline = (hashedQueryKey) => {
|
|
221
|
+
const knownRows = queryToRows.get(hashedQueryKey);
|
|
222
|
+
if (knownRows) {
|
|
223
|
+
return new Set(knownRows);
|
|
224
|
+
}
|
|
225
|
+
const ownedRows = /* @__PURE__ */ new Set();
|
|
226
|
+
for (const [rowKey] of collection._state.syncedData.entries()) {
|
|
227
|
+
const owners = getPersistedOwners(rowKey);
|
|
228
|
+
if (owners.size === 0) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
rowToQueries.set(rowKey, new Set(owners));
|
|
232
|
+
owners.forEach((owner) => {
|
|
233
|
+
const queryToRowsSet = queryToRows.get(owner) || /* @__PURE__ */ new Set();
|
|
234
|
+
queryToRowsSet.add(rowKey);
|
|
235
|
+
queryToRows.set(owner, queryToRowsSet);
|
|
236
|
+
});
|
|
237
|
+
if (owners.has(hashedQueryKey)) {
|
|
238
|
+
ownedRows.add(rowKey);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return ownedRows;
|
|
242
|
+
};
|
|
243
|
+
const loadPersistedBaselineForQuery = async (hashedQueryKey) => {
|
|
244
|
+
const knownRows = queryToRows.get(hashedQueryKey);
|
|
245
|
+
if (knownRows && Array.from(knownRows).every((rowKey) => collection.has(rowKey))) {
|
|
246
|
+
const baseline2 = /* @__PURE__ */ new Map();
|
|
247
|
+
knownRows.forEach((rowKey) => {
|
|
248
|
+
const value = collection.get(rowKey);
|
|
249
|
+
const owners = rowToQueries.get(rowKey);
|
|
250
|
+
if (value && owners) {
|
|
251
|
+
baseline2.set(rowKey, {
|
|
252
|
+
value,
|
|
253
|
+
owners: new Set(owners)
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
return baseline2;
|
|
258
|
+
}
|
|
259
|
+
const scanPersisted = persistedMetadata?.row.scanPersisted;
|
|
260
|
+
if (!scanPersisted) {
|
|
261
|
+
const baseline2 = /* @__PURE__ */ new Map();
|
|
262
|
+
getHydratedOwnedRowsForQueryBaseline(hashedQueryKey).forEach(
|
|
263
|
+
(rowKey) => {
|
|
264
|
+
const value = collection.get(rowKey);
|
|
265
|
+
const owners = rowToQueries.get(rowKey);
|
|
266
|
+
if (value && owners) {
|
|
267
|
+
baseline2.set(rowKey, {
|
|
268
|
+
value,
|
|
269
|
+
owners: new Set(owners)
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
return baseline2;
|
|
275
|
+
}
|
|
276
|
+
const baseline = /* @__PURE__ */ new Map();
|
|
277
|
+
const scannedRows = await scanPersisted();
|
|
278
|
+
scannedRows.forEach((row) => {
|
|
279
|
+
const rowMetadata = row.metadata;
|
|
280
|
+
const queryMetadata = rowMetadata?.queryCollection;
|
|
281
|
+
if (!queryMetadata || typeof queryMetadata !== `object`) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const owners = queryMetadata.owners;
|
|
285
|
+
if (!owners || typeof owners !== `object`) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const ownerSet = new Set(Object.keys(owners));
|
|
289
|
+
if (ownerSet.size === 0) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
rowToQueries.set(row.key, new Set(ownerSet));
|
|
293
|
+
ownerSet.forEach((owner) => {
|
|
294
|
+
const queryToRowsSet = queryToRows.get(owner) || /* @__PURE__ */ new Set();
|
|
295
|
+
queryToRowsSet.add(row.key);
|
|
296
|
+
queryToRows.set(owner, queryToRowsSet);
|
|
297
|
+
});
|
|
298
|
+
if (ownerSet.has(hashedQueryKey)) {
|
|
299
|
+
baseline.set(row.key, {
|
|
300
|
+
value: row.value,
|
|
301
|
+
owners: ownerSet
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
return baseline;
|
|
306
|
+
};
|
|
307
|
+
const cleanupPersistedPlaceholder = async (hashedQueryKey) => {
|
|
308
|
+
if (!metadata) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const baseline = await loadPersistedBaselineForQuery(hashedQueryKey);
|
|
312
|
+
const rowsToDelete = [];
|
|
313
|
+
begin();
|
|
314
|
+
baseline.forEach(({ value: oldItem, owners }, rowKey) => {
|
|
315
|
+
owners.delete(hashedQueryKey);
|
|
316
|
+
setPersistedOwners(rowKey, owners);
|
|
317
|
+
const needToRemove = removeRow(rowKey, hashedQueryKey);
|
|
318
|
+
if (needToRemove) {
|
|
319
|
+
rowsToDelete.push(oldItem);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
rowsToDelete.forEach((row) => {
|
|
323
|
+
write({ type: `delete`, value: row });
|
|
324
|
+
});
|
|
325
|
+
metadata.collection.delete(
|
|
326
|
+
`${QUERY_COLLECTION_GC_PREFIX}${hashedQueryKey}`
|
|
327
|
+
);
|
|
328
|
+
commit();
|
|
329
|
+
};
|
|
330
|
+
const schedulePersistedRetentionExpiry = (entry) => {
|
|
331
|
+
if (entry.mode !== `ttl`) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
cancelPersistedRetentionExpiry(entry.queryHash);
|
|
335
|
+
const delay = Math.max(0, entry.expiresAt - Date.now());
|
|
336
|
+
const timer = setTimeout(() => {
|
|
337
|
+
persistedRetentionTimers.delete(entry.queryHash);
|
|
338
|
+
void runPersistedRetentionMaintenance(async () => {
|
|
339
|
+
const currentEntry = metadata?.collection.get(
|
|
340
|
+
`${QUERY_COLLECTION_GC_PREFIX}${entry.queryHash}`
|
|
341
|
+
);
|
|
342
|
+
const parsedCurrentEntry = parsePersistedQueryRetentionEntry(
|
|
343
|
+
currentEntry,
|
|
344
|
+
entry.queryHash
|
|
345
|
+
);
|
|
346
|
+
if (!parsedCurrentEntry || parsedCurrentEntry.mode !== `ttl` || parsedCurrentEntry.expiresAt > Date.now()) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
await cleanupPersistedPlaceholder(entry.queryHash);
|
|
350
|
+
});
|
|
351
|
+
}, delay);
|
|
352
|
+
persistedRetentionTimers.set(entry.queryHash, timer);
|
|
353
|
+
};
|
|
354
|
+
const consumePersistedQueryRetentionAtStartup = async () => {
|
|
355
|
+
if (!metadata) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const retentionEntries = metadata.collection.list(
|
|
359
|
+
QUERY_COLLECTION_GC_PREFIX
|
|
360
|
+
);
|
|
361
|
+
const now = Date.now();
|
|
362
|
+
for (const { key, value } of retentionEntries) {
|
|
363
|
+
const hashedQueryKey = key.slice(QUERY_COLLECTION_GC_PREFIX.length);
|
|
364
|
+
const parsed = parsePersistedQueryRetentionEntry(value, hashedQueryKey);
|
|
365
|
+
if (!parsed) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (parsed.mode === `ttl` && parsed.expiresAt <= now) {
|
|
369
|
+
await cleanupPersistedPlaceholder(parsed.queryHash);
|
|
370
|
+
} else if (parsed.mode === `ttl`) {
|
|
371
|
+
schedulePersistedRetentionExpiry(parsed);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
127
375
|
const generateQueryKeyFromOptions = (opts) => {
|
|
128
376
|
if (typeof queryKey === `function`) {
|
|
129
377
|
return queryKey(opts);
|
|
@@ -134,10 +382,37 @@ function queryCollectionOptions(config) {
|
|
|
134
382
|
return queryKey;
|
|
135
383
|
}
|
|
136
384
|
};
|
|
385
|
+
const startupRetentionEntries = metadata?.collection.list(
|
|
386
|
+
QUERY_COLLECTION_GC_PREFIX
|
|
387
|
+
);
|
|
388
|
+
const startupRetentionMaintenancePromise = !startupRetentionEntries || startupRetentionEntries.length === 0 ? (() => {
|
|
389
|
+
startupRetentionSettled = true;
|
|
390
|
+
return Promise.resolve();
|
|
391
|
+
})() : runPersistedRetentionMaintenance(async () => {
|
|
392
|
+
try {
|
|
393
|
+
await consumePersistedQueryRetentionAtStartup();
|
|
394
|
+
} finally {
|
|
395
|
+
startupRetentionSettled = true;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
137
398
|
const createQueryFromOpts = (opts = {}, queryFunction = queryFn) => {
|
|
399
|
+
if (!startupRetentionSettled) {
|
|
400
|
+
return startupRetentionMaintenancePromise.then(() => {
|
|
401
|
+
const resumed = createQueryFromOpts(opts, queryFunction);
|
|
402
|
+
return resumed === true ? void 0 : resumed;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
138
405
|
const key = generateQueryKeyFromOptions(opts);
|
|
139
406
|
const hashedQueryKey = queryCore.hashKey(key);
|
|
140
407
|
const extendedMeta = { ...meta, loadSubsetOptions: opts };
|
|
408
|
+
const retainedEntry = metadata?.collection.get(
|
|
409
|
+
`${QUERY_COLLECTION_GC_PREFIX}${hashedQueryKey}`
|
|
410
|
+
);
|
|
411
|
+
if (parsePersistedQueryRetentionEntry(retainedEntry, hashedQueryKey) !== void 0) {
|
|
412
|
+
retainedQueriesPendingRevalidation.add(hashedQueryKey);
|
|
413
|
+
}
|
|
414
|
+
cancelPersistedRetentionExpiry(hashedQueryKey);
|
|
415
|
+
validateQueryKeyPrefix(key);
|
|
141
416
|
if (state.observers.has(hashedQueryKey)) {
|
|
142
417
|
queryRefCounts.set(
|
|
143
418
|
hashedQueryKey,
|
|
@@ -183,8 +458,18 @@ function queryCollectionOptions(config) {
|
|
|
183
458
|
...staleTime !== void 0 && { staleTime }
|
|
184
459
|
};
|
|
185
460
|
const localObserver = new queryCore.QueryObserver(queryClient, observerOptions);
|
|
461
|
+
const resolvedQueryGcTime = queryClient.getQueryCache().find({
|
|
462
|
+
queryKey: key,
|
|
463
|
+
exact: true
|
|
464
|
+
})?.gcTime;
|
|
465
|
+
const effectivePersistedGcTime = persistedGcTime ?? resolvedQueryGcTime;
|
|
186
466
|
hashToQueryKey.set(hashedQueryKey, key);
|
|
187
467
|
state.observers.set(hashedQueryKey, localObserver);
|
|
468
|
+
if (effectivePersistedGcTime !== void 0) {
|
|
469
|
+
effectivePersistedGcTimes.set(hashedQueryKey, effectivePersistedGcTime);
|
|
470
|
+
} else {
|
|
471
|
+
effectivePersistedGcTimes.delete(hashedQueryKey);
|
|
472
|
+
}
|
|
188
473
|
queryRefCounts.set(
|
|
189
474
|
hashedQueryKey,
|
|
190
475
|
(queryRefCounts.get(hashedQueryKey) || 0) + 1
|
|
@@ -214,47 +499,102 @@ function queryCollectionOptions(config) {
|
|
|
214
499
|
}
|
|
215
500
|
return readyPromise;
|
|
216
501
|
};
|
|
502
|
+
const applySuccessfulResult = (queryKey2, result, persistedBaseline) => {
|
|
503
|
+
const hashedQueryKey = queryCore.hashKey(queryKey2);
|
|
504
|
+
if (collection.status === `cleaned-up`) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
state.lastError = void 0;
|
|
508
|
+
state.errorCount = 0;
|
|
509
|
+
const rawData = result.data;
|
|
510
|
+
const newItemsArray = select ? select(rawData) : rawData;
|
|
511
|
+
if (!Array.isArray(newItemsArray) || newItemsArray.some((item) => typeof item !== `object`)) {
|
|
512
|
+
const errorMessage = select ? `@tanstack/query-db-collection: select() must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey2)}` : `@tanstack/query-db-collection: queryFn must return an array of objects. Got: ${typeof newItemsArray} for queryKey ${JSON.stringify(queryKey2)}`;
|
|
513
|
+
console.error(errorMessage);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const currentSyncedItems = new Map(
|
|
517
|
+
collection._state.syncedData.entries()
|
|
518
|
+
);
|
|
519
|
+
const shouldUsePersistedBaseline = persistedBaseline !== void 0;
|
|
520
|
+
const previouslyOwnedRows = shouldUsePersistedBaseline ? new Set(persistedBaseline.keys()) : getHydratedOwnedRowsForQueryBaseline(hashedQueryKey);
|
|
521
|
+
const newItemsMap = /* @__PURE__ */ new Map();
|
|
522
|
+
newItemsArray.forEach((item) => {
|
|
523
|
+
const key = getKey(item);
|
|
524
|
+
newItemsMap.set(key, item);
|
|
525
|
+
});
|
|
526
|
+
begin();
|
|
527
|
+
if (metadata) {
|
|
528
|
+
metadata.collection.delete(
|
|
529
|
+
`${QUERY_COLLECTION_GC_PREFIX}${hashedQueryKey}`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
previouslyOwnedRows.forEach((key) => {
|
|
533
|
+
const oldItem = shouldUsePersistedBaseline ? persistedBaseline.get(key)?.value : currentSyncedItems.get(key);
|
|
534
|
+
if (!oldItem) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const newItem = newItemsMap.get(key);
|
|
538
|
+
if (!newItem) {
|
|
539
|
+
const owners = getPersistedOwners(key);
|
|
540
|
+
owners.delete(hashedQueryKey);
|
|
541
|
+
setPersistedOwners(key, owners);
|
|
542
|
+
const needToRemove = removeRow(key, hashedQueryKey);
|
|
543
|
+
if (needToRemove) {
|
|
544
|
+
write({ type: `delete`, value: oldItem });
|
|
545
|
+
}
|
|
546
|
+
} else if (!db.deepEquals(oldItem, newItem)) {
|
|
547
|
+
write({ type: `update`, value: newItem });
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
newItemsMap.forEach((newItem, key) => {
|
|
551
|
+
const owners = getPersistedOwners(key);
|
|
552
|
+
if (!owners.has(hashedQueryKey)) {
|
|
553
|
+
owners.add(hashedQueryKey);
|
|
554
|
+
setPersistedOwners(key, owners);
|
|
555
|
+
}
|
|
556
|
+
addRow(key, hashedQueryKey);
|
|
557
|
+
if (!currentSyncedItems.has(key)) {
|
|
558
|
+
write({ type: `insert`, value: newItem });
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
commit();
|
|
562
|
+
retainedQueriesPendingRevalidation.delete(hashedQueryKey);
|
|
563
|
+
cancelPersistedRetentionExpiry(hashedQueryKey);
|
|
564
|
+
markReady();
|
|
565
|
+
};
|
|
566
|
+
const reconcileSuccessfulResult = async (queryKey2, result) => {
|
|
567
|
+
const hashedQueryKey = queryCore.hashKey(queryKey2);
|
|
568
|
+
const persistedBaseline = await loadPersistedBaselineForQuery(hashedQueryKey);
|
|
569
|
+
if (collection.status === `cleaned-up`) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
applySuccessfulResult(queryKey2, result, persistedBaseline);
|
|
573
|
+
};
|
|
217
574
|
const makeQueryResultHandler = (queryKey2) => {
|
|
218
575
|
const hashedQueryKey = queryCore.hashKey(queryKey2);
|
|
219
576
|
const handleQueryResult = (result) => {
|
|
220
577
|
if (result.isSuccess) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
578
|
+
if (collection.deferDataRefresh) {
|
|
579
|
+
collection.deferDataRefresh.then(() => {
|
|
580
|
+
const observer = state.observers.get(hashedQueryKey);
|
|
581
|
+
if (observer) {
|
|
582
|
+
observer.refetch().catch(() => {
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
});
|
|
228
586
|
return;
|
|
229
587
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const newItem = newItemsMap.get(key);
|
|
241
|
-
if (!newItem) {
|
|
242
|
-
const needToRemove = removeRow(key, hashedQueryKey);
|
|
243
|
-
if (needToRemove) {
|
|
244
|
-
write({ type: `delete`, value: oldItem });
|
|
245
|
-
}
|
|
246
|
-
} else if (!db.deepEquals(oldItem, newItem)) {
|
|
247
|
-
write({ type: `update`, value: newItem });
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
newItemsMap.forEach((newItem, key) => {
|
|
251
|
-
addRow(key, hashedQueryKey);
|
|
252
|
-
if (!currentSyncedItems.has(key)) {
|
|
253
|
-
write({ type: `insert`, value: newItem });
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
commit();
|
|
257
|
-
markReady();
|
|
588
|
+
if (retainedQueriesPendingRevalidation.has(hashedQueryKey)) {
|
|
589
|
+
void reconcileSuccessfulResult(queryKey2, result).catch((error) => {
|
|
590
|
+
console.error(
|
|
591
|
+
`[QueryCollection] Error reconciling query ${String(queryKey2)}:`,
|
|
592
|
+
error
|
|
593
|
+
);
|
|
594
|
+
});
|
|
595
|
+
} else {
|
|
596
|
+
applySuccessfulResult(queryKey2, result);
|
|
597
|
+
}
|
|
258
598
|
} else if (result.isError) {
|
|
259
599
|
const isNewError = result.errorUpdatedAt !== state.lastErrorUpdatedAt || result.error !== state.lastError;
|
|
260
600
|
if (isNewError) {
|
|
@@ -313,7 +653,13 @@ function queryCollectionOptions(config) {
|
|
|
313
653
|
});
|
|
314
654
|
}
|
|
315
655
|
} else {
|
|
316
|
-
|
|
656
|
+
if (startupRetentionSettled) {
|
|
657
|
+
markReady();
|
|
658
|
+
} else {
|
|
659
|
+
void startupRetentionMaintenancePromise.then(() => {
|
|
660
|
+
markReady();
|
|
661
|
+
});
|
|
662
|
+
}
|
|
317
663
|
}
|
|
318
664
|
subscribeToQueries();
|
|
319
665
|
state.observers.forEach((observer, hashedQueryKey) => {
|
|
@@ -324,36 +670,56 @@ function queryCollectionOptions(config) {
|
|
|
324
670
|
const cleanupQueryInternal = (hashedQueryKey) => {
|
|
325
671
|
unsubscribes.get(hashedQueryKey)?.();
|
|
326
672
|
unsubscribes.delete(hashedQueryKey);
|
|
673
|
+
cancelPersistedRetentionExpiry(hashedQueryKey);
|
|
674
|
+
retainedQueriesPendingRevalidation.delete(hashedQueryKey);
|
|
327
675
|
const rowKeys = queryToRows.get(hashedQueryKey) ?? /* @__PURE__ */ new Set();
|
|
676
|
+
const nextOwnersByRow = /* @__PURE__ */ new Map();
|
|
328
677
|
const rowsToDelete = [];
|
|
329
678
|
rowKeys.forEach((rowKey) => {
|
|
330
679
|
const queries = rowToQueries.get(rowKey);
|
|
331
680
|
if (!queries) {
|
|
332
681
|
return;
|
|
333
682
|
}
|
|
334
|
-
queries
|
|
335
|
-
|
|
683
|
+
const nextOwners = new Set(queries);
|
|
684
|
+
nextOwners.delete(hashedQueryKey);
|
|
685
|
+
nextOwnersByRow.set(rowKey, nextOwners);
|
|
686
|
+
if (nextOwners.size === 0 && collection.has(rowKey)) {
|
|
687
|
+
rowsToDelete.push(collection.get(rowKey));
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
const shouldWriteMetadata = metadata !== void 0 && nextOwnersByRow.size > 0;
|
|
691
|
+
const needsTransaction = shouldWriteMetadata || rowsToDelete.length > 0;
|
|
692
|
+
if (needsTransaction) {
|
|
693
|
+
begin();
|
|
694
|
+
}
|
|
695
|
+
nextOwnersByRow.forEach((owners, rowKey) => {
|
|
696
|
+
if (owners.size === 0) {
|
|
336
697
|
rowToQueries.delete(rowKey);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
698
|
+
} else {
|
|
699
|
+
rowToQueries.set(rowKey, owners);
|
|
700
|
+
}
|
|
701
|
+
if (shouldWriteMetadata) {
|
|
702
|
+
setPersistedOwners(rowKey, owners);
|
|
340
703
|
}
|
|
341
704
|
});
|
|
342
705
|
if (rowsToDelete.length > 0) {
|
|
343
|
-
begin();
|
|
344
706
|
rowsToDelete.forEach((row) => {
|
|
345
707
|
write({ type: `delete`, value: row });
|
|
346
708
|
});
|
|
709
|
+
}
|
|
710
|
+
if (needsTransaction) {
|
|
347
711
|
commit();
|
|
348
712
|
}
|
|
349
713
|
state.observers.delete(hashedQueryKey);
|
|
350
714
|
queryToRows.delete(hashedQueryKey);
|
|
351
715
|
hashToQueryKey.delete(hashedQueryKey);
|
|
352
716
|
queryRefCounts.delete(hashedQueryKey);
|
|
717
|
+
effectivePersistedGcTimes.delete(hashedQueryKey);
|
|
353
718
|
};
|
|
354
719
|
const cleanupQueryIfIdle = (hashedQueryKey) => {
|
|
355
720
|
const refcount = queryRefCounts.get(hashedQueryKey) || 0;
|
|
356
721
|
const observer = state.observers.get(hashedQueryKey);
|
|
722
|
+
const effectivePersistedGcTime = effectivePersistedGcTimes.get(hashedQueryKey);
|
|
357
723
|
if (refcount <= 0) {
|
|
358
724
|
unsubscribes.get(hashedQueryKey)?.();
|
|
359
725
|
unsubscribes.delete(hashedQueryKey);
|
|
@@ -369,6 +735,31 @@ function queryCollectionOptions(config) {
|
|
|
369
735
|
{ hashedQueryKey }
|
|
370
736
|
);
|
|
371
737
|
}
|
|
738
|
+
if (effectivePersistedGcTime !== void 0 && metadata && persistedMetadata?.row.scanPersisted) {
|
|
739
|
+
begin();
|
|
740
|
+
metadata.collection.set(
|
|
741
|
+
`${QUERY_COLLECTION_GC_PREFIX}${hashedQueryKey}`,
|
|
742
|
+
{
|
|
743
|
+
queryHash: hashedQueryKey,
|
|
744
|
+
mode: effectivePersistedGcTime === Number.POSITIVE_INFINITY ? `until-revalidated` : `ttl`,
|
|
745
|
+
...effectivePersistedGcTime === Number.POSITIVE_INFINITY ? {} : { expiresAt: Date.now() + effectivePersistedGcTime }
|
|
746
|
+
}
|
|
747
|
+
);
|
|
748
|
+
commit();
|
|
749
|
+
if (effectivePersistedGcTime !== Number.POSITIVE_INFINITY) {
|
|
750
|
+
schedulePersistedRetentionExpiry({
|
|
751
|
+
queryHash: hashedQueryKey,
|
|
752
|
+
mode: `ttl`,
|
|
753
|
+
expiresAt: Date.now() + effectivePersistedGcTime
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
unsubscribes.get(hashedQueryKey)?.();
|
|
757
|
+
unsubscribes.delete(hashedQueryKey);
|
|
758
|
+
state.observers.delete(hashedQueryKey);
|
|
759
|
+
hashToQueryKey.delete(hashedQueryKey);
|
|
760
|
+
queryRefCounts.set(hashedQueryKey, 0);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
372
763
|
cleanupQueryInternal(hashedQueryKey);
|
|
373
764
|
};
|
|
374
765
|
const forceCleanupQuery = (hashedQueryKey) => {
|
|
@@ -385,6 +776,10 @@ function queryCollectionOptions(config) {
|
|
|
385
776
|
const cleanup = async () => {
|
|
386
777
|
unsubscribeFromCollectionEvents();
|
|
387
778
|
unsubscribeFromQueries();
|
|
779
|
+
persistedRetentionTimers.forEach((timer) => {
|
|
780
|
+
clearTimeout(timer);
|
|
781
|
+
});
|
|
782
|
+
persistedRetentionTimers.clear();
|
|
388
783
|
const allQueryKeys = [...hashToQueryKey.values()];
|
|
389
784
|
const allHashedKeys = [...state.observers.keys()];
|
|
390
785
|
for (const hashedKey of allHashedKeys) {
|
|
@@ -465,13 +860,12 @@ function queryCollectionOptions(config) {
|
|
|
465
860
|
}
|
|
466
861
|
};
|
|
467
862
|
const updateCacheData = (items) => {
|
|
468
|
-
const
|
|
469
|
-
if (
|
|
470
|
-
for (const
|
|
471
|
-
updateCacheDataForKey(
|
|
863
|
+
const allCached = queryClient.getQueryCache().findAll({ queryKey: baseKey });
|
|
864
|
+
if (allCached.length > 0) {
|
|
865
|
+
for (const query of allCached) {
|
|
866
|
+
updateCacheDataForKey(query.queryKey, items);
|
|
472
867
|
}
|
|
473
868
|
} else {
|
|
474
|
-
const baseKey = typeof queryKey === `function` ? queryKey({}) : queryKey;
|
|
475
869
|
updateCacheDataForKey(baseKey, items);
|
|
476
870
|
}
|
|
477
871
|
};
|