@tanstack/query-db-collection 0.2.15 → 0.2.17

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/src/query.ts CHANGED
@@ -13,14 +13,12 @@ import type {
13
13
  QueryObserverOptions,
14
14
  } from "@tanstack/query-core"
15
15
  import type {
16
+ BaseCollectionConfig,
16
17
  ChangeMessage,
17
18
  CollectionConfig,
18
- DeleteMutationFn,
19
19
  DeleteMutationFnParams,
20
- InsertMutationFn,
21
20
  InsertMutationFnParams,
22
21
  SyncConfig,
23
- UpdateMutationFn,
24
22
  UpdateMutationFnParams,
25
23
  UtilsRecord,
26
24
  } from "@tanstack/db"
@@ -36,37 +34,24 @@ type InferSchemaOutput<T> = T extends StandardSchemaV1
36
34
  : Record<string, unknown>
37
35
  : Record<string, unknown>
38
36
 
39
- // QueryFn return type inference helper
40
- type InferQueryFnOutput<TQueryFn> = TQueryFn extends (
41
- context: QueryFunctionContext<any>
42
- ) => Promise<Array<infer TItem>>
43
- ? TItem extends object
44
- ? TItem
37
+ // Schema input type inference helper (matches electric.ts pattern)
38
+ type InferSchemaInput<T> = T extends StandardSchemaV1
39
+ ? StandardSchemaV1.InferInput<T> extends object
40
+ ? StandardSchemaV1.InferInput<T>
45
41
  : Record<string, unknown>
46
42
  : Record<string, unknown>
47
43
 
48
- // Type resolution system with priority order (matches electric.ts pattern)
49
- type ResolveType<
50
- TExplicit extends object | unknown = unknown,
51
- TSchema extends StandardSchemaV1 = never,
52
- TQueryFn = unknown,
53
- > = unknown extends TExplicit
54
- ? [TSchema] extends [never]
55
- ? InferQueryFnOutput<TQueryFn>
56
- : InferSchemaOutput<TSchema>
57
- : TExplicit
58
-
59
44
  /**
60
45
  * Configuration options for creating a Query Collection
61
- * @template TExplicit - The explicit type of items stored in the collection (highest priority)
62
- * @template TSchema - The schema type for validation and type inference (second priority)
63
- * @template TQueryFn - The queryFn type for inferring return type (third priority)
46
+ * @template T - The explicit type of items stored in the collection
47
+ * @template TQueryFn - The queryFn type
64
48
  * @template TError - The type of errors that can occur during queries
65
49
  * @template TQueryKey - The type of the query key
50
+ * @template TKey - The type of the item keys
51
+ * @template TSchema - The schema type for validation
66
52
  */
67
53
  export interface QueryCollectionConfig<
68
- TExplicit extends object = object,
69
- TSchema extends StandardSchemaV1 = never,
54
+ T extends object = object,
70
55
  TQueryFn extends (
71
56
  context: QueryFunctionContext<any>
72
57
  ) => Promise<Array<any>> = (
@@ -74,7 +59,9 @@ export interface QueryCollectionConfig<
74
59
  ) => Promise<Array<any>>,
75
60
  TError = unknown,
76
61
  TQueryKey extends QueryKey = QueryKey,
77
- > {
62
+ TKey extends string | number = string | number,
63
+ TSchema extends StandardSchemaV1 = never,
64
+ > extends BaseCollectionConfig<T, TKey, TSchema> {
78
65
  /** The query key used by TanStack Query to identify this query */
79
66
  queryKey: TQueryKey
80
67
  /** Function that fetches data from the server. Must return the complete collection state */
@@ -82,9 +69,7 @@ export interface QueryCollectionConfig<
82
69
  context: QueryFunctionContext<TQueryKey>
83
70
  ) => Promise<Array<any>>
84
71
  ? TQueryFn
85
- : (
86
- context: QueryFunctionContext<TQueryKey>
87
- ) => Promise<Array<ResolveType<TExplicit, TSchema, TQueryFn>>>
72
+ : (context: QueryFunctionContext<TQueryKey>) => Promise<Array<T>>
88
73
 
89
74
  /** The TanStack Query client instance */
90
75
  queryClient: QueryClient
@@ -93,188 +78,34 @@ export interface QueryCollectionConfig<
93
78
  /** Whether the query should automatically run (default: true) */
94
79
  enabled?: boolean
95
80
  refetchInterval?: QueryObserverOptions<
96
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
81
+ Array<T>,
97
82
  TError,
98
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
99
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
83
+ Array<T>,
84
+ Array<T>,
100
85
  TQueryKey
101
86
  >[`refetchInterval`]
102
87
  retry?: QueryObserverOptions<
103
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
88
+ Array<T>,
104
89
  TError,
105
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
106
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
90
+ Array<T>,
91
+ Array<T>,
107
92
  TQueryKey
108
93
  >[`retry`]
109
94
  retryDelay?: QueryObserverOptions<
110
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
95
+ Array<T>,
111
96
  TError,
112
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
113
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
97
+ Array<T>,
98
+ Array<T>,
114
99
  TQueryKey
115
100
  >[`retryDelay`]
116
101
  staleTime?: QueryObserverOptions<
117
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
102
+ Array<T>,
118
103
  TError,
119
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
120
- Array<ResolveType<TExplicit, TSchema, TQueryFn>>,
104
+ Array<T>,
105
+ Array<T>,
121
106
  TQueryKey
122
107
  >[`staleTime`]
123
108
 
124
- // Standard Collection configuration properties
125
- /** Unique identifier for the collection */
126
- id?: string
127
- /** Function to extract the unique key from an item */
128
- getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`getKey`]
129
- /** Schema for validating items */
130
- schema?: TSchema
131
- sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>>[`sync`]
132
- startSync?: CollectionConfig<
133
- ResolveType<TExplicit, TSchema, TQueryFn>
134
- >[`startSync`]
135
-
136
- // Direct persistence handlers
137
- /**
138
- * Optional asynchronous handler function called before an insert operation
139
- * @param params Object containing transaction and collection information
140
- * @returns Promise resolving to void or { refetch?: boolean } to control refetching
141
- * @example
142
- * // Basic query collection insert handler
143
- * onInsert: async ({ transaction }) => {
144
- * const newItem = transaction.mutations[0].modified
145
- * await api.createTodo(newItem)
146
- * // Automatically refetches query after insert
147
- * }
148
- *
149
- * @example
150
- * // Insert handler with refetch control
151
- * onInsert: async ({ transaction }) => {
152
- * const newItem = transaction.mutations[0].modified
153
- * await api.createTodo(newItem)
154
- * return { refetch: false } // Skip automatic refetch
155
- * }
156
- *
157
- * @example
158
- * // Insert handler with multiple items
159
- * onInsert: async ({ transaction }) => {
160
- * const items = transaction.mutations.map(m => m.modified)
161
- * await api.createTodos(items)
162
- * // Will refetch query to get updated data
163
- * }
164
- *
165
- * @example
166
- * // Insert handler with error handling
167
- * onInsert: async ({ transaction }) => {
168
- * try {
169
- * const newItem = transaction.mutations[0].modified
170
- * await api.createTodo(newItem)
171
- * } catch (error) {
172
- * console.error('Insert failed:', error)
173
- * throw error // Transaction will rollback optimistic changes
174
- * }
175
- * }
176
- */
177
- onInsert?: InsertMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>
178
-
179
- /**
180
- * Optional asynchronous handler function called before an update operation
181
- * @param params Object containing transaction and collection information
182
- * @returns Promise resolving to void or { refetch?: boolean } to control refetching
183
- * @example
184
- * // Basic query collection update handler
185
- * onUpdate: async ({ transaction }) => {
186
- * const mutation = transaction.mutations[0]
187
- * await api.updateTodo(mutation.original.id, mutation.changes)
188
- * // Automatically refetches query after update
189
- * }
190
- *
191
- * @example
192
- * // Update handler with multiple items
193
- * onUpdate: async ({ transaction }) => {
194
- * const updates = transaction.mutations.map(m => ({
195
- * id: m.key,
196
- * changes: m.changes
197
- * }))
198
- * await api.updateTodos(updates)
199
- * // Will refetch query to get updated data
200
- * }
201
- *
202
- * @example
203
- * // Update handler with manual refetch
204
- * onUpdate: async ({ transaction, collection }) => {
205
- * const mutation = transaction.mutations[0]
206
- * await api.updateTodo(mutation.original.id, mutation.changes)
207
- *
208
- * // Manually trigger refetch
209
- * await collection.utils.refetch()
210
- *
211
- * return { refetch: false } // Skip automatic refetch
212
- * }
213
- *
214
- * @example
215
- * // Update handler with related collection refetch
216
- * onUpdate: async ({ transaction, collection }) => {
217
- * const mutation = transaction.mutations[0]
218
- * await api.updateTodo(mutation.original.id, mutation.changes)
219
- *
220
- * // Refetch related collections when this item changes
221
- * await Promise.all([
222
- * collection.utils.refetch(), // Refetch this collection
223
- * usersCollection.utils.refetch(), // Refetch users
224
- * tagsCollection.utils.refetch() // Refetch tags
225
- * ])
226
- *
227
- * return { refetch: false } // Skip automatic refetch since we handled it manually
228
- * }
229
- */
230
- onUpdate?: UpdateMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>
231
-
232
- /**
233
- * Optional asynchronous handler function called before a delete operation
234
- * @param params Object containing transaction and collection information
235
- * @returns Promise resolving to void or { refetch?: boolean } to control refetching
236
- * @example
237
- * // Basic query collection delete handler
238
- * onDelete: async ({ transaction }) => {
239
- * const mutation = transaction.mutations[0]
240
- * await api.deleteTodo(mutation.original.id)
241
- * // Automatically refetches query after delete
242
- * }
243
- *
244
- * @example
245
- * // Delete handler with refetch control
246
- * onDelete: async ({ transaction }) => {
247
- * const mutation = transaction.mutations[0]
248
- * await api.deleteTodo(mutation.original.id)
249
- * return { refetch: false } // Skip automatic refetch
250
- * }
251
- *
252
- * @example
253
- * // Delete handler with multiple items
254
- * onDelete: async ({ transaction }) => {
255
- * const keysToDelete = transaction.mutations.map(m => m.key)
256
- * await api.deleteTodos(keysToDelete)
257
- * // Will refetch query to get updated data
258
- * }
259
- *
260
- * @example
261
- * // Delete handler with related collection refetch
262
- * onDelete: async ({ transaction, collection }) => {
263
- * const mutation = transaction.mutations[0]
264
- * await api.deleteTodo(mutation.original.id)
265
- *
266
- * // Refetch related collections when this item is deleted
267
- * await Promise.all([
268
- * collection.utils.refetch(), // Refetch this collection
269
- * usersCollection.utils.refetch(), // Refetch users
270
- * projectsCollection.utils.refetch() // Refetch projects
271
- * ])
272
- *
273
- * return { refetch: false } // Skip automatic refetch since we handled it manually
274
- * }
275
- */
276
- onDelete?: DeleteMutationFn<ResolveType<TExplicit, TSchema, TQueryFn>>
277
-
278
109
  /**
279
110
  * Metadata to pass to the query.
280
111
  * Available in queryFn via context.meta
@@ -301,7 +132,7 @@ export interface QueryCollectionConfig<
301
132
  /**
302
133
  * Type for the refetch utility function
303
134
  */
304
- export type RefetchFn = () => Promise<void>
135
+ export type RefetchFn = (opts?: { throwOnError?: boolean }) => Promise<void>
305
136
 
306
137
  /**
307
138
  * Utility methods available on Query Collections for direct writes and manual operations.
@@ -309,11 +140,13 @@ export type RefetchFn = () => Promise<void>
309
140
  * @template TItem - The type of items stored in the collection
310
141
  * @template TKey - The type of the item keys
311
142
  * @template TInsertInput - The type accepted for insert operations
143
+ * @template TError - The type of errors that can occur during queries
312
144
  */
313
145
  export interface QueryCollectionUtils<
314
146
  TItem extends object = Record<string, unknown>,
315
147
  TKey extends string | number = string | number,
316
148
  TInsertInput extends object = TItem,
149
+ TError = unknown,
317
150
  > extends UtilsRecord {
318
151
  /** Manually trigger a refetch of the query */
319
152
  refetch: RefetchFn
@@ -327,6 +160,21 @@ export interface QueryCollectionUtils<
327
160
  writeUpsert: (data: Partial<TItem> | Array<Partial<TItem>>) => void
328
161
  /** Execute multiple write operations as a single atomic batch to the synced data store */
329
162
  writeBatch: (callback: () => void) => void
163
+ /** Get the last error encountered by the query (if any); reset on success */
164
+ lastError: () => TError | undefined
165
+ /** Check if the collection is in an error state */
166
+ isError: () => boolean
167
+ /**
168
+ * Get the number of consecutive sync failures.
169
+ * Incremented only when query fails completely (not per retry attempt); reset on success.
170
+ */
171
+ errorCount: () => number
172
+ /**
173
+ * Clear the error state and trigger a refetch of the query
174
+ * @returns Promise that resolves when the refetch completes successfully
175
+ * @throws Error if the refetch fails
176
+ */
177
+ clearError: () => Promise<void>
330
178
  }
331
179
 
332
180
  /**
@@ -334,18 +182,13 @@ export interface QueryCollectionUtils<
334
182
  * This integrates TanStack Query with TanStack DB for automatic synchronization.
335
183
  *
336
184
  * Supports automatic type inference following the priority order:
337
- * 1. Explicit type (highest priority)
338
- * 2. Schema inference (second priority)
339
- * 3. QueryFn return type inference (third priority)
340
- * 4. Fallback to Record<string, unknown>
185
+ * 1. Schema inference (highest priority)
186
+ * 2. QueryFn return type inference (second priority)
341
187
  *
342
- * @template TExplicit - The explicit type of items in the collection (highest priority)
343
- * @template TSchema - The schema type for validation and type inference (second priority)
344
- * @template TQueryFn - The queryFn type for inferring return type (third priority)
188
+ * @template T - Type of the schema if a schema is provided otherwise it is the type of the values returned by the queryFn
345
189
  * @template TError - The type of errors that can occur during queries
346
190
  * @template TQueryKey - The type of the query key
347
191
  * @template TKey - The type of the item keys
348
- * @template TInsertInput - The type accepted for insert operations
349
192
  * @param config - Configuration options for the Query collection
350
193
  * @returns Collection options with utilities for direct writes and manual operations
351
194
  *
@@ -364,7 +207,7 @@ export interface QueryCollectionUtils<
364
207
  * )
365
208
  *
366
209
  * @example
367
- * // Explicit type (highest priority)
210
+ * // Explicit type
368
211
  * const todosCollection = createCollection<Todo>(
369
212
  * queryCollectionOptions({
370
213
  * queryKey: ['todos'],
@@ -375,7 +218,7 @@ export interface QueryCollectionUtils<
375
218
  * )
376
219
  *
377
220
  * @example
378
- * // Schema inference (second priority)
221
+ * // Schema inference
379
222
  * const todosCollection = createCollection(
380
223
  * queryCollectionOptions({
381
224
  * queryKey: ['todos'],
@@ -406,29 +249,62 @@ export interface QueryCollectionUtils<
406
249
  * })
407
250
  * )
408
251
  */
252
+
253
+ // Overload for when schema is provided
409
254
  export function queryCollectionOptions<
410
- TExplicit extends object = object,
411
- TSchema extends StandardSchemaV1 = never,
412
- TQueryFn extends (
413
- context: QueryFunctionContext<any>
414
- ) => Promise<Array<any>> = (
415
- context: QueryFunctionContext<any>
416
- ) => Promise<Array<any>>,
255
+ T extends StandardSchemaV1,
417
256
  TError = unknown,
418
257
  TQueryKey extends QueryKey = QueryKey,
419
258
  TKey extends string | number = string | number,
420
- TInsertInput extends object = ResolveType<TExplicit, TSchema, TQueryFn>,
421
259
  >(
422
- config: QueryCollectionConfig<TExplicit, TSchema, TQueryFn, TError, TQueryKey>
423
- ): CollectionConfig<ResolveType<TExplicit, TSchema, TQueryFn>> & {
260
+ config: QueryCollectionConfig<
261
+ InferSchemaOutput<T>,
262
+ (
263
+ context: QueryFunctionContext<any>
264
+ ) => Promise<Array<InferSchemaOutput<T>>>,
265
+ TError,
266
+ TQueryKey,
267
+ TKey,
268
+ T
269
+ > & {
270
+ schema: T
271
+ }
272
+ ): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
273
+ schema: T
424
274
  utils: QueryCollectionUtils<
425
- ResolveType<TExplicit, TSchema, TQueryFn>,
275
+ InferSchemaOutput<T>,
426
276
  TKey,
427
- TInsertInput
277
+ InferSchemaInput<T>,
278
+ TError
428
279
  >
429
- } {
430
- type TItem = ResolveType<TExplicit, TSchema, TQueryFn>
280
+ }
431
281
 
282
+ // Overload for when no schema is provided
283
+ export function queryCollectionOptions<
284
+ T extends object,
285
+ TError = unknown,
286
+ TQueryKey extends QueryKey = QueryKey,
287
+ TKey extends string | number = string | number,
288
+ >(
289
+ config: QueryCollectionConfig<
290
+ T,
291
+ (context: QueryFunctionContext<any>) => Promise<Array<T>>,
292
+ TError,
293
+ TQueryKey,
294
+ TKey
295
+ > & {
296
+ schema?: never // prohibit schema
297
+ }
298
+ ): CollectionConfig<T, TKey> & {
299
+ schema?: never // no schema in the result
300
+ utils: QueryCollectionUtils<T, TKey, T, TError>
301
+ }
302
+
303
+ export function queryCollectionOptions(
304
+ config: QueryCollectionConfig<Record<string, unknown>>
305
+ ): CollectionConfig & {
306
+ utils: QueryCollectionUtils
307
+ } {
432
308
  const {
433
309
  queryKey,
434
310
  queryFn,
@@ -466,15 +342,22 @@ export function queryCollectionOptions<
466
342
  throw new GetKeyRequiredError()
467
343
  }
468
344
 
469
- const internalSync: SyncConfig<TItem>[`sync`] = (params) => {
345
+ /** The last error encountered by the query */
346
+ let lastError: any
347
+ /** The number of consecutive sync failures */
348
+ let errorCount = 0
349
+ /** The timestamp for when the query most recently returned the status as "error" */
350
+ let lastErrorUpdatedAt = 0
351
+
352
+ const internalSync: SyncConfig<any>[`sync`] = (params) => {
470
353
  const { begin, write, commit, markReady, collection } = params
471
354
 
472
355
  const observerOptions: QueryObserverOptions<
473
- Array<TItem>,
474
- TError,
475
- Array<TItem>,
476
- Array<TItem>,
477
- TQueryKey
356
+ Array<any>,
357
+ any,
358
+ Array<any>,
359
+ Array<any>,
360
+ any
478
361
  > = {
479
362
  queryKey: queryKey,
480
363
  queryFn: queryFn,
@@ -489,16 +372,20 @@ export function queryCollectionOptions<
489
372
  }
490
373
 
491
374
  const localObserver = new QueryObserver<
492
- Array<TItem>,
493
- TError,
494
- Array<TItem>,
495
- Array<TItem>,
496
- TQueryKey
375
+ Array<any>,
376
+ any,
377
+ Array<any>,
378
+ Array<any>,
379
+ any
497
380
  >(queryClient, observerOptions)
498
381
 
499
382
  type UpdateHandler = Parameters<typeof localObserver.subscribe>[0]
500
383
  const handleUpdate: UpdateHandler = (result) => {
501
384
  if (result.isSuccess) {
385
+ // Clear error state
386
+ lastError = undefined
387
+ errorCount = 0
388
+
502
389
  const newItemsArray = result.data
503
390
 
504
391
  if (
@@ -513,7 +400,7 @@ export function queryCollectionOptions<
513
400
  }
514
401
 
515
402
  const currentSyncedItems = new Map(collection.syncedData)
516
- const newItemsMap = new Map<string | number, TItem>()
403
+ const newItemsMap = new Map<string | number, any>()
517
404
  newItemsArray.forEach((item) => {
518
405
  const key = getKey(item)
519
406
  newItemsMap.set(key, item)
@@ -567,6 +454,12 @@ export function queryCollectionOptions<
567
454
  // Mark collection as ready after first successful query result
568
455
  markReady()
569
456
  } else if (result.isError) {
457
+ if (result.errorUpdatedAt !== lastErrorUpdatedAt) {
458
+ lastError = result.error
459
+ errorCount++
460
+ lastErrorUpdatedAt = result.errorUpdatedAt
461
+ }
462
+
570
463
  console.error(
571
464
  `[QueryCollection] Error observing query ${String(queryKey)}:`,
572
465
  result.error
@@ -594,10 +487,15 @@ export function queryCollectionOptions<
594
487
  * Refetch the query data
595
488
  * @returns Promise that resolves when the refetch is complete
596
489
  */
597
- const refetch: RefetchFn = async (): Promise<void> => {
598
- return queryClient.refetchQueries({
599
- queryKey: queryKey,
600
- })
490
+ const refetch: RefetchFn = (opts) => {
491
+ return queryClient.refetchQueries(
492
+ {
493
+ queryKey: queryKey,
494
+ },
495
+ {
496
+ throwOnError: opts?.throwOnError,
497
+ }
498
+ )
601
499
  }
602
500
 
603
501
  // Create write context for manual write operations
@@ -605,14 +503,14 @@ export function queryCollectionOptions<
605
503
  collection: any
606
504
  queryClient: QueryClient
607
505
  queryKey: Array<unknown>
608
- getKey: (item: TItem) => TKey
506
+ getKey: (item: any) => string | number
609
507
  begin: () => void
610
- write: (message: Omit<ChangeMessage<TItem>, `key`>) => void
508
+ write: (message: Omit<ChangeMessage<any>, `key`>) => void
611
509
  commit: () => void
612
510
  } | null = null
613
511
 
614
512
  // Enhanced internalSync that captures write functions for manual use
615
- const enhancedInternalSync: SyncConfig<TItem>[`sync`] = (params) => {
513
+ const enhancedInternalSync: SyncConfig<any>[`sync`] = (params) => {
616
514
  const { begin, write, commit, collection } = params
617
515
 
618
516
  // Store references for manual write operations
@@ -620,7 +518,7 @@ export function queryCollectionOptions<
620
518
  collection,
621
519
  queryClient,
622
520
  queryKey: queryKey as unknown as Array<unknown>,
623
- getKey: getKey as (item: TItem) => TKey,
521
+ getKey: getKey as (item: any) => string | number,
624
522
  begin,
625
523
  write,
626
524
  commit,
@@ -631,13 +529,13 @@ export function queryCollectionOptions<
631
529
  }
632
530
 
633
531
  // Create write utils using the manual-sync module
634
- const writeUtils = createWriteUtils<TItem, TKey, TInsertInput>(
532
+ const writeUtils = createWriteUtils<any, string | number, any>(
635
533
  () => writeContext
636
534
  )
637
535
 
638
536
  // Create wrapper handlers for direct persistence operations that handle refetching
639
537
  const wrappedOnInsert = onInsert
640
- ? async (params: InsertMutationFnParams<TItem>) => {
538
+ ? async (params: InsertMutationFnParams<any>) => {
641
539
  const handlerResult = (await onInsert(params)) ?? {}
642
540
  const shouldRefetch =
643
541
  (handlerResult as { refetch?: boolean }).refetch !== false
@@ -651,7 +549,7 @@ export function queryCollectionOptions<
651
549
  : undefined
652
550
 
653
551
  const wrappedOnUpdate = onUpdate
654
- ? async (params: UpdateMutationFnParams<TItem>) => {
552
+ ? async (params: UpdateMutationFnParams<any>) => {
655
553
  const handlerResult = (await onUpdate(params)) ?? {}
656
554
  const shouldRefetch =
657
555
  (handlerResult as { refetch?: boolean }).refetch !== false
@@ -665,7 +563,7 @@ export function queryCollectionOptions<
665
563
  : undefined
666
564
 
667
565
  const wrappedOnDelete = onDelete
668
- ? async (params: DeleteMutationFnParams<TItem>) => {
566
+ ? async (params: DeleteMutationFnParams<any>) => {
669
567
  const handlerResult = (await onDelete(params)) ?? {}
670
568
  const shouldRefetch =
671
569
  (handlerResult as { refetch?: boolean }).refetch !== false
@@ -688,6 +586,15 @@ export function queryCollectionOptions<
688
586
  utils: {
689
587
  refetch,
690
588
  ...writeUtils,
589
+ lastError: () => lastError,
590
+ isError: () => !!lastError,
591
+ errorCount: () => errorCount,
592
+ clearError: () => {
593
+ lastError = undefined
594
+ errorCount = 0
595
+ lastErrorUpdatedAt = 0
596
+ return refetch({ throwOnError: true })
597
+ },
691
598
  },
692
599
  }
693
600
  }