@tanstack/db
Version:
A reactive client store for building super fast apps on sync
102 lines (94 loc) • 3.4 kB
text/typescript
import type { Collection } from './collection/index.js'
import type { CollectionStatus, HealthNode } from './types.js'
// Collection guards and status flags. Core modules import them from here, and
// the adapter layer (`live-query-adapter.ts`) re-exports them.
/**
* Structural check for a live-query/`Collection` instance.
*
* Uses duck typing rather than `instanceof CollectionImpl` on purpose: adapters
* and core can resolve to different copies of `@tanstack/db` (dual-package /
* multi-realm), where `instanceof` gives false negatives. The three methods
* below uniquely identify a Collection.
*/
export function isCollection(
value: unknown,
): value is Collection<any, any, any> {
return (
typeof value === `object` &&
value !== null &&
typeof (value as any).subscribeChanges === `function` &&
typeof (value as any).startSyncImmediate === `function` &&
typeof (value as any).id === `string`
)
}
/**
* The Collection an adapter hands users as `collection`. A pooled live query
* stands in for its live-query Collection and builds it only when touched.
*/
export function getPublicCollection<
T extends Collection<any, any, any> | null | undefined,
>(collection: T): T {
return (
(collection as { publicCollection?: T } | null | undefined)
?.publicCollection ?? collection
)
}
/** Whether a collection yields a single result (`findOne`) rather than an array. */
export function isSingleResultCollection(
collection: Collection<any, any, any>,
): boolean {
return (
(collection.config as { singleResult?: boolean } | undefined)
?.singleResult === true
)
}
/** `error` and `crashed` end a sync run before it can serve its result. */
export function isFailedStatus(status: CollectionStatus): boolean {
return status === `error` || status === `crashed`
}
/**
* The derived flags and health every adapter exposes for a query, besides
* `status`, which each adapter reports as its own field.
*/
export interface LiveQueryStatusFlags {
isLoading: boolean
isReady: boolean
isIdle: boolean
/** The first load failed: there is nothing to show. `status` is `error`. */
isLoadingError: boolean
/** Local storage or an invariant broke. `status` is `crashed`. */
isCrashed: boolean
isCleanedUp: boolean
/**
* The data is shown, and something keeps it from staying current: a sync
* problem or failed request somewhere in `health`. Set only while `status` is
* `ready`; a disabled query is never in a sync error.
*/
isSyncError: boolean
/**
* The health tree for the query and every Collection it depends on, or
* `undefined` for a disabled query.
*/
health: HealthNode | undefined
}
/**
* Derive the status flags from a collection status and its health tree. This is
* the one place the `isSyncError` rule is written. Adapters represent a disabled
* query separately (with `isReady: true`); this covers the real
* `CollectionStatus` values.
*/
export function getLiveQueryStatusFlags(
status: CollectionStatus,
health?: HealthNode,
): LiveQueryStatusFlags {
return {
isLoading: status === `loading`,
isReady: status === `ready`,
isIdle: status === `idle`,
isLoadingError: status === `error`,
isCrashed: status === `crashed`,
isCleanedUp: status === `cleaned-up`,
isSyncError: status === `ready` && health?.hasErrors === true,
health,
}
}