authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-03-11 15:27:52-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-03-11 16:27:26-07:00
log4b09ff4dba5fad5b7e2b44a0bdc2789918f74561
treea864dc511e5a7281e78b633b9c2247492adfd226
parentec65067d04a72a51b71b63d3b4569843b8fd0dad
signaturebadge-check Signed by SSH key SHA256:cOKiuRFOeSRxne6EWgHtdQQSlBxjOXm2hOCFnCdLQbQ

fix: allow spreading `TanstackQueryOptimisticHelpers`


2 files changed, 76 insertions(+), 59 deletions(-)

src/tanstack-query.ts+59-59
...@@ -47,10 +47,10 @@ class TanstackQueryOptimisticHelpers {...@@ -47,10 +47,10 @@ class TanstackQueryOptimisticHelpers {
47 * Set the entire query data.47 * Set the entire query data.
48 * If the query doesn't exist, the new query is created.48 * If the query doesn't exist, the new query is created.
49 */49 */
50 set<Data>(50 set = <Data>(
51 queryKey: QueryKeyAndFn<Data>,51 queryKey: QueryKeyAndFn<Data>,
52 value: Data | ((prev: Data | undefined) => Data | undefined),52 value: Data | ((prev: Data | undefined) => Data | undefined),
53 ) {53 ) => {
54 const prev = this.#get(queryKey);54 const prev = this.#get(queryKey);
5555
56 const newValue = typeof value === "function"56 const newValue = typeof value === "function"
...@@ -68,16 +68,16 @@ class TanstackQueryOptimisticHelpers {...@@ -68,16 +68,16 @@ class TanstackQueryOptimisticHelpers {
68 this.#set(queryKey, prev);68 this.#set(queryKey, prev);
69 }69 }
70 });70 });
71 }71 };
7272
73 /**73 /**
74 * Update the entire query data.74 * Update the entire query data.
75 * If the query doesn't exist, it cancels75 * If the query doesn't exist, it cancels
76 */76 */
77 updateExisting<Data>(77 updateExisting = <Data>(
78 queryKey: QueryKeyAndFn<Data>,78 queryKey: QueryKeyAndFn<Data>,
79 value: Data | ((prev: Data) => Data),79 value: Data | ((prev: Data) => Data),
80 ) {80 ) => {
81 const prev = this.#get(queryKey);81 const prev = this.#get(queryKey);
82 if (!prev) return;82 if (!prev) return;
8383
...@@ -89,14 +89,14 @@ class TanstackQueryOptimisticHelpers {...@@ -89,14 +89,14 @@ class TanstackQueryOptimisticHelpers {
89 this.#onRestore(() => {89 this.#onRestore(() => {
90 this.#set(queryKey, prev);90 this.#set(queryKey, prev);
91 });91 });
92 }92 };
9393
94 /**94 /**
95 * Mark a query as changed and schedule a refetch.95 * Mark a query as changed and schedule a refetch.
96 * Does not modify any data.96 * Does not modify any data.
97 * If the query doesn't exist, the updater is skipped.97 * If the query doesn't exist, the updater is skipped.
98 */98 */
99 refetchOnSettled(queryKey: QueryKeyAndFn) {99 refetchOnSettled = (queryKey: QueryKeyAndFn) => {
100 if (!queryKey.queryFn) return;100 if (!queryKey.queryFn) return;
101 const state = this.#client.getQueryCache().find({101 const state = this.#client.getQueryCache().find({
102 queryKey: queryKey.queryKey,102 queryKey: queryKey.queryKey,
...@@ -109,19 +109,19 @@ class TanstackQueryOptimisticHelpers {...@@ -109,19 +109,19 @@ class TanstackQueryOptimisticHelpers {
109 await this.#client.invalidateQueries(queryKey);109 await this.#client.invalidateQueries(queryKey);
110 });110 });
111 } catch { /* Ignore expiry */ }111 } catch { /* Ignore expiry */ }
112 }112 };
113113
114 /**114 /**
115 * Set a property at an object path.115 * Set a property at an object path.
116 * If the query or path doesn't exist, the updater is skipped.116 * If the query or path doesn't exist, the updater is skipped.
117 */117 */
118 objSet<Data extends object, const Path extends AllObjectPaths<Data>>(118 objSet = <Data extends object, const Path extends AllObjectPaths<Data>>(
119 queryKey: QueryKeyAndFn<Data>,119 queryKey: QueryKeyAndFn<Data>,
120 path: Path,120 path: Path,
121 value:121 value:
122 | Exclude<GetObjectPath<Data, Path>, Function>122 | Exclude<GetObjectPath<Data, Path>, Function>
123 | ((prev: GetObjectPath<Data, Path>) => GetObjectPath<Data, Path>),123 | ((prev: GetObjectPath<Data, Path>) => GetObjectPath<Data, Path>),
124 ) {124 ) => {
125 const prev = this.#get(queryKey);125 const prev = this.#get(queryKey);
126 if (!prev) return;126 if (!prev) return;
127 const { value: original, exists } = getPath(prev, path);127 const { value: original, exists } = getPath(prev, path);
...@@ -145,20 +145,20 @@ class TanstackQueryOptimisticHelpers {...@@ -145,20 +145,20 @@ class TanstackQueryOptimisticHelpers {
145 this.#onRestore(() => {145 this.#onRestore(() => {
146 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);146 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
147 });147 });
148 }148 };
149149
150 /**150 /**
151 * Increment a numeric property at an object path.151 * Increment a numeric property at an object path.
152 * If the query or path doesn't exist, the updater is skipped.152 * If the query or path doesn't exist, the updater is skipped.
153 */153 */
154 objIncrement<154 objIncrement = <
155 Data extends object,155 Data extends object,
156 const Path extends AllObjectPaths<Data>,156 const Path extends AllObjectPaths<Data>,
157 >(157 >(
158 queryKey: QueryKeyAndFn<Data>,158 queryKey: QueryKeyAndFn<Data>,
159 path: Path,159 path: Path,
160 amount: number = 1,160 amount: number = 1,
161 ) {161 ) => {
162 const prev = this.#get(queryKey);162 const prev = this.#get(queryKey);
163 if (!prev) return;163 if (!prev) return;
164 const { value: original, exists } = getPath(prev, path);164 const { value: original, exists } = getPath(prev, path);
...@@ -171,20 +171,20 @@ class TanstackQueryOptimisticHelpers {...@@ -171,20 +171,20 @@ class TanstackQueryOptimisticHelpers {
171 this.#onRestore(() => {171 this.#onRestore(() => {
172 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);172 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
173 });173 });
174 }174 };
175175
176 /**176 /**
177 * Decrement a numeric property at an object path.177 * Decrement a numeric property at an object path.
178 * If the query or path doesn't exist, the updater is skipped.178 * If the query or path doesn't exist, the updater is skipped.
179 */179 */
180 objDecrement<180 objDecrement = <
181 Data extends object,181 Data extends object,
182 const Path extends AllObjectPaths<Data>,182 const Path extends AllObjectPaths<Data>,
183 >(183 >(
184 queryKey: QueryKeyAndFn<Data>,184 queryKey: QueryKeyAndFn<Data>,
185 path: Path,185 path: Path,
186 amount: number = 1,186 amount: number = 1,
187 ) {187 ) => {
188 const prev = this.#get(queryKey);188 const prev = this.#get(queryKey);
189 if (!prev) return;189 if (!prev) return;
190 const { value: original, exists } = getPath(prev, path);190 const { value: original, exists } = getPath(prev, path);
...@@ -197,16 +197,16 @@ class TanstackQueryOptimisticHelpers {...@@ -197,16 +197,16 @@ class TanstackQueryOptimisticHelpers {
197 this.#onRestore(() => {197 this.#onRestore(() => {
198 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);198 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
199 });199 });
200 }200 };
201201
202 /**202 /**
203 * Toggle a boolean property at an object path.203 * Toggle a boolean property at an object path.
204 * If the query or path doesn't exist, the updater is skipped.204 * If the query or path doesn't exist, the updater is skipped.
205 */205 */
206 objToggle<Data extends object, const Path extends AllObjectPaths<Data>>(206 objToggle = <Data extends object, const Path extends AllObjectPaths<Data>>(
207 queryKey: QueryKeyAndFn<Data>,207 queryKey: QueryKeyAndFn<Data>,
208 path: Path,208 path: Path,
209 ) {209 ) => {
210 const prev = this.#get(queryKey);210 const prev = this.#get(queryKey);
211 if (!prev) return;211 if (!prev) return;
212 const { value: original, exists } = getPath(prev, path);212 const { value: original, exists } = getPath(prev, path);
...@@ -219,17 +219,17 @@ class TanstackQueryOptimisticHelpers {...@@ -219,17 +219,17 @@ class TanstackQueryOptimisticHelpers {
219 this.#onRestore(() => {219 this.#onRestore(() => {
220 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);220 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
221 });221 });
222 }222 };
223223
224 /**224 /**
225 * Shallow merge multiple properties at an object path.225 * Shallow merge multiple properties at an object path.
226 * If the query or path doesn't exist, the updater is skipped.226 * If the query or path doesn't exist, the updater is skipped.
227 */227 */
228 objSetMany<Data extends object, const Path extends AllObjectPaths<Data>>(228 objSetMany = <Data extends object, const Path extends AllObjectPaths<Data>>(
229 queryKey: QueryKeyAndFn<Data>,229 queryKey: QueryKeyAndFn<Data>,
230 path: Path,230 path: Path,
231 updates: Partial<GetObjectPath<Data, Path>>,231 updates: Partial<GetObjectPath<Data, Path>>,
232 ) {232 ) => {
233 const prev = this.#get(queryKey);233 const prev = this.#get(queryKey);
234 if (!prev) return;234 if (!prev) return;
235 const { value: original, exists } = getPath(prev, path);235 const { value: original, exists } = getPath(prev, path);
...@@ -248,13 +248,13 @@ class TanstackQueryOptimisticHelpers {...@@ -248,13 +248,13 @@ class TanstackQueryOptimisticHelpers {
248 this.#onRestore(() => {248 this.#onRestore(() => {
249 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);249 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
250 });250 });
251 }251 };
252252
253 /**253 /**
254 * Push item(s) to the end of an array at an object path.254 * Push item(s) to the end of an array at an object path.
255 * If the query or path doesn't exist, the updater is skipped.255 * If the query or path doesn't exist, the updater is skipped.
256 */256 */
257 objArrayPush<257 objArrayPush = <
258 Data extends object,258 Data extends object,
259 const Path extends AllObjectPaths<Data>,259 const Path extends AllObjectPaths<Data>,
260 >(260 >(
...@@ -262,7 +262,7 @@ class TanstackQueryOptimisticHelpers {...@@ -262,7 +262,7 @@ class TanstackQueryOptimisticHelpers {
262 path: Path,262 path: Path,
263 ...items: GetObjectPath<Data, Path> extends Array<infer T> ? T[]263 ...items: GetObjectPath<Data, Path> extends Array<infer T> ? T[]
264 : never264 : never
265 ) {265 ) => {
266 const prev = this.#get(queryKey);266 const prev = this.#get(queryKey);
267 if (!prev) return;267 if (!prev) return;
268 const { value: original, exists } = getPath(prev, path);268 const { value: original, exists } = getPath(prev, path);
...@@ -276,13 +276,13 @@ class TanstackQueryOptimisticHelpers {...@@ -276,13 +276,13 @@ class TanstackQueryOptimisticHelpers {
276 this.#onRestore(() => {276 this.#onRestore(() => {
277 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);277 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
278 });278 });
279 }279 };
280280
281 /**281 /**
282 * Add item(s) to the beginning of an array at an object path.282 * Add item(s) to the beginning of an array at an object path.
283 * If the query or path doesn't exist, the updater is skipped.283 * If the query or path doesn't exist, the updater is skipped.
284 */284 */
285 objArrayUnshift<285 objArrayUnshift = <
286 Data extends object,286 Data extends object,
287 const Path extends AllObjectPaths<Data>,287 const Path extends AllObjectPaths<Data>,
288 >(288 >(
...@@ -290,7 +290,7 @@ class TanstackQueryOptimisticHelpers {...@@ -290,7 +290,7 @@ class TanstackQueryOptimisticHelpers {
290 path: Path,290 path: Path,
291 ...items: GetObjectPath<Data, Path> extends Array<infer T> ? T[]291 ...items: GetObjectPath<Data, Path> extends Array<infer T> ? T[]
292 : never292 : never
293 ) {293 ) => {
294 const prev = this.#get(queryKey);294 const prev = this.#get(queryKey);
295 if (!prev) return;295 if (!prev) return;
296 const { value: original, exists } = getPath(prev, path);296 const { value: original, exists } = getPath(prev, path);
...@@ -304,13 +304,13 @@ class TanstackQueryOptimisticHelpers {...@@ -304,13 +304,13 @@ class TanstackQueryOptimisticHelpers {
304 this.#onRestore(() => {304 this.#onRestore(() => {
305 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);305 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
306 });306 });
307 }307 };
308308
309 /**309 /**
310 * Remove items from an array that match `filter`.310 * Remove items from an array that match `filter`.
311 * If the query or path doesn't exist, the updater is skipped.311 * If the query or path doesn't exist, the updater is skipped.
312 */312 */
313 objArrayRemove<313 objArrayRemove = <
314 Data extends object,314 Data extends object,
315 const Path extends AllObjectPaths<Data>,315 const Path extends AllObjectPaths<Data>,
316 >(316 >(
...@@ -320,7 +320,7 @@ class TanstackQueryOptimisticHelpers {...@@ -320,7 +320,7 @@ class TanstackQueryOptimisticHelpers {
320 item: GetObjectPath<Data, Path> extends Array<infer T> ? T : never,320 item: GetObjectPath<Data, Path> extends Array<infer T> ? T : never,
321 index: number,321 index: number,
322 ) => boolean,322 ) => boolean,
323 ) {323 ) => {
324 const prev = this.#get(queryKey);324 const prev = this.#get(queryKey);
325 if (!prev) return;325 if (!prev) return;
326 const { value: original, exists } = getPath(prev, path);326 const { value: original, exists } = getPath(prev, path);
...@@ -335,13 +335,13 @@ class TanstackQueryOptimisticHelpers {...@@ -335,13 +335,13 @@ class TanstackQueryOptimisticHelpers {
335 // TODO: splice items back in case original changed335 // TODO: splice items back in case original changed
336 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);336 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
337 });337 });
338 }338 };
339339
340 /**340 /**
341 * Filter items to just include items that match `filter`. This is the inverse of `objArrayRemove`341 * Filter items to just include items that match `filter`. This is the inverse of `objArrayRemove`
342 * If the query or path doesn't exist, the updater is skipped.342 * If the query or path doesn't exist, the updater is skipped.
343 */343 */
344 objArrayFilter<344 objArrayFilter = <
345 Data extends object,345 Data extends object,
346 const Path extends AllObjectPaths<Data>,346 const Path extends AllObjectPaths<Data>,
347 >(347 >(
...@@ -351,7 +351,7 @@ class TanstackQueryOptimisticHelpers {...@@ -351,7 +351,7 @@ class TanstackQueryOptimisticHelpers {
351 item: GetObjectPath<Data, Path> extends Array<infer T> ? T : never,351 item: GetObjectPath<Data, Path> extends Array<infer T> ? T : never,
352 index: number,352 index: number,
353 ) => boolean,353 ) => boolean,
354 ) {354 ) => {
355 const prev = this.#get(queryKey);355 const prev = this.#get(queryKey);
356 if (!prev) return;356 if (!prev) return;
357 const { value: original, exists } = getPath(prev, path);357 const { value: original, exists } = getPath(prev, path);
...@@ -366,13 +366,13 @@ class TanstackQueryOptimisticHelpers {...@@ -366,13 +366,13 @@ class TanstackQueryOptimisticHelpers {
366 // TODO: splice items back in case original changed366 // TODO: splice items back in case original changed
367 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);367 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
368 });368 });
369 }369 };
370370
371 /**371 /**
372 * Update items in an array that match a predicate.372 * Update items in an array that match a predicate.
373 * If the query or path doesn't exist, the updater is skipped.373 * If the query or path doesn't exist, the updater is skipped.
374 */374 */
375 objArrayUpdate<375 objArrayUpdate = <
376 Data extends object,376 Data extends object,
377 const Path extends AllObjectPaths<Data>,377 const Path extends AllObjectPaths<Data>,
378 >(378 >(
...@@ -387,7 +387,7 @@ class TanstackQueryOptimisticHelpers {...@@ -387,7 +387,7 @@ class TanstackQueryOptimisticHelpers {
387 item: GetObjectPath<Data, Path> extends Array<infer T> ? T : never,387 item: GetObjectPath<Data, Path> extends Array<infer T> ? T : never,
388 ) => GetObjectPath<Data, Path> extends Array<infer T> ? T : never;388 ) => GetObjectPath<Data, Path> extends Array<infer T> ? T : never;
389 },389 },
390 ) {390 ) => {
391 const prev = this.#get(queryKey);391 const prev = this.#get(queryKey);
392 if (!prev) return;392 if (!prev) return;
393 const { value: original, exists } = getPath(prev, path);393 const { value: original, exists } = getPath(prev, path);
...@@ -402,13 +402,13 @@ class TanstackQueryOptimisticHelpers {...@@ -402,13 +402,13 @@ class TanstackQueryOptimisticHelpers {
402 // TODO: splice items back in case original changed402 // TODO: splice items back in case original changed
403 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);403 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
404 });404 });
405 }405 };
406406
407 /**407 /**
408 * Insert item(s) at a specific index in an array.408 * Insert item(s) at a specific index in an array.
409 * If the query or path doesn't exist, the updater is skipped.409 * If the query or path doesn't exist, the updater is skipped.
410 */410 */
411 objArrayInsertIndex<411 objArrayInsertIndex = <
412 Data extends object,412 Data extends object,
413 const Path extends AllObjectPaths<Data>,413 const Path extends AllObjectPaths<Data>,
414 >(414 >(
...@@ -417,7 +417,7 @@ class TanstackQueryOptimisticHelpers {...@@ -417,7 +417,7 @@ class TanstackQueryOptimisticHelpers {
417 index: number,417 index: number,
418 ...items: GetObjectPath<Data, Path> extends Array<infer T> ? T[]418 ...items: GetObjectPath<Data, Path> extends Array<infer T> ? T[]
419 : never419 : never
420 ) {420 ) => {
421 const prev = this.#get(queryKey);421 const prev = this.#get(queryKey);
422 if (!prev) return;422 if (!prev) return;
423 const { value: original, exists } = getPath(prev, path);423 const { value: original, exists } = getPath(prev, path);
...@@ -436,13 +436,13 @@ class TanstackQueryOptimisticHelpers {...@@ -436,13 +436,13 @@ class TanstackQueryOptimisticHelpers {
436 // TODO: splice items back in case original changed436 // TODO: splice items back in case original changed
437 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);437 this.#set(queryKey, (obj) => obj ? setPath(obj, path, original) : obj);
438 });438 });
439 }439 };
440440
441 /**441 /**
442 * Push item(s) to the end of an array at an object path.442 * Push item(s) to the end of an array at an object path.
443 * If the query or path doesn't exist, the updater is skipped.443 * If the query or path doesn't exist, the updater is skipped.
444 */444 */
445 arrayPush<Data>(queryKey: QueryKeyAndFn<Data[] | null>, ...items: Data[]) {445 arrayPush = <Data>(queryKey: QueryKeyAndFn<Data[] | null>, ...items: Data[]) => {
446 const prev = this.#get(queryKey);446 const prev = this.#get(queryKey);
447 if (!prev || !Array.isArray(prev)) return;447 if (!prev || !Array.isArray(prev)) return;
448448
...@@ -454,13 +454,13 @@ class TanstackQueryOptimisticHelpers {...@@ -454,13 +454,13 @@ class TanstackQueryOptimisticHelpers {
454 (old) => old ? old.filter((x) => !items.includes(x)) : old,454 (old) => old ? old.filter((x) => !items.includes(x)) : old,
455 );455 );
456 });456 });
457 }457 };
458458
459 /**459 /**
460 * Add item(s) to the beginning of an array at an object path.460 * Add item(s) to the beginning of an array at an object path.
461 * If the query or path doesn't exist, the updater is skipped.461 * If the query or path doesn't exist, the updater is skipped.
462 */462 */
463 arrayUnshift<Data>(queryKey: QueryKeyAndFn<Data[] | null>, ...items: Data[]) {463 arrayUnshift = <Data>(queryKey: QueryKeyAndFn<Data[] | null>, ...items: Data[]) => {
464 const prev = this.#get(queryKey);464 const prev = this.#get(queryKey);
465 if (!prev || !Array.isArray(prev)) return;465 if (!prev || !Array.isArray(prev)) return;
466466
...@@ -472,19 +472,19 @@ class TanstackQueryOptimisticHelpers {...@@ -472,19 +472,19 @@ class TanstackQueryOptimisticHelpers {
472 (old) => old ? old.filter((x) => !items.includes(x)) : old,472 (old) => old ? old.filter((x) => !items.includes(x)) : old,
473 );473 );
474 });474 });
475 }475 };
476476
477 /**477 /**
478 * Remove items from an array that match a `filter`.478 * Remove items from an array that match a `filter`.
479 * If the query or path doesn't exist, the updater is skipped.479 * If the query or path doesn't exist, the updater is skipped.
480 */480 */
481 arrayRemove<Data>(481 arrayRemove = <Data>(
482 queryKey: QueryKeyAndFn<Data[] | null>,482 queryKey: QueryKeyAndFn<Data[] | null>,
483 filter: (483 filter: (
484 item: Data,484 item: Data,
485 index: number,485 index: number,
486 ) => boolean,486 ) => boolean,
487 ) {487 ) => {
488 const prev = this.#get(queryKey);488 const prev = this.#get(queryKey);
489 if (!prev || !Array.isArray(prev)) return;489 if (!prev || !Array.isArray(prev)) return;
490490
...@@ -494,19 +494,19 @@ class TanstackQueryOptimisticHelpers {...@@ -494,19 +494,19 @@ class TanstackQueryOptimisticHelpers {
494 // TODO: splice items back in case original changed494 // TODO: splice items back in case original changed
495 this.#set(queryKey, prev);495 this.#set(queryKey, prev);
496 });496 });
497 }497 };
498498
499 /**499 /**
500 * Filter items to just include items that match `filter`. This is the inverse of `arrayRemove`500 * Filter items to just include items that match `filter`. This is the inverse of `arrayRemove`
501 * If the query or path doesn't exist, the updater is skipped.501 * If the query or path doesn't exist, the updater is skipped.
502 */502 */
503 arrayFilter<Data>(503 arrayFilter = <Data>(
504 queryKey: QueryKeyAndFn<Data[] | null>,504 queryKey: QueryKeyAndFn<Data[] | null>,
505 filter: (505 filter: (
506 item: Data,506 item: Data,
507 index: number,507 index: number,
508 ) => boolean,508 ) => boolean,
509 ) {509 ) => {
510 const prev = this.#get(queryKey);510 const prev = this.#get(queryKey);
511 if (!prev || !Array.isArray(prev)) return;511 if (!prev || !Array.isArray(prev)) return;
512512
...@@ -516,13 +516,13 @@ class TanstackQueryOptimisticHelpers {...@@ -516,13 +516,13 @@ class TanstackQueryOptimisticHelpers {
516 // TODO: splice items back in case original changed516 // TODO: splice items back in case original changed
517 this.#set(queryKey, prev);517 this.#set(queryKey, prev);
518 });518 });
519 }519 };
520520
521 /**521 /**
522 * Update items in an array that match a `filter`.522 * Update items in an array that match a `filter`.
523 * If the query or path doesn't exist, the updater is skipped.523 * If the query or path doesn't exist, the updater is skipped.
524 */524 */
525 arrayUpdate<Data>(525 arrayUpdate = <Data>(
526 queryKey: QueryKeyAndFn<Data[] | null>,526 queryKey: QueryKeyAndFn<Data[] | null>,
527 {527 {
528 filter,528 filter,
...@@ -531,7 +531,7 @@ class TanstackQueryOptimisticHelpers {...@@ -531,7 +531,7 @@ class TanstackQueryOptimisticHelpers {
531 filter?: (item: Data, index: number) => boolean;531 filter?: (item: Data, index: number) => boolean;
532 update: (item: Data) => Data;532 update: (item: Data) => Data;
533 },533 },
534 ) {534 ) => {
535 const prev = this.#get(queryKey);535 const prev = this.#get(queryKey);
536 if (!prev || !Array.isArray(prev)) return;536 if (!prev || !Array.isArray(prev)) return;
537537
...@@ -541,17 +541,17 @@ class TanstackQueryOptimisticHelpers {...@@ -541,17 +541,17 @@ class TanstackQueryOptimisticHelpers {
541 // TODO: splice items back in case original changed541 // TODO: splice items back in case original changed
542 this.#set(queryKey, prev);542 this.#set(queryKey, prev);
543 });543 });
544 }544 };
545545
546 /**546 /**
547 * Insert item(s) at a specific index in an array.547 * Insert item(s) at a specific index in an array.
548 * If the query or path doesn't exist, the updater is skipped.548 * If the query or path doesn't exist, the updater is skipped.
549 */549 */
550 arrayInsertIndex<Data>(550 arrayInsertIndex = <Data>(
551 queryKey: QueryKeyAndFn<Data[] | null>,551 queryKey: QueryKeyAndFn<Data[] | null>,
552 index: number,552 index: number,
553 ...items: Data[]553 ...items: Data[]
554 ) {554 ) => {
555 const prev = this.#get(queryKey);555 const prev = this.#get(queryKey);
556 if (!prev || !Array.isArray(prev)) return;556 if (!prev || !Array.isArray(prev)) return;
557557
...@@ -565,12 +565,12 @@ class TanstackQueryOptimisticHelpers {...@@ -565,12 +565,12 @@ class TanstackQueryOptimisticHelpers {
565 // TODO: splice items back in case original changed565 // TODO: splice items back in case original changed
566 this.#set(queryKey, prev);566 this.#set(queryKey, prev);
567 });567 });
568 }568 };
569569
570 /**570 /**
571 * Remove a query from the cache entirely.571 * Remove a query from the cache entirely.
572 */572 */
573 removeQuery(queryKey: QueryKeyAndFn) {573 removeQuery = (queryKey: QueryKeyAndFn) => {
574 const prev = this.#get(queryKey);574 const prev = this.#get(queryKey);
575 if (!prev) return;575 if (!prev) return;
576576
...@@ -578,7 +578,7 @@ class TanstackQueryOptimisticHelpers {...@@ -578,7 +578,7 @@ class TanstackQueryOptimisticHelpers {
578 this.#onRestore(() => {578 this.#onRestore(() => {
579 this.#set(queryKey, prev);579 this.#set(queryKey, prev);
580 });580 });
581 }581 };
582}582}
583583
584export function queryClientOptimisticHelpers(584export function queryClientOptimisticHelpers(
test/tanstack-query-helpers.test.ts+17
...@@ -46,6 +46,23 @@ function createTestQueryClient() {...@@ -46,6 +46,23 @@ function createTestQueryClient() {
46 return { client, queryTest };46 return { client, queryTest };
47}47}
4848
49test("helpers can be spread and retain bound this", () => {
50 const { client, queryTest } = createTestQueryClient();
51
52 const helpers = queryClientOptimisticHelpers(client)({
53 onRestore: () => {},
54 onRefetch: () => {},
55 });
56 const spreadHelpers = { ...helpers };
57
58 assertEquals(Object.keys(spreadHelpers).includes("objIncrement"), true);
59
60 spreadHelpers.objIncrement(queryTest, ["count"], 2);
61
62 const result = client.getQueryData<TestData>(queryTest.queryKey);
63 assertEquals(result?.count, 12);
64});
65
49// ============================================================================66// ============================================================================
50// set() tests67// set() tests
51// ============================================================================68// ============================================================================