diff --git a/.changeset/query-observer-state-utils.md b/.changeset/query-observer-state-utils.md new file mode 100644 index 000000000..1d1d3996d --- /dev/null +++ b/.changeset/query-observer-state-utils.md @@ -0,0 +1,55 @@ +--- +"@tanstack/query-db-collection": minor +"@tanstack/db": patch +--- + +Add QueryObserver state utilities and convert error utils to getters + +Exposes TanStack Query's QueryObserver state through QueryCollectionUtils, providing visibility into sync status beyond just error states. Also converts existing error state utilities from methods to getters for consistency with TanStack DB/Query patterns. + +**Breaking Changes:** + +- `lastError()`, `isError()`, and `errorCount()` are now getters instead of methods + - Before: `collection.utils.lastError()` + - After: `collection.utils.lastError` + +**New Utilities:** + +- `isFetching` - Check if query is currently fetching (initial or background) +- `isRefetching` - Check if query is refetching in background +- `isLoading` - Check if query is loading for first time +- `dataUpdatedAt` - Get timestamp of last successful data update +- `fetchStatus` - Get current fetch status ('fetching' | 'paused' | 'idle') + +**Use Cases:** + +- Show loading indicators during background refetches +- Implement "Last updated X minutes ago" UI patterns +- Better understanding of query sync behavior + +**Example Usage:** + +```ts +const collection = queryCollectionOptions({ + // ... config +}) + +// Check sync status +if (collection.utils.isFetching) { + console.log("Syncing with server...") +} + +if (collection.utils.isRefetching) { + console.log("Background refresh in progress") +} + +// Show last update time +const lastUpdate = new Date(collection.utils.dataUpdatedAt) +console.log(`Last synced: ${lastUpdate.toLocaleTimeString()}`) + +// Check error state (now using getters) +if (collection.utils.isError) { + console.error("Sync failed:", collection.utils.lastError) + console.log(`Failed ${collection.utils.errorCount} times`) +} +``` diff --git a/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md b/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md index 1bfe6b416..ed955d29a 100644 --- a/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md +++ b/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md @@ -22,7 +22,7 @@ Electric collection utilities type ## Indexable ```ts -[key: string]: Fn +[key: string]: any ``` ## Properties diff --git a/docs/reference/interfaces/LocalOnlyCollectionUtils.md b/docs/reference/interfaces/LocalOnlyCollectionUtils.md index 75d0a06ff..75d3e41ce 100644 --- a/docs/reference/interfaces/LocalOnlyCollectionUtils.md +++ b/docs/reference/interfaces/LocalOnlyCollectionUtils.md @@ -16,7 +16,7 @@ Local-only collection utilities type ## Indexable ```ts -[key: string]: Fn +[key: string]: any ``` ## Properties diff --git a/docs/reference/interfaces/LocalStorageCollectionUtils.md b/docs/reference/interfaces/LocalStorageCollectionUtils.md index 57671cfbb..9b35e540d 100644 --- a/docs/reference/interfaces/LocalStorageCollectionUtils.md +++ b/docs/reference/interfaces/LocalStorageCollectionUtils.md @@ -16,7 +16,7 @@ LocalStorage collection utilities type ## Indexable ```ts -[key: string]: Fn +[key: string]: any ``` ## Properties diff --git a/docs/reference/query-db-collection/functions/queryCollectionOptions.md b/docs/reference/query-db-collection/functions/queryCollectionOptions.md index 7da3182e8..9e5c026c2 100644 --- a/docs/reference/query-db-collection/functions/queryCollectionOptions.md +++ b/docs/reference/query-db-collection/functions/queryCollectionOptions.md @@ -8,10 +8,10 @@ title: queryCollectionOptions ## Call Signature ```ts -function queryCollectionOptions(config): CollectionConfig, TKey, T, UtilsRecord> & object; +function queryCollectionOptions(config): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:270](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L270) +Defined in: [packages/query-db-collection/src/query.ts:370](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L370) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -64,7 +64,7 @@ Configuration options for the Query collection ### Returns -`CollectionConfig`\<`InferSchemaOutput`\<`T`\>, `TKey`, `T`, `UtilsRecord`\> & `object` +`CollectionConfig`\<`InferSchemaOutput`\<`T`\>, `TKey`, `T`, [`QueryCollectionUtils`](../../interfaces/QueryCollectionUtils.md)\<`InferSchemaOutput`\<`T`\>, `TKey`, `InferSchemaInput`\<`T`\>, `TError`\>\> & `object` Collection options with utilities for direct writes and manual operations @@ -148,10 +148,10 @@ const todosCollection = createCollection( ## Call Signature ```ts -function queryCollectionOptions(config): CollectionConfig & object; +function queryCollectionOptions(config): CollectionConfig> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:300](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L300) +Defined in: [packages/query-db-collection/src/query.ts:405](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L405) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -204,7 +204,7 @@ Configuration options for the Query collection ### Returns -`CollectionConfig`\<`T`, `TKey`, `never`, `UtilsRecord`\> & `object` +`CollectionConfig`\<`T`, `TKey`, `never`, [`QueryCollectionUtils`](../../interfaces/QueryCollectionUtils.md)\<`T`, `TKey`, `T`, `TError`\>\> & `object` Collection options with utilities for direct writes and manual operations @@ -288,10 +288,10 @@ const todosCollection = createCollection( ## Call Signature ```ts -function queryCollectionOptions(config): CollectionConfig, TKey, T, UtilsRecord> & object; +function queryCollectionOptions(config): CollectionConfig, TKey, T, QueryCollectionUtils, TKey, InferSchemaInput, TError>> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:328](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L328) +Defined in: [packages/query-db-collection/src/query.ts:438](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L438) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -336,7 +336,7 @@ Configuration options for the Query collection ### Returns -`CollectionConfig`\<`InferSchemaOutput`\<`T`\>, `TKey`, `T`, `UtilsRecord`\> & `object` +`CollectionConfig`\<`InferSchemaOutput`\<`T`\>, `TKey`, `T`, [`QueryCollectionUtils`](../../interfaces/QueryCollectionUtils.md)\<`InferSchemaOutput`\<`T`\>, `TKey`, `InferSchemaInput`\<`T`\>, `TError`\>\> & `object` Collection options with utilities for direct writes and manual operations @@ -420,10 +420,10 @@ const todosCollection = createCollection( ## Call Signature ```ts -function queryCollectionOptions(config): CollectionConfig & object; +function queryCollectionOptions(config): CollectionConfig> & object; ``` -Defined in: [packages/query-db-collection/src/query.ts:357](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L357) +Defined in: [packages/query-db-collection/src/query.ts:472](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L472) Creates query collection options for use with a standard Collection. This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -468,7 +468,7 @@ Configuration options for the Query collection ### Returns -`CollectionConfig`\<`T`, `TKey`, `never`, `UtilsRecord`\> & `object` +`CollectionConfig`\<`T`, `TKey`, `never`, [`QueryCollectionUtils`](../../interfaces/QueryCollectionUtils.md)\<`T`, `TKey`, `T`, `TError`\>\> & `object` Collection options with utilities for direct writes and manual operations diff --git a/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md b/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md index a65cd4221..5f599e3de 100644 --- a/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md +++ b/docs/reference/query-db-collection/interfaces/QueryCollectionUtils.md @@ -43,7 +43,7 @@ The type of errors that can occur during queries ## Indexable ```ts -[key: string]: Fn +[key: string]: any ``` ## Properties @@ -54,7 +54,7 @@ The type of errors that can occur during queries clearError: () => Promise; ``` -Defined in: [packages/query-db-collection/src/query.ts:181](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L181) +Defined in: [packages/query-db-collection/src/query.ts:194](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L194) Clear the error state and trigger a refetch of the query @@ -70,52 +70,100 @@ Error if the refetch fails *** -### errorCount() +### dataUpdatedAt ```ts -errorCount: () => number; +dataUpdatedAt: number; ``` -Defined in: [packages/query-db-collection/src/query.ts:175](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L175) +Defined in: [packages/query-db-collection/src/query.ts:185](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L185) + +Get timestamp of last successful data update (in milliseconds) + +*** + +### errorCount + +```ts +errorCount: number; +``` + +Defined in: [packages/query-db-collection/src/query.ts:177](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L177) Get the number of consecutive sync failures. Incremented only when query fails completely (not per retry attempt); reset on success. -#### Returns +*** -`number` +### fetchStatus + +```ts +fetchStatus: "idle" | "fetching" | "paused"; +``` + +Defined in: [packages/query-db-collection/src/query.ts:187](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L187) + +Get current fetch status *** -### isError() +### isError ```ts -isError: () => boolean; +isError: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:170](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L170) +Defined in: [packages/query-db-collection/src/query.ts:172](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L172) Check if the collection is in an error state -#### Returns +*** + +### isFetching -`boolean` +```ts +isFetching: boolean; +``` + +Defined in: [packages/query-db-collection/src/query.ts:179](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L179) + +Check if query is currently fetching (initial or background) *** -### lastError() +### isLoading ```ts -lastError: () => TError | undefined; +isLoading: boolean; ``` -Defined in: [packages/query-db-collection/src/query.ts:168](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L168) +Defined in: [packages/query-db-collection/src/query.ts:183](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L183) -Get the last error encountered by the query (if any); reset on success +Check if query is loading for the first time (no data yet) -#### Returns +*** + +### isRefetching + +```ts +isRefetching: boolean; +``` + +Defined in: [packages/query-db-collection/src/query.ts:181](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L181) -`TError` \| `undefined` +Check if query is refetching in background (not initial fetch) + +*** + +### lastError + +```ts +lastError: TError | undefined; +``` + +Defined in: [packages/query-db-collection/src/query.ts:170](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L170) + +Get the last error encountered by the query (if any); reset on success *** diff --git a/docs/reference/type-aliases/UtilsRecord.md b/docs/reference/type-aliases/UtilsRecord.md index 9b694c349..309a1cfd9 100644 --- a/docs/reference/type-aliases/UtilsRecord.md +++ b/docs/reference/type-aliases/UtilsRecord.md @@ -6,9 +6,9 @@ title: UtilsRecord # Type Alias: UtilsRecord ```ts -type UtilsRecord = Record; +type UtilsRecord = Record; ``` Defined in: [packages/db/src/types.ts:40](https://github.com/TanStack/db/blob/main/packages/db/src/types.ts#L40) -A record of utility functions that can be attached to a collection +A record of utilities (functions or getters) that can be attached to a collection diff --git a/packages/db/src/collection/index.ts b/packages/db/src/collection/index.ts index 5a4ab3c07..638c0e451 100644 --- a/packages/db/src/collection/index.ts +++ b/packages/db/src/collection/index.ts @@ -189,9 +189,9 @@ export function createCollection( options ) - // Copy utils to both top level and .utils namespace + // Attach utils to collection if (options.utils) { - collection.utils = { ...options.utils } + collection.utils = options.utils } else { collection.utils = {} } diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts index 73c1fc4fe..04fadef51 100644 --- a/packages/db/src/types.ts +++ b/packages/db/src/types.ts @@ -35,9 +35,9 @@ export type TransactionState = `pending` | `persisting` | `completed` | `failed` export type Fn = (...args: Array) => any /** - * A record of utility functions that can be attached to a collection + * A record of utilities (functions or getters) that can be attached to a collection */ -export type UtilsRecord = Record +export type UtilsRecord = Record /** * diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index bbfd6db56..73bcdf43b 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -164,15 +164,28 @@ export interface QueryCollectionUtils< writeUpsert: (data: Partial | Array>) => void /** Execute multiple write operations as a single atomic batch to the synced data store */ writeBatch: (callback: () => void) => void + + // Query Observer State (getters) /** Get the last error encountered by the query (if any); reset on success */ - lastError: () => TError | undefined + lastError: TError | undefined /** Check if the collection is in an error state */ - isError: () => boolean + isError: boolean /** * Get the number of consecutive sync failures. * Incremented only when query fails completely (not per retry attempt); reset on success. */ - errorCount: () => number + errorCount: number + /** Check if query is currently fetching (initial or background) */ + isFetching: boolean + /** Check if query is refetching in background (not initial fetch) */ + isRefetching: boolean + /** Check if query is loading for the first time (no data yet) */ + isLoading: boolean + /** Get timestamp of last successful data update (in milliseconds) */ + dataUpdatedAt: number + /** Get current fetch status */ + fetchStatus: `fetching` | `paused` | `idle` + /** * Clear the error state and trigger a refetch of the query * @returns Promise that resolves when the refetch completes successfully @@ -181,6 +194,93 @@ export interface QueryCollectionUtils< clearError: () => Promise } +/** + * Internal state object for tracking query observer and errors + */ +interface QueryCollectionState { + lastError: any + errorCount: number + lastErrorUpdatedAt: number + queryObserver: + | QueryObserver, any, Array, Array, any> + | undefined +} + +/** + * Implementation class for QueryCollectionUtils with explicit dependency injection + * for better testability and architectural clarity + */ +class QueryCollectionUtilsImpl { + private state: QueryCollectionState + private refetchFn: RefetchFn + + // Write methods + public refetch: RefetchFn + public writeInsert: any + public writeUpdate: any + public writeDelete: any + public writeUpsert: any + public writeBatch: any + + constructor( + state: QueryCollectionState, + refetch: RefetchFn, + writeUtils: ReturnType + ) { + this.state = state + this.refetchFn = refetch + + // Initialize methods to use passed dependencies + this.refetch = refetch + this.writeInsert = writeUtils.writeInsert + this.writeUpdate = writeUtils.writeUpdate + this.writeDelete = writeUtils.writeDelete + this.writeUpsert = writeUtils.writeUpsert + this.writeBatch = writeUtils.writeBatch + } + + public async clearError() { + this.state.lastError = undefined + this.state.errorCount = 0 + this.state.lastErrorUpdatedAt = 0 + await this.refetchFn({ throwOnError: true }) + } + + // Getters for error state + public get lastError() { + return this.state.lastError + } + + public get isError() { + return !!this.state.lastError + } + + public get errorCount() { + return this.state.errorCount + } + + // Getters for QueryObserver state + public get isFetching() { + return this.state.queryObserver?.getCurrentResult().isFetching ?? false + } + + public get isRefetching() { + return this.state.queryObserver?.getCurrentResult().isRefetching ?? false + } + + public get isLoading() { + return this.state.queryObserver?.getCurrentResult().isLoading ?? false + } + + public get dataUpdatedAt() { + return this.state.queryObserver?.getCurrentResult().dataUpdatedAt ?? 0 + } + + public get fetchStatus() { + return this.state.queryObserver?.getCurrentResult().fetchStatus ?? `idle` + } +} + /** * Creates query collection options for use with a standard Collection. * This integrates TanStack Query with TanStack DB for automatic synchronization. @@ -286,7 +386,12 @@ export function queryCollectionOptions< schema: T select: (data: TQueryData) => Array> } -): CollectionConfig, TKey, T> & { +): CollectionConfig< + InferSchemaOutput, + TKey, + T, + QueryCollectionUtils, TKey, InferSchemaInput, TError> +> & { schema: T utils: QueryCollectionUtils< InferSchemaOutput, @@ -319,7 +424,12 @@ export function queryCollectionOptions< schema?: never // prohibit schema select: (data: TQueryData) => Array } -): CollectionConfig & { +): CollectionConfig< + T, + TKey, + never, + QueryCollectionUtils +> & { schema?: never // no schema in the result utils: QueryCollectionUtils } @@ -343,7 +453,12 @@ export function queryCollectionOptions< > & { schema: T } -): CollectionConfig, TKey, T> & { +): CollectionConfig< + InferSchemaOutput, + TKey, + T, + QueryCollectionUtils, TKey, InferSchemaInput, TError> +> & { schema: T utils: QueryCollectionUtils< InferSchemaOutput, @@ -369,14 +484,24 @@ export function queryCollectionOptions< > & { schema?: never // prohibit schema } -): CollectionConfig & { +): CollectionConfig< + T, + TKey, + never, + QueryCollectionUtils +> & { schema?: never // no schema in the result utils: QueryCollectionUtils } export function queryCollectionOptions( config: QueryCollectionConfig> -): CollectionConfig & { +): CollectionConfig< + Record, + string | number, + never, + QueryCollectionUtils +> & { utils: QueryCollectionUtils } { const { @@ -418,14 +543,13 @@ export function queryCollectionOptions( throw new GetKeyRequiredError() } - /** The last error encountered by the query */ - let lastError: any - /** The number of consecutive sync failures */ - let errorCount = 0 - /** The timestamp for when the query most recently returned the status as "error" */ - let lastErrorUpdatedAt = 0 - /** Reference to the QueryObserver for imperative refetch */ - let queryObserver: QueryObserver, any, Array, Array, any> + /** State object to hold error tracking and observer reference */ + const state: QueryCollectionState = { + lastError: undefined as any, + errorCount: 0, + lastErrorUpdatedAt: 0, + queryObserver: undefined, + } const internalSync: SyncConfig[`sync`] = (params) => { const { begin, write, commit, markReady, collection } = params @@ -459,7 +583,7 @@ export function queryCollectionOptions( >(queryClient, observerOptions) // Store reference for imperative refetch - queryObserver = localObserver + state.queryObserver = localObserver let isSubscribed = false let actualUnsubscribeFn: (() => void) | null = null @@ -468,8 +592,8 @@ export function queryCollectionOptions( const handleQueryResult: UpdateHandler = (result) => { if (result.isSuccess) { // Clear error state - lastError = undefined - errorCount = 0 + state.lastError = undefined + state.errorCount = 0 const rawData = result.data const newItemsArray = select ? select(rawData) : rawData @@ -543,10 +667,10 @@ export function queryCollectionOptions( // Mark collection as ready after first successful query result markReady() } else if (result.isError) { - if (result.errorUpdatedAt !== lastErrorUpdatedAt) { - lastError = result.error - errorCount++ - lastErrorUpdatedAt = result.errorUpdatedAt + if (result.errorUpdatedAt !== state.lastErrorUpdatedAt) { + state.lastError = result.error + state.errorCount++ + state.lastErrorUpdatedAt = result.errorUpdatedAt } console.error( @@ -622,12 +746,12 @@ export function queryCollectionOptions( */ const refetch: RefetchFn = async (opts) => { // Observer is created when sync starts. If never synced, nothing to refetch. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!queryObserver) { + + if (!state.queryObserver) { return } // Return the QueryObserverResult for users to inspect - return queryObserver.refetch({ + return state.queryObserver.refetch({ throwOnError: opts?.throwOnError, }) } @@ -710,6 +834,9 @@ export function queryCollectionOptions( } : undefined + // Create utils instance with state and dependencies passed explicitly + const utils: any = new QueryCollectionUtilsImpl(state, refetch, writeUtils) + return { ...baseCollectionConfig, getKey, @@ -717,18 +844,6 @@ export function queryCollectionOptions( onInsert: wrappedOnInsert, onUpdate: wrappedOnUpdate, onDelete: wrappedOnDelete, - utils: { - refetch, - ...writeUtils, - lastError: () => lastError, - isError: () => !!lastError, - errorCount: () => errorCount, - clearError: async () => { - lastError = undefined - errorCount = 0 - lastErrorUpdatedAt = 0 - await refetch({ throwOnError: true }) - }, - }, + utils, } } diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index b3a6dd712..7d2547270 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -2097,7 +2097,7 @@ describe(`QueryCollection`, () => { const collection2 = createCollection(options2) await vi.waitFor(() => { - expect(collection1.utils.isError()).toBe(true) + expect(collection1.utils.isError).toBe(true) expect(collection2.status).toBe(`ready`) }) @@ -2134,7 +2134,7 @@ describe(`QueryCollection`, () => { ) await vi.waitFor(() => { - expect(collection.utils.isError()).toBe(true) + expect(collection.utils.isError).toBe(true) }) await expect( @@ -2264,33 +2264,33 @@ describe(`QueryCollection`, () => { // Wait for initial success - no errors await vi.waitFor(() => { expect(collection.status).toBe(`ready`) - expect(collection.utils.lastError()).toBeUndefined() - expect(collection.utils.isError()).toBe(false) - expect(collection.utils.errorCount()).toBe(0) + expect(collection.utils.lastError).toBeUndefined() + expect(collection.utils.isError).toBe(false) + expect(collection.utils.errorCount).toBe(0) }) // First error - count increments await collection.utils.refetch() await vi.waitFor(() => { - expect(collection.utils.lastError()).toBe(errors[0]) - expect(collection.utils.errorCount()).toBe(1) - expect(collection.utils.isError()).toBe(true) + expect(collection.utils.lastError).toBe(errors[0]) + expect(collection.utils.errorCount).toBe(1) + expect(collection.utils.isError).toBe(true) }) // Second error - count increments again await collection.utils.refetch() await vi.waitFor(() => { - expect(collection.utils.lastError()).toBe(errors[1]) - expect(collection.utils.errorCount()).toBe(2) - expect(collection.utils.isError()).toBe(true) + expect(collection.utils.lastError).toBe(errors[1]) + expect(collection.utils.errorCount).toBe(2) + expect(collection.utils.isError).toBe(true) }) // Successful refetch resets error state await collection.utils.refetch() await vi.waitFor(() => { - expect(collection.utils.lastError()).toBeUndefined() - expect(collection.utils.isError()).toBe(false) - expect(collection.utils.errorCount()).toBe(0) + expect(collection.utils.lastError).toBeUndefined() + expect(collection.utils.isError).toBe(false) + expect(collection.utils.errorCount).toBe(0) expect(collection.get(`1`)).toEqual(updatedData[0]) }) }) @@ -2312,16 +2312,16 @@ describe(`QueryCollection`, () => { // Wait for initial error await vi.waitFor(() => { - expect(collection.utils.isError()).toBe(true) - expect(collection.utils.errorCount()).toBe(1) + expect(collection.utils.isError).toBe(true) + expect(collection.utils.errorCount).toBe(1) }) // Manual error clearing triggers refetch await collection.utils.clearError() - expect(collection.utils.lastError()).toBeUndefined() - expect(collection.utils.isError()).toBe(false) - expect(collection.utils.errorCount()).toBe(0) + expect(collection.utils.lastError).toBeUndefined() + expect(collection.utils.isError).toBe(false) + expect(collection.utils.errorCount).toBe(0) await vi.waitFor(() => { expect(collection.get(`1`)).toEqual(recoveryData[0]) @@ -2329,9 +2329,9 @@ describe(`QueryCollection`, () => { // Refetch on rejection should throw an error await expect(collection.utils.clearError()).rejects.toThrow(testError) - expect(collection.utils.lastError()).toBe(testError) - expect(collection.utils.isError()).toBe(true) - expect(collection.utils.errorCount()).toBe(1) + expect(collection.utils.lastError).toBe(testError) + expect(collection.utils.isError).toBe(true) + expect(collection.utils.errorCount).toBe(1) }) it(`should maintain collection functionality despite errors and persist error state`, async () => { @@ -2359,8 +2359,8 @@ describe(`QueryCollection`, () => { // Cause error await collection.utils.refetch() await vi.waitFor(() => { - expect(collection.utils.errorCount()).toBe(1) - expect(collection.utils.isError()).toBe(true) + expect(collection.utils.errorCount).toBe(1) + expect(collection.utils.isError).toBe(true) }) // Collection operations still work with cached data @@ -2377,25 +2377,25 @@ describe(`QueryCollection`, () => { await flushPromises() // Manual writes clear error state - expect(collection.utils.lastError()).toBeUndefined() - expect(collection.utils.isError()).toBe(false) - expect(collection.utils.errorCount()).toBe(0) + expect(collection.utils.lastError).toBeUndefined() + expect(collection.utils.isError).toBe(false) + expect(collection.utils.errorCount).toBe(0) // Create error state again for persistence test await collection.utils.refetch() - await vi.waitFor(() => expect(collection.utils.isError()).toBe(true)) + await vi.waitFor(() => expect(collection.utils.isError).toBe(true)) - const originalError = collection.utils.lastError() - const originalErrorCount = collection.utils.errorCount() + const originalError = collection.utils.lastError + const originalErrorCount = collection.utils.errorCount // Read-only operations don't affect error state expect(collection.has(`1`)).toBe(true) const changeHandler = vi.fn() const subscription = collection.subscribeChanges(changeHandler) - expect(collection.utils.lastError()).toBe(originalError) - expect(collection.utils.isError()).toBe(true) - expect(collection.utils.errorCount()).toBe(originalErrorCount) + expect(collection.utils.lastError).toBe(originalError) + expect(collection.utils.isError).toBe(true) + expect(collection.utils.errorCount).toBe(originalErrorCount) subscription.unsubscribe() }) @@ -2435,16 +2435,16 @@ describe(`QueryCollection`, () => { // Wait for collection to be ready (even with error) await vi.waitFor(() => { expect(collection.status).toBe(`ready`) - expect(collection.utils.isError()).toBe(true) + expect(collection.utils.isError).toBe(true) }) // Verify custom error is accessible with all its properties - const lastError = collection.utils.lastError() + const lastError = collection.utils.lastError expect(lastError).toBe(customError) expect(lastError?.code).toBe(`NETWORK_ERROR`) expect(lastError?.message).toBe(`Failed to fetch data`) expect(lastError?.details?.retryAfter).toBe(5000) - expect(collection.utils.errorCount()).toBe(1) + expect(collection.utils.errorCount).toBe(1) }) it(`should persist error state after collection cleanup`, async () => { @@ -2461,21 +2461,21 @@ describe(`QueryCollection`, () => { // Wait for collection to be ready (even with error) await vi.waitFor(() => { expect(collection.status).toBe(`ready`) - expect(collection.utils.isError()).toBe(true) + expect(collection.utils.isError).toBe(true) }) // Verify error state before cleanup - expect(collection.utils.lastError()).toBe(testError) - expect(collection.utils.errorCount()).toBe(1) + expect(collection.utils.lastError).toBe(testError) + expect(collection.utils.errorCount).toBe(1) // Cleanup collection await collection.cleanup() expect(collection.status).toBe(`cleaned-up`) // Error state should persist after cleanup - expect(collection.utils.isError()).toBe(true) - expect(collection.utils.lastError()).toBe(testError) - expect(collection.utils.errorCount()).toBe(1) + expect(collection.utils.isError).toBe(true) + expect(collection.utils.lastError).toBe(testError) + expect(collection.utils.errorCount).toBe(1) }) it(`should increment errorCount only after final failure when using Query retries`, async () => { @@ -2506,16 +2506,16 @@ describe(`QueryCollection`, () => { () => { expect(collection.status).toBe(`ready`) // Should be ready even with error expect(queryFn).toHaveBeenCalledTimes(totalAttempts) - expect(collection.utils.isError()).toBe(true) + expect(collection.utils.isError).toBe(true) }, { timeout: 2000 } ) // Error count should only increment once after all retries are exhausted // This ensures we track "consecutive post-retry failures," not per-attempt failures - expect(collection.utils.errorCount()).toBe(1) - expect(collection.utils.lastError()).toBe(testError) - expect(collection.utils.isError()).toBe(true) + expect(collection.utils.errorCount).toBe(1) + expect(collection.utils.lastError).toBe(testError) + expect(collection.utils.isError).toBe(true) // Reset attempt counter for second test queryFn.mockClear() @@ -2532,9 +2532,9 @@ describe(`QueryCollection`, () => { ) // Error count should now be 2 (two post-retry failures) - expect(collection.utils.errorCount()).toBe(2) - expect(collection.utils.lastError()).toBe(testError) - expect(collection.utils.isError()).toBe(true) + expect(collection.utils.errorCount).toBe(2) + expect(collection.utils.lastError).toBe(testError) + expect(collection.utils.isError).toBe(true) }) })