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