@tanstack/vue-db 0.2.1 → 0.3.1

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.
@@ -104,6 +104,9 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps = []) {
104
104
  status: vue.computed(() => snapshot.value.status),
105
105
  isLoading: vue.computed(() => snapshot.value.isLoading),
106
106
  isReady: vue.computed(() => snapshot.value.isReady),
107
+ persistedStatus: vue.computed(() => snapshot.value.persistedStatus),
108
+ isPersistedReady: vue.computed(() => snapshot.value.isPersistedReady),
109
+ persistedError: vue.computed(() => snapshot.value.persistedError),
107
110
  isIdle: vue.computed(() => snapshot.value.isIdle),
108
111
  isError: vue.computed(() => snapshot.value.isError),
109
112
  isCleanedUp: vue.computed(() => snapshot.value.isCleanedUp),
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveInfiniteQuery.cjs","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["import { computed, shallowRef, toValue, watchEffect } from 'vue'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryCollection,\n createLiveQueryWindowController,\n fetchNextLiveQueryWindowPage,\n getLiveQueryWindowCollectionWarning,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n NonSingleResult,\n QueryBuilder,\n UtilsRecord,\n} from '@tanstack/db'\nimport type { ComputedRef, MaybeRefOrGetter } from 'vue'\n\nconst DEFAULT_GC_TIME_MS = 1\n\ntype InternalCollection = Collection<object, string | number, UtilsRecord>\n\ntype PreviousController = {\n getSnapshot: () => { pages: ReadonlyArray<ReadonlyArray<unknown>> }\n}\n\ntype InfiniteQueryOptions = {\n pageSize?: number\n /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\n}\n\n// Keep the generic parameter for existing typed config wrappers.\nexport type LiveInfiniteQueryConfig<_TRow> = InfiniteQueryOptions\n\nexport type UseLiveInfiniteQueryConfig<\n TContext extends Context & NonSingleResult,\n> = LiveInfiniteQueryConfig<InferResultType<TContext>[number]>\n\nexport interface UseLiveInfiniteQueryReturn<\n TContext extends Context & NonSingleResult,\n> {\n state: ComputedRef<Map<string | number, GetResult<TContext>>>\n data: ComputedRef<InferResultType<TContext>>\n collection: ComputedRef<\n Collection<GetResult<TContext>, string | number, UtilsRecord>\n >\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n pages: ComputedRef<Array<Array<InferResultType<TContext>[number]>>>\n pageParams: ComputedRef<Array<number>>\n fetchNextPage: () => Promise<void>\n hasNextPage: ComputedRef<boolean>\n isFetchingNextPage: ComputedRef<boolean>\n error: ComputedRef<unknown>\n}\n\nexport interface UseLiveInfiniteQueryReturnWithCollection<\n TResult extends object,\n TKey extends string | number,\n TUtils extends UtilsRecord,\n> {\n state: ComputedRef<Map<TKey, TResult>>\n data: ComputedRef<Array<TResult>>\n collection: ComputedRef<Collection<TResult, TKey, TUtils>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n pages: ComputedRef<Array<Array<TResult>>>\n pageParams: ComputedRef<Array<number>>\n fetchNextPage: () => Promise<void>\n hasNextPage: ComputedRef<boolean>\n isFetchingNextPage: ComputedRef<boolean>\n error: ComputedRef<unknown>\n}\n\n/**\n * Create a Vue-native reactive view over the shared live-query window\n * controller. The query must include an `orderBy` clause.\n */\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends UtilsRecord,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & NonSingleResult\n >,\n config: LiveInfiniteQueryConfig<TResult>,\n): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>\n\nexport function useLiveInfiniteQuery<\n TContext extends Context & NonSingleResult,\n>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveInfiniteQueryReturn<TContext>\n\nexport function useLiveInfiniteQuery<\n TContext extends Context & NonSingleResult,\n>(\n queryFnOrCollection: unknown,\n config: InfiniteQueryOptions,\n deps: Array<MaybeRefOrGetter<unknown>> = [],\n): UseLiveInfiniteQueryReturn<TContext> {\n if (`getNextPageParam` in config) {\n throw new Error(\n `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`,\n )\n }\n let validatedCollection: InternalCollection | null = null\n let previousController: PreviousController | null = null\n let previousInput: ReturnType<\n typeof resolveLiveQueryWindowInput<TContext>\n > | null = null\n let previousDependencies: Array<unknown> | null = null\n let previousPageSize: number | null = null\n let previousInitialPageParam: number | null = null\n\n const controller = computed(() => {\n const dependencies = deps.map((dependency) => toValue(dependency))\n\n const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)\n const initialPageParam = config.initialPageParam ?? 0\n const unwrappedInput =\n typeof queryFnOrCollection === `function`\n ? queryFnOrCollection\n : toValue(queryFnOrCollection)\n const input = resolveLiveQueryWindowInput<TContext>(unwrappedInput)\n const dependencyComparison = compareLiveQueryWindowDependencies(\n previousDependencies,\n dependencies,\n )\n const dependenciesChanged = dependencyComparison.changed\n const dependenciesStructurallyEqual = dependencyComparison.structurallyEqual\n const pageShapeChanged =\n previousPageSize !== pageSize ||\n previousInitialPageParam !== initialPageParam\n const sameCollection =\n input.kind === `collection` &&\n previousInput?.kind === `collection` &&\n previousInput.collection === input.collection\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: previousController !== null,\n previousInputKind: previousInput?.kind,\n inputKind: input.kind,\n sameCollection,\n dependenciesChanged,\n dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n const previousPageCount = previousController\n ? Math.max(1, previousController.getSnapshot().pages.length)\n : 1\n const initialPageCount = canPreservePageCount ? previousPageCount : 1\n\n previousInput = input\n previousDependencies = [...dependencies]\n previousPageSize = pageSize\n previousInitialPageParam = initialPageParam\n\n if (input.kind === `collection`) {\n const collection = input.collection\n const warning = getLiveQueryWindowCollectionWarning(\n collection,\n pageSize + 1,\n )\n\n if (validatedCollection !== collection) {\n validatedCollection = collection\n if (warning) console.warn(warning)\n }\n\n const currentController = createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n })\n previousController = currentController\n return currentController\n }\n\n const collection = createLiveQueryCollection({\n query: input.query.limit(pageSize + 1).offset(0),\n startSync: false,\n gcTime: DEFAULT_GC_TIME_MS,\n })\n assertLiveQueryWindowManyResult(collection)\n const currentController = createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n })\n previousController = currentController\n return currentController\n })\n\n const snapshot = shallowRef(controller.value.getSnapshot())\n\n watchEffect(\n (onInvalidate) => {\n const currentController = controller.value\n const updateSnapshot = () => {\n snapshot.value = currentController.getSnapshot()\n }\n\n updateSnapshot()\n const unsubscribe = currentController.subscribe(updateSnapshot)\n updateSnapshot()\n\n onInvalidate(() => {\n unsubscribe()\n currentController.dispose()\n })\n },\n { flush: `sync` },\n )\n\n return {\n state: computed(\n () => snapshot.value.state as Map<string | number, GetResult<TContext>>,\n ),\n data: computed(() => snapshot.value.data as InferResultType<TContext>),\n collection: computed(\n () =>\n snapshot.value.collection as Collection<\n GetResult<TContext>,\n string | number,\n UtilsRecord\n >,\n ),\n status: computed(() => snapshot.value.status as CollectionStatus),\n isLoading: computed(() => snapshot.value.isLoading),\n isReady: computed(() => snapshot.value.isReady),\n isIdle: computed(() => snapshot.value.isIdle),\n isError: computed(() => snapshot.value.isError),\n isCleanedUp: computed(() => snapshot.value.isCleanedUp),\n pages: computed(\n () =>\n snapshot.value.pages as Array<Array<InferResultType<TContext>[number]>>,\n ),\n pageParams: computed(() => snapshot.value.pageParams as Array<number>),\n fetchNextPage: () => fetchNextLiveQueryWindowPage(controller.value),\n hasNextPage: computed(() => snapshot.value.hasNextPage),\n isFetchingNextPage: computed(() => snapshot.value.isFetchingNextPage),\n error: computed(() => snapshot.value.error),\n }\n}\n"],"names":["computed","toValue","normalizeLiveQueryWindowPageSize","resolveLiveQueryWindowInput","compareLiveQueryWindowDependencies","shouldPreserveLiveQueryWindowPageCount","collection","getLiveQueryWindowCollectionWarning","currentController","createLiveQueryWindowController","createLiveQueryCollection","assertLiveQueryWindowManyResult","shallowRef","watchEffect","fetchNextLiveQueryWindowPage"],"mappings":"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"}
1
+ {"version":3,"file":"useLiveInfiniteQuery.cjs","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["import { computed, shallowRef, toValue, watchEffect } from 'vue'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryCollection,\n createLiveQueryWindowController,\n fetchNextLiveQueryWindowPage,\n getLiveQueryWindowCollectionWarning,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryPersistedStatus,\n NonSingleResult,\n QueryBuilder,\n UtilsRecord,\n} from '@tanstack/db'\nimport type { ComputedRef, MaybeRefOrGetter } from 'vue'\n\nconst DEFAULT_GC_TIME_MS = 1\n\ntype InternalCollection = Collection<object, string | number, UtilsRecord>\n\ntype PreviousController = {\n getSnapshot: () => { pages: ReadonlyArray<ReadonlyArray<unknown>> }\n}\n\ntype InfiniteQueryOptions = {\n pageSize?: number\n /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\n}\n\n// Keep the generic parameter for existing typed config wrappers.\nexport type LiveInfiniteQueryConfig<_TRow> = InfiniteQueryOptions\n\nexport type UseLiveInfiniteQueryConfig<\n TContext extends Context & NonSingleResult,\n> = LiveInfiniteQueryConfig<InferResultType<TContext>[number]>\n\nexport interface UseLiveInfiniteQueryReturn<\n TContext extends Context & NonSingleResult,\n> {\n state: ComputedRef<Map<string | number, GetResult<TContext>>>\n data: ComputedRef<InferResultType<TContext>>\n collection: ComputedRef<\n Collection<GetResult<TContext>, string | number, UtilsRecord>\n >\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n pages: ComputedRef<Array<Array<InferResultType<TContext>[number]>>>\n pageParams: ComputedRef<Array<number>>\n fetchNextPage: () => Promise<void>\n hasNextPage: ComputedRef<boolean>\n isFetchingNextPage: ComputedRef<boolean>\n error: ComputedRef<unknown>\n}\n\nexport interface UseLiveInfiniteQueryReturnWithCollection<\n TResult extends object,\n TKey extends string | number,\n TUtils extends UtilsRecord,\n> {\n state: ComputedRef<Map<TKey, TResult>>\n data: ComputedRef<Array<TResult>>\n collection: ComputedRef<Collection<TResult, TKey, TUtils>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n pages: ComputedRef<Array<Array<TResult>>>\n pageParams: ComputedRef<Array<number>>\n fetchNextPage: () => Promise<void>\n hasNextPage: ComputedRef<boolean>\n isFetchingNextPage: ComputedRef<boolean>\n error: ComputedRef<unknown>\n}\n\n/**\n * Create a Vue-native reactive view over the shared live-query window\n * controller. The query must include an `orderBy` clause.\n */\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends UtilsRecord,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & NonSingleResult\n >,\n config: LiveInfiniteQueryConfig<TResult>,\n): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>\n\nexport function useLiveInfiniteQuery<\n TContext extends Context & NonSingleResult,\n>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveInfiniteQueryReturn<TContext>\n\nexport function useLiveInfiniteQuery<\n TContext extends Context & NonSingleResult,\n>(\n queryFnOrCollection: unknown,\n config: InfiniteQueryOptions,\n deps: Array<MaybeRefOrGetter<unknown>> = [],\n): UseLiveInfiniteQueryReturn<TContext> {\n if (`getNextPageParam` in config) {\n throw new Error(\n `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`,\n )\n }\n let validatedCollection: InternalCollection | null = null\n let previousController: PreviousController | null = null\n let previousInput: ReturnType<\n typeof resolveLiveQueryWindowInput<TContext>\n > | null = null\n let previousDependencies: Array<unknown> | null = null\n let previousPageSize: number | null = null\n let previousInitialPageParam: number | null = null\n\n const controller = computed(() => {\n const dependencies = deps.map((dependency) => toValue(dependency))\n\n const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)\n const initialPageParam = config.initialPageParam ?? 0\n const unwrappedInput =\n typeof queryFnOrCollection === `function`\n ? queryFnOrCollection\n : toValue(queryFnOrCollection)\n const input = resolveLiveQueryWindowInput<TContext>(unwrappedInput)\n const dependencyComparison = compareLiveQueryWindowDependencies(\n previousDependencies,\n dependencies,\n )\n const dependenciesChanged = dependencyComparison.changed\n const dependenciesStructurallyEqual = dependencyComparison.structurallyEqual\n const pageShapeChanged =\n previousPageSize !== pageSize ||\n previousInitialPageParam !== initialPageParam\n const sameCollection =\n input.kind === `collection` &&\n previousInput?.kind === `collection` &&\n previousInput.collection === input.collection\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: previousController !== null,\n previousInputKind: previousInput?.kind,\n inputKind: input.kind,\n sameCollection,\n dependenciesChanged,\n dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n const previousPageCount = previousController\n ? Math.max(1, previousController.getSnapshot().pages.length)\n : 1\n const initialPageCount = canPreservePageCount ? previousPageCount : 1\n\n previousInput = input\n previousDependencies = [...dependencies]\n previousPageSize = pageSize\n previousInitialPageParam = initialPageParam\n\n if (input.kind === `collection`) {\n const collection = input.collection\n const warning = getLiveQueryWindowCollectionWarning(\n collection,\n pageSize + 1,\n )\n\n if (validatedCollection !== collection) {\n validatedCollection = collection\n if (warning) console.warn(warning)\n }\n\n const currentController = createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n })\n previousController = currentController\n return currentController\n }\n\n const collection = createLiveQueryCollection({\n query: input.query.limit(pageSize + 1).offset(0),\n startSync: false,\n gcTime: DEFAULT_GC_TIME_MS,\n })\n assertLiveQueryWindowManyResult(collection)\n const currentController = createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n })\n previousController = currentController\n return currentController\n })\n\n const snapshot = shallowRef(controller.value.getSnapshot())\n\n watchEffect(\n (onInvalidate) => {\n const currentController = controller.value\n const updateSnapshot = () => {\n snapshot.value = currentController.getSnapshot()\n }\n\n updateSnapshot()\n const unsubscribe = currentController.subscribe(updateSnapshot)\n updateSnapshot()\n\n onInvalidate(() => {\n unsubscribe()\n currentController.dispose()\n })\n },\n { flush: `sync` },\n )\n\n return {\n state: computed(\n () => snapshot.value.state as Map<string | number, GetResult<TContext>>,\n ),\n data: computed(() => snapshot.value.data as InferResultType<TContext>),\n collection: computed(\n () =>\n snapshot.value.collection as Collection<\n GetResult<TContext>,\n string | number,\n UtilsRecord\n >,\n ),\n status: computed(() => snapshot.value.status as CollectionStatus),\n isLoading: computed(() => snapshot.value.isLoading),\n isReady: computed(() => snapshot.value.isReady),\n persistedStatus: computed(() => snapshot.value.persistedStatus),\n isPersistedReady: computed(() => snapshot.value.isPersistedReady),\n persistedError: computed(() => snapshot.value.persistedError),\n isIdle: computed(() => snapshot.value.isIdle),\n isError: computed(() => snapshot.value.isError),\n isCleanedUp: computed(() => snapshot.value.isCleanedUp),\n pages: computed(\n () =>\n snapshot.value.pages as Array<Array<InferResultType<TContext>[number]>>,\n ),\n pageParams: computed(() => snapshot.value.pageParams as Array<number>),\n fetchNextPage: () => fetchNextLiveQueryWindowPage(controller.value),\n hasNextPage: computed(() => snapshot.value.hasNextPage),\n isFetchingNextPage: computed(() => snapshot.value.isFetchingNextPage),\n error: computed(() => snapshot.value.error),\n }\n}\n"],"names":["computed","toValue","normalizeLiveQueryWindowPageSize","resolveLiveQueryWindowInput","compareLiveQueryWindowDependencies","shouldPreserveLiveQueryWindowPageCount","collection","getLiveQueryWindowCollectionWarning","currentController","createLiveQueryWindowController","createLiveQueryCollection","assertLiveQueryWindowManyResult","shallowRef","watchEffect","fetchNextLiveQueryWindowPage"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, NonSingleResult, QueryBuilder, UtilsRecord } from '@tanstack/db';
1
+ import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, LiveQueryPersistedStatus, NonSingleResult, QueryBuilder, UtilsRecord } from '@tanstack/db';
2
2
  import { ComputedRef, MaybeRefOrGetter } from 'vue';
3
3
  type InfiniteQueryOptions = {
4
4
  pageSize?: number;
@@ -14,6 +14,9 @@ export interface UseLiveInfiniteQueryReturn<TContext extends Context & NonSingle
14
14
  status: ComputedRef<CollectionStatus>;
15
15
  isLoading: ComputedRef<boolean>;
16
16
  isReady: ComputedRef<boolean>;
17
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
18
+ isPersistedReady: ComputedRef<boolean>;
19
+ persistedError: ComputedRef<unknown | undefined>;
17
20
  isIdle: ComputedRef<boolean>;
18
21
  isError: ComputedRef<boolean>;
19
22
  isCleanedUp: ComputedRef<boolean>;
@@ -31,6 +34,9 @@ export interface UseLiveInfiniteQueryReturnWithCollection<TResult extends object
31
34
  status: ComputedRef<CollectionStatus>;
32
35
  isLoading: ComputedRef<boolean>;
33
36
  isReady: ComputedRef<boolean>;
37
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
38
+ isPersistedReady: ComputedRef<boolean>;
39
+ persistedError: ComputedRef<unknown | undefined>;
34
40
  isIdle: ComputedRef<boolean>;
35
41
  isError: ComputedRef<boolean>;
36
42
  isCleanedUp: ComputedRef<boolean>;
@@ -35,10 +35,11 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
35
35
  }
36
36
  deps.forEach((dep) => vue.toValue(dep));
37
37
  if (typeof unwrappedParam === `function`) {
38
+ const disabledQuery = /* @__PURE__ */ Symbol();
38
39
  const wrappedQuery = (q) => {
39
40
  const result = unwrappedParam(q);
40
41
  if (result === void 0 || result === null) {
41
- throw new Error(`__DISABLED_QUERY__`);
42
+ throw disabledQuery;
42
43
  }
43
44
  return result;
44
45
  };
@@ -48,7 +49,7 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
48
49
  startSync: true
49
50
  });
50
51
  } catch (error) {
51
- if (error instanceof Error && error.message === `__DISABLED_QUERY__`) {
52
+ if (error === disabledQuery) {
52
53
  return null;
53
54
  }
54
55
  throw error;
@@ -61,25 +62,26 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
61
62
  }
62
63
  });
63
64
  const state = vue.reactive(/* @__PURE__ */ new Map());
64
- const internalData = vue.reactive([]);
65
+ const internalData = vue.ref([]);
65
66
  const data = vue.computed(() => {
66
67
  const currentCollection = collection.value;
67
68
  if (!currentCollection) {
68
- return internalData;
69
+ return internalData.value;
69
70
  }
70
- return db.isSingleResultCollection(currentCollection) ? internalData[0] : internalData;
71
+ return db.isSingleResultCollection(currentCollection) ? internalData.value[0] : internalData.value;
71
72
  });
72
73
  const status = vue.ref(
73
74
  collection.value ? collection.value.status : `disabled`
74
75
  );
75
- const syncDataFromCollection = (currentCollection) => {
76
- internalData.length = 0;
77
- internalData.push(...Array.from(currentCollection.values()));
78
- };
76
+ const persistedStatus = vue.ref(`unavailable`);
77
+ const persistedError = vue.shallowRef(void 0);
79
78
  let currentObserver = null;
80
79
  const syncFromObserver = (observer, currentCollection) => {
81
- status.value = observer.getSnapshot().status;
82
- syncDataFromCollection(currentCollection);
80
+ const snapshot = observer.getSnapshot();
81
+ status.value = snapshot.status;
82
+ persistedStatus.value = snapshot.persistedStatus;
83
+ persistedError.value = snapshot.persistedError;
84
+ internalData.value = Array.from(currentCollection.values());
83
85
  };
84
86
  vue.watchEffect((onInvalidate) => {
85
87
  const currentCollection = collection.value;
@@ -87,8 +89,10 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
87
89
  currentObserver = null;
88
90
  if (!currentCollection) {
89
91
  status.value = `disabled`;
92
+ persistedStatus.value = `unavailable`;
93
+ persistedError.value = void 0;
90
94
  state.clear();
91
- internalData.length = 0;
95
+ internalData.value = [];
92
96
  return;
93
97
  }
94
98
  const observer = db.createLiveQueryObserver(currentCollection);
@@ -137,6 +141,9 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
137
141
  isReady: vue.computed(
138
142
  () => status.value === `ready` || status.value === `disabled`
139
143
  ),
144
+ persistedStatus: vue.computed(() => persistedStatus.value),
145
+ isPersistedReady: vue.computed(() => persistedStatus.value === `ready`),
146
+ persistedError: vue.computed(() => persistedError.value),
140
147
  isIdle: vue.computed(() => status.value === `idle`),
141
148
  isError: vue.computed(() => status.value === `error`),
142
149
  isCleanedUp: vue.computed(() => status.value === `cleaned-up`)
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n computed,\n getCurrentInstance,\n onUnmounted,\n reactive,\n ref,\n toValue,\n watchEffect,\n} from 'vue'\nimport {\n createLiveQueryCollection,\n createLiveQueryObserver,\n isCollection,\n isSingleResultCollection,\n} from '@tanstack/db'\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\nimport type { ComputedRef, MaybeRefOrGetter } from 'vue'\n\n/**\n * Return type for useLiveQuery hook\n * @property state - Reactive Map of query results (key → item)\n * @property data - Reactive array of query results in order, or single result for findOne queries\n * @property collection - The underlying query collection instance\n * @property status - Current query status\n * @property isLoading - True while initial query data is loading\n * @property isReady - True when query has received first data and is ready\n * @property isIdle - True when query hasn't started yet\n * @property isError - True when query encountered an error\n * @property isCleanedUp - True when query has been cleaned up\n */\nexport interface UseLiveQueryReturn<TContext extends Context> {\n state: ComputedRef<Map<string | number, GetResult<TContext>>>\n data: ComputedRef<InferResultType<TContext>>\n collection: ComputedRef<Collection<GetResult<TContext>, string | number, {}>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\nexport interface UseLiveQueryReturnWithCollection<\n T extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> {\n state: ComputedRef<Map<TKey, T>>\n data: ComputedRef<Array<T>>\n collection: ComputedRef<Collection<T, TKey, TUtils>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\nexport interface UseLiveQueryReturnWithSingleResultCollection<\n T extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> {\n state: ComputedRef<Map<TKey, T>>\n data: ComputedRef<T | undefined>\n collection: ComputedRef<Collection<T, TKey, TUtils> & SingleResult>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\n/**\n * Create a live query using a query function\n * @param queryFn - Query function that defines what data to fetch\n * @param deps - Array of reactive dependencies that trigger query re-execution when changed\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Basic query with object syntax\n * const { data, isLoading } = useLiveQuery((q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * )\n *\n * @example\n * // With reactive dependencies\n * const minPriority = ref(5)\n * const { data, state } = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority.value)),\n * [minPriority] // Re-run when minPriority changes\n * )\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery((q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * )\n *\n * @example\n * // Handle loading and error states in template\n * const { data, isLoading, isError, status } = useLiveQuery((q) =>\n * q.from({ todos: todoCollection })\n * )\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Error: {{ status }}</div>\n * // <ul v-else>\n * // <li v-for=\"todo in data\" :key=\"todo.id\">{{ todo.text }}</li>\n * // </ul>\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n// Overload 1b: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - Array of reactive dependencies that trigger query re-execution when changed\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Basic config object usage\n * const { data, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * })\n *\n * @example\n * // With reactive dependencies\n * const filter = ref('active')\n * const { data, isReady } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.status, filter.value))\n * }, [filter])\n *\n * @example\n * // Handle all states uniformly\n * const { data, isLoading, isReady, isError } = useLiveQuery({\n * query: (q) => q.from({ items: itemCollection })\n * })\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Something went wrong</div>\n * // <div v-else-if=\"!isReady\">Preparing...</div>\n * // <div v-else>{{ data.length }} items loaded</div>\n */\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n/**\n * Subscribe to an existing query collection (can be reactive)\n * @param liveQueryCollection - Pre-created query collection to subscribe to (can be a ref)\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Using pre-created query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const { data, collection } = useLiveQuery(myLiveQuery)\n *\n * @example\n * // Reactive query collection reference\n * const selectedQuery = ref(todosQuery)\n * const { data, collection } = useLiveQuery(selectedQuery)\n *\n * // Switch queries reactively\n * selectedQuery.value = archiveQuery\n *\n * @example\n * // Access query collection methods directly\n * const { data, collection, isReady } = useLiveQuery(existingQuery)\n *\n * // Use underlying collection for mutations\n * const handleToggle = (id) => {\n * collection.value.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const { data, isLoading, isError } = useLiveQuery(sharedQuery)\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Error loading data</div>\n * // <div v-else>\n * // <Item v-for=\"item in data\" :key=\"item.id\" v-bind=\"item\" />\n * // </div>\n */\n// Overload 3: Accept pre-created live query collection (can be reactive) - non-single result\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & NonSingleResult\n >,\n): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils>\n\n// Overload 4: Accept pre-created live query collection with singleResult: true\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & SingleResult\n >,\n): UseLiveQueryReturnWithSingleResultCollection<TResult, TKey, TUtils>\n\n// Implementation\nexport function useLiveQuery(\n configOrQueryOrCollection: any,\n deps: Array<MaybeRefOrGetter<unknown>> = [],\n): UseLiveQueryReturn<any> | UseLiveQueryReturnWithCollection<any, any, any> {\n const collection = computed(() => {\n // First check if the original parameter might be a ref/getter\n // by seeing if toValue returns something different than the original\n // NOTE: Don't call toValue on functions - toValue treats functions as getters and calls them!\n let unwrappedParam = configOrQueryOrCollection\n if (typeof configOrQueryOrCollection !== `function`) {\n try {\n const potentiallyUnwrapped = toValue(configOrQueryOrCollection)\n if (potentiallyUnwrapped !== configOrQueryOrCollection) {\n unwrappedParam = potentiallyUnwrapped\n }\n } catch {\n // If toValue fails, use original parameter\n unwrappedParam = configOrQueryOrCollection\n }\n }\n\n // Check if it's already a collection\n const inputIsCollection = isCollection(unwrappedParam)\n\n if (inputIsCollection) {\n // Warn when passing a collection directly with on-demand sync mode\n // In on-demand mode, data is only loaded when queries with predicates request it\n // Passing the collection directly doesn't provide any predicates, so no data loads\n const syncMode = (unwrappedParam as { config?: { syncMode?: string } })\n .config?.syncMode\n if (syncMode === `on-demand`) {\n console.warn(\n `[useLiveQuery] Warning: Passing a collection with syncMode \"on-demand\" directly to useLiveQuery ` +\n `will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.\\n\\n` +\n `Instead, use a query builder function:\\n` +\n ` const { data } = useLiveQuery((q) => q.from({ c: myCollection }).select(({ c }) => c))\\n\\n` +\n `Or switch to syncMode \"eager\" if you want all data to sync automatically.`,\n )\n }\n // It's already a collection, ensure sync is started for Vue hooks\n // Only start sync if the collection is in idle state\n if (unwrappedParam.status === `idle`) {\n unwrappedParam.startSyncImmediate()\n }\n return unwrappedParam\n }\n\n // Reference deps to make computed reactive to them\n deps.forEach((dep) => toValue(dep))\n\n // Ensure we always start sync for Vue hooks\n if (typeof unwrappedParam === `function`) {\n // To avoid calling the query function twice, we wrap it to handle null/undefined returns\n // The wrapper will be called once by createLiveQueryCollection\n const wrappedQuery = (q: InitialQueryBuilder) => {\n const result = unwrappedParam(q)\n // If the query function returns null/undefined, throw a special error\n // that we'll catch to return null collection\n if (result === undefined || result === null) {\n throw new Error(`__DISABLED_QUERY__`)\n }\n return result\n }\n\n try {\n return createLiveQueryCollection({\n query: wrappedQuery,\n startSync: true,\n })\n } catch (error) {\n // Check if this is our special disabled query marker\n if (error instanceof Error && error.message === `__DISABLED_QUERY__`) {\n return null\n }\n // Re-throw other errors\n throw error\n }\n } else {\n return createLiveQueryCollection({\n ...unwrappedParam,\n startSync: true,\n })\n }\n })\n\n // Reactive state that gets updated granularly through change events\n const state = reactive(new Map<string | number, any>())\n\n // Reactive data array that maintains sorted order\n const internalData = reactive<Array<any>>([])\n\n // Computed wrapper for the data to match expected return type\n // Returns single item for singleResult collections, array otherwise\n const data = computed(() => {\n const currentCollection = collection.value\n if (!currentCollection) {\n return internalData\n }\n return isSingleResultCollection(currentCollection)\n ? internalData[0]\n : internalData\n })\n\n // Track collection status reactively\n const status = ref(\n collection.value ? collection.value.status : (`disabled` as const),\n )\n\n // Helper to sync data array from collection in correct order\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>,\n ) => {\n internalData.length = 0\n internalData.push(...Array.from(currentCollection.values()))\n }\n\n // The shared observer owns subscription, the ready-race, and status; Vue\n // materializes into its own reactive map (granular) + ordered array.\n let currentObserver: LiveQueryObserver<any, any> | null = null\n\n const syncFromObserver = (\n observer: LiveQueryObserver<any, any>,\n currentCollection: Collection<any, any, any>,\n ) => {\n status.value = observer.getSnapshot().status as CollectionStatus\n syncDataFromCollection(currentCollection)\n }\n\n // Watch for collection changes and subscribe to updates\n watchEffect((onInvalidate) => {\n const currentCollection = collection.value\n\n // Tear down any previous observer.\n currentObserver?.dispose()\n currentObserver = null\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n status.value = `disabled` as const\n state.clear()\n internalData.length = 0\n return\n }\n\n const observer = createLiveQueryObserver(currentCollection)\n currentObserver = observer\n\n // Initial rows arrive as the observer's first delta (includeInitialState);\n // apply them and every subsequent delta granularly to the reactive map.\n state.clear()\n\n const unsubscribe = observer.subscribe(\n (changes: Array<ChangeMessage<any>> | undefined) => {\n if (changes) {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n case `update`:\n state.set(change.key, change.value)\n break\n case `delete`:\n state.delete(change.key)\n break\n }\n }\n } else {\n // Cleanup and other status-only publications carry no row deltas.\n // Rebuild the keyed view so it cannot diverge from ordered data.\n state.clear()\n for (const [key, value] of observer.getSnapshot().state ?? []) {\n state.set(key, value)\n }\n }\n syncFromObserver(observer, currentCollection)\n },\n )\n syncFromObserver(observer, currentCollection)\n\n // Cleanup when effect is invalidated\n onInvalidate(() => {\n unsubscribe()\n observer.dispose()\n currentObserver = null\n })\n })\n\n // Cleanup on unmount (only if we're in a component context)\n const instance = getCurrentInstance()\n if (instance) {\n onUnmounted(() => currentObserver?.dispose())\n }\n\n return {\n state: computed(() => state),\n data,\n collection: computed(() => collection.value),\n status: computed(() => status.value),\n isLoading: computed(() => status.value === `loading`),\n isReady: computed(\n () => status.value === `ready` || status.value === `disabled`,\n ),\n isIdle: computed(() => status.value === `idle`),\n isError: computed(() => status.value === `error`),\n isCleanedUp: computed(() => status.value === `cleaned-up`),\n }\n}\n"],"names":["computed","toValue","isCollection","createLiveQueryCollection","reactive","isSingleResultCollection","ref","watchEffect","createLiveQueryObserver","getCurrentInstance","onUnmounted"],"mappings":"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"}
1
+ {"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n computed,\n getCurrentInstance,\n onUnmounted,\n reactive,\n ref,\n shallowRef,\n toValue,\n watchEffect,\n} from 'vue'\nimport {\n createLiveQueryCollection,\n createLiveQueryObserver,\n isCollection,\n isSingleResultCollection,\n} from '@tanstack/db'\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n LiveQueryPersistedStatus,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\nimport type { ComputedRef, MaybeRefOrGetter } from 'vue'\n\n/**\n * Return type for useLiveQuery hook\n * @property state - Reactive Map of query results (key → item)\n * @property data - Reactive array of query results in order, or single result for findOne queries\n * @property collection - The underlying query collection instance\n * @property status - Current query status\n * @property isLoading - True while initial query data is loading\n * @property isReady - True when query has received first data and is ready\n * @property isIdle - True when query hasn't started yet\n * @property isError - True when query encountered an error\n * @property isCleanedUp - True when query has been cleaned up\n */\nexport interface UseLiveQueryReturn<TContext extends Context> {\n state: ComputedRef<Map<string | number, GetResult<TContext>>>\n data: ComputedRef<InferResultType<TContext>>\n collection: ComputedRef<Collection<GetResult<TContext>, string | number, {}>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\ntype InferConditionalResultType<TContext extends Context> =\n TContext extends SingleResult\n ? InferResultType<TContext> | []\n : InferResultType<TContext>\n\nexport type ConditionalUseLiveQueryReturn<TContext extends Context> = Omit<\n UseLiveQueryReturn<TContext>,\n `data` | `collection` | `status`\n> & {\n data: ComputedRef<InferConditionalResultType<TContext>>\n collection: ComputedRef<Collection<\n GetResult<TContext>,\n string | number,\n {}\n > | null>\n status: ComputedRef<CollectionStatus | `disabled`>\n}\n\nexport interface UseLiveQueryReturnWithCollection<\n T extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> {\n state: ComputedRef<Map<TKey, T>>\n data: ComputedRef<Array<T>>\n collection: ComputedRef<Collection<T, TKey, TUtils>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\nexport interface UseLiveQueryReturnWithSingleResultCollection<\n T extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> {\n state: ComputedRef<Map<TKey, T>>\n data: ComputedRef<T | undefined>\n collection: ComputedRef<Collection<T, TKey, TUtils> & SingleResult>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\n/**\n * Create a live query using a query function\n * @param queryFn - Query function that defines what data to fetch\n * @param deps - Array of reactive dependencies that trigger query re-execution when changed\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Basic query with object syntax\n * const { data, isLoading } = useLiveQuery((q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * )\n *\n * @example\n * // With reactive dependencies\n * const minPriority = ref(5)\n * const { data, state } = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority.value)),\n * [minPriority] // Re-run when minPriority changes\n * )\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery((q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * )\n *\n * @example\n * // Handle loading and error states in template\n * const { data, isLoading, isError, status } = useLiveQuery((q) =>\n * q.from({ todos: todoCollection })\n * )\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Error: {{ status }}</div>\n * // <ul v-else>\n * // <li v-for=\"todo in data\" :key=\"todo.id\">{{ todo.text }}</li>\n * // </ul>\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n// Overload 1b: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): ConditionalUseLiveQueryReturn<TContext>\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - Array of reactive dependencies that trigger query re-execution when changed\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Basic config object usage\n * const { data, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * })\n *\n * @example\n * // With reactive dependencies\n * const filter = ref('active')\n * const { data, isReady } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.status, filter.value))\n * }, [filter])\n *\n * @example\n * // Handle all states uniformly\n * const { data, isLoading, isReady, isError } = useLiveQuery({\n * query: (q) => q.from({ items: itemCollection })\n * })\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Something went wrong</div>\n * // <div v-else-if=\"!isReady\">Preparing...</div>\n * // <div v-else>{{ data.length }} items loaded</div>\n */\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n/**\n * Subscribe to an existing query collection (can be reactive)\n * @param liveQueryCollection - Pre-created query collection to subscribe to (can be a ref)\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Using pre-created query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const { data, collection } = useLiveQuery(myLiveQuery)\n *\n * @example\n * // Reactive query collection reference\n * const selectedQuery = ref(todosQuery)\n * const { data, collection } = useLiveQuery(selectedQuery)\n *\n * // Switch queries reactively\n * selectedQuery.value = archiveQuery\n *\n * @example\n * // Access query collection methods directly\n * const { data, collection, isReady } = useLiveQuery(existingQuery)\n *\n * // Use underlying collection for mutations\n * const handleToggle = (id) => {\n * collection.value.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const { data, isLoading, isError } = useLiveQuery(sharedQuery)\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Error loading data</div>\n * // <div v-else>\n * // <Item v-for=\"item in data\" :key=\"item.id\" v-bind=\"item\" />\n * // </div>\n */\n// Overload 3: Accept pre-created live query collection (can be reactive) - non-single result\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & NonSingleResult\n >,\n): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils>\n\n// Overload 4: Accept pre-created live query collection with singleResult: true\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & SingleResult\n >,\n): UseLiveQueryReturnWithSingleResultCollection<TResult, TKey, TUtils>\n\n// Implementation\nexport function useLiveQuery(\n configOrQueryOrCollection: any,\n deps: Array<MaybeRefOrGetter<unknown>> = [],\n): UseLiveQueryReturn<any> | UseLiveQueryReturnWithCollection<any, any, any> {\n const collection = computed(() => {\n // First check if the original parameter might be a ref/getter\n // by seeing if toValue returns something different than the original\n // NOTE: Don't call toValue on functions - toValue treats functions as getters and calls them!\n let unwrappedParam = configOrQueryOrCollection\n if (typeof configOrQueryOrCollection !== `function`) {\n try {\n const potentiallyUnwrapped = toValue(configOrQueryOrCollection)\n if (potentiallyUnwrapped !== configOrQueryOrCollection) {\n unwrappedParam = potentiallyUnwrapped\n }\n } catch {\n // If toValue fails, use original parameter\n unwrappedParam = configOrQueryOrCollection\n }\n }\n\n // Check if it's already a collection\n const inputIsCollection = isCollection(unwrappedParam)\n\n if (inputIsCollection) {\n // Warn when passing a collection directly with on-demand sync mode\n // In on-demand mode, data is only loaded when queries with predicates request it\n // Passing the collection directly doesn't provide any predicates, so no data loads\n const syncMode = (unwrappedParam as { config?: { syncMode?: string } })\n .config?.syncMode\n if (syncMode === `on-demand`) {\n console.warn(\n `[useLiveQuery] Warning: Passing a collection with syncMode \"on-demand\" directly to useLiveQuery ` +\n `will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.\\n\\n` +\n `Instead, use a query builder function:\\n` +\n ` const { data } = useLiveQuery((q) => q.from({ c: myCollection }).select(({ c }) => c))\\n\\n` +\n `Or switch to syncMode \"eager\" if you want all data to sync automatically.`,\n )\n }\n // It's already a collection, ensure sync is started for Vue hooks\n // Only start sync if the collection is in idle state\n if (unwrappedParam.status === `idle`) {\n unwrappedParam.startSyncImmediate()\n }\n return unwrappedParam\n }\n\n // Reference deps to make computed reactive to them\n deps.forEach((dep) => toValue(dep))\n\n // Ensure we always start sync for Vue hooks\n if (typeof unwrappedParam === `function`) {\n // To avoid calling the query function twice, we wrap it to handle null/undefined returns\n // The wrapper will be called once by createLiveQueryCollection\n const disabledQuery = Symbol()\n const wrappedQuery = (q: InitialQueryBuilder) => {\n const result = unwrappedParam(q)\n if (result === undefined || result === null) {\n throw disabledQuery\n }\n return result\n }\n\n try {\n return createLiveQueryCollection({\n query: wrappedQuery,\n startSync: true,\n })\n } catch (error) {\n if (error === disabledQuery) {\n return null\n }\n // Re-throw other errors\n throw error\n }\n } else {\n return createLiveQueryCollection({\n ...unwrappedParam,\n startSync: true,\n })\n }\n })\n\n // Reactive state that gets updated granularly through change events\n const state = reactive(new Map<string | number, any>())\n\n // Reactive data array that maintains sorted order\n const internalData = ref<Array<any>>([])\n\n // Computed wrapper for the data to match expected return type\n // Returns single item for singleResult collections, array otherwise\n const data = computed(() => {\n const currentCollection = collection.value\n if (!currentCollection) {\n return internalData.value\n }\n return isSingleResultCollection(currentCollection)\n ? internalData.value[0]\n : internalData.value\n })\n\n // Track collection status reactively\n const status = ref(\n collection.value ? collection.value.status : (`disabled` as const),\n )\n const persistedStatus = ref<LiveQueryPersistedStatus>(`unavailable`)\n const persistedError = shallowRef<unknown>(undefined)\n\n // The shared observer owns subscription, the ready-race, and status; Vue\n // materializes into its own reactive map (granular) + ordered array.\n let currentObserver: LiveQueryObserver<any, any> | null = null\n\n const syncFromObserver = (\n observer: LiveQueryObserver<any, any>,\n currentCollection: Collection<any, any, any>,\n ) => {\n const snapshot = observer.getSnapshot()\n status.value = snapshot.status as CollectionStatus\n persistedStatus.value = snapshot.persistedStatus\n persistedError.value = snapshot.persistedError\n internalData.value = Array.from(currentCollection.values())\n }\n\n // Watch for collection changes and subscribe to updates\n watchEffect((onInvalidate) => {\n const currentCollection = collection.value\n\n // Tear down any previous observer.\n currentObserver?.dispose()\n currentObserver = null\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n status.value = `disabled` as const\n persistedStatus.value = `unavailable`\n persistedError.value = undefined\n state.clear()\n internalData.value = []\n return\n }\n\n const observer = createLiveQueryObserver(currentCollection)\n currentObserver = observer\n\n // Initial rows arrive as the observer's first delta (includeInitialState);\n // apply them and every subsequent delta granularly to the reactive map.\n state.clear()\n\n const unsubscribe = observer.subscribe(\n (changes: Array<ChangeMessage<any>> | undefined) => {\n if (changes) {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n case `update`:\n state.set(change.key, change.value)\n break\n case `delete`:\n state.delete(change.key)\n break\n }\n }\n } else {\n // Cleanup and other status-only publications carry no row deltas.\n // Rebuild the keyed view so it cannot diverge from ordered data.\n state.clear()\n for (const [key, value] of observer.getSnapshot().state ?? []) {\n state.set(key, value)\n }\n }\n syncFromObserver(observer, currentCollection)\n },\n )\n syncFromObserver(observer, currentCollection)\n\n // Cleanup when effect is invalidated\n onInvalidate(() => {\n unsubscribe()\n observer.dispose()\n currentObserver = null\n })\n })\n\n // Cleanup on unmount (only if we're in a component context)\n const instance = getCurrentInstance()\n if (instance) {\n onUnmounted(() => currentObserver?.dispose())\n }\n\n return {\n state: computed(() => state),\n data,\n collection: computed(() => collection.value),\n status: computed(() => status.value),\n isLoading: computed(() => status.value === `loading`),\n isReady: computed(\n () => status.value === `ready` || status.value === `disabled`,\n ),\n persistedStatus: computed(() => persistedStatus.value),\n isPersistedReady: computed(() => persistedStatus.value === `ready`),\n persistedError: computed(() => persistedError.value),\n isIdle: computed(() => status.value === `idle`),\n isError: computed(() => status.value === `error`),\n isCleanedUp: computed(() => status.value === `cleaned-up`),\n }\n}\n"],"names":["computed","toValue","isCollection","createLiveQueryCollection","reactive","ref","isSingleResultCollection","shallowRef","watchEffect","createLiveQueryObserver","getCurrentInstance","onUnmounted"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, LiveQueryCollectionConfig, NonSingleResult, QueryBuilder, SingleResult } from '@tanstack/db';
1
+ import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, LiveQueryCollectionConfig, LiveQueryPersistedStatus, NonSingleResult, QueryBuilder, SingleResult } from '@tanstack/db';
2
2
  import { ComputedRef, MaybeRefOrGetter } from 'vue';
3
3
  /**
4
4
  * Return type for useLiveQuery hook
@@ -19,10 +19,19 @@ export interface UseLiveQueryReturn<TContext extends Context> {
19
19
  status: ComputedRef<CollectionStatus>;
20
20
  isLoading: ComputedRef<boolean>;
21
21
  isReady: ComputedRef<boolean>;
22
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
23
+ isPersistedReady: ComputedRef<boolean>;
24
+ persistedError: ComputedRef<unknown | undefined>;
22
25
  isIdle: ComputedRef<boolean>;
23
26
  isError: ComputedRef<boolean>;
24
27
  isCleanedUp: ComputedRef<boolean>;
25
28
  }
29
+ type InferConditionalResultType<TContext extends Context> = TContext extends SingleResult ? InferResultType<TContext> | [] : InferResultType<TContext>;
30
+ export type ConditionalUseLiveQueryReturn<TContext extends Context> = Omit<UseLiveQueryReturn<TContext>, `data` | `collection` | `status`> & {
31
+ data: ComputedRef<InferConditionalResultType<TContext>>;
32
+ collection: ComputedRef<Collection<GetResult<TContext>, string | number, {}> | null>;
33
+ status: ComputedRef<CollectionStatus | `disabled`>;
34
+ };
26
35
  export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends string | number, TUtils extends Record<string, any>> {
27
36
  state: ComputedRef<Map<TKey, T>>;
28
37
  data: ComputedRef<Array<T>>;
@@ -30,6 +39,9 @@ export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends
30
39
  status: ComputedRef<CollectionStatus>;
31
40
  isLoading: ComputedRef<boolean>;
32
41
  isReady: ComputedRef<boolean>;
42
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
43
+ isPersistedReady: ComputedRef<boolean>;
44
+ persistedError: ComputedRef<unknown | undefined>;
33
45
  isIdle: ComputedRef<boolean>;
34
46
  isError: ComputedRef<boolean>;
35
47
  isCleanedUp: ComputedRef<boolean>;
@@ -41,6 +53,9 @@ export interface UseLiveQueryReturnWithSingleResultCollection<T extends object,
41
53
  status: ComputedRef<CollectionStatus>;
42
54
  isLoading: ComputedRef<boolean>;
43
55
  isReady: ComputedRef<boolean>;
56
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
57
+ isPersistedReady: ComputedRef<boolean>;
58
+ persistedError: ComputedRef<unknown | undefined>;
44
59
  isIdle: ComputedRef<boolean>;
45
60
  isError: ComputedRef<boolean>;
46
61
  isCleanedUp: ComputedRef<boolean>;
@@ -95,7 +110,7 @@ export interface UseLiveQueryReturnWithSingleResultCollection<T extends object,
95
110
  * // </ul>
96
111
  */
97
112
  export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<TContext>;
98
- export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<TContext>;
113
+ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null, deps?: Array<MaybeRefOrGetter<unknown>>): ConditionalUseLiveQueryReturn<TContext>;
99
114
  /**
100
115
  * Create a live query using configuration object
101
116
  * @param config - Configuration object with query and options
@@ -170,3 +185,4 @@ export declare function useLiveQuery<TContext extends Context>(config: LiveQuery
170
185
  */
171
186
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils> & NonSingleResult>): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils>;
172
187
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils> & SingleResult>): UseLiveQueryReturnWithSingleResultCollection<TResult, TKey, TUtils>;
188
+ export {};
@@ -1,4 +1,4 @@
1
- import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, NonSingleResult, QueryBuilder, UtilsRecord } from '@tanstack/db';
1
+ import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, LiveQueryPersistedStatus, NonSingleResult, QueryBuilder, UtilsRecord } from '@tanstack/db';
2
2
  import { ComputedRef, MaybeRefOrGetter } from 'vue';
3
3
  type InfiniteQueryOptions = {
4
4
  pageSize?: number;
@@ -14,6 +14,9 @@ export interface UseLiveInfiniteQueryReturn<TContext extends Context & NonSingle
14
14
  status: ComputedRef<CollectionStatus>;
15
15
  isLoading: ComputedRef<boolean>;
16
16
  isReady: ComputedRef<boolean>;
17
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
18
+ isPersistedReady: ComputedRef<boolean>;
19
+ persistedError: ComputedRef<unknown | undefined>;
17
20
  isIdle: ComputedRef<boolean>;
18
21
  isError: ComputedRef<boolean>;
19
22
  isCleanedUp: ComputedRef<boolean>;
@@ -31,6 +34,9 @@ export interface UseLiveInfiniteQueryReturnWithCollection<TResult extends object
31
34
  status: ComputedRef<CollectionStatus>;
32
35
  isLoading: ComputedRef<boolean>;
33
36
  isReady: ComputedRef<boolean>;
37
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
38
+ isPersistedReady: ComputedRef<boolean>;
39
+ persistedError: ComputedRef<unknown | undefined>;
34
40
  isIdle: ComputedRef<boolean>;
35
41
  isError: ComputedRef<boolean>;
36
42
  isCleanedUp: ComputedRef<boolean>;
@@ -102,6 +102,9 @@ function useLiveInfiniteQuery(queryFnOrCollection, config, deps = []) {
102
102
  status: computed(() => snapshot.value.status),
103
103
  isLoading: computed(() => snapshot.value.isLoading),
104
104
  isReady: computed(() => snapshot.value.isReady),
105
+ persistedStatus: computed(() => snapshot.value.persistedStatus),
106
+ isPersistedReady: computed(() => snapshot.value.isPersistedReady),
107
+ persistedError: computed(() => snapshot.value.persistedError),
105
108
  isIdle: computed(() => snapshot.value.isIdle),
106
109
  isError: computed(() => snapshot.value.isError),
107
110
  isCleanedUp: computed(() => snapshot.value.isCleanedUp),
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveInfiniteQuery.js","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["import { computed, shallowRef, toValue, watchEffect } from 'vue'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryCollection,\n createLiveQueryWindowController,\n fetchNextLiveQueryWindowPage,\n getLiveQueryWindowCollectionWarning,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n NonSingleResult,\n QueryBuilder,\n UtilsRecord,\n} from '@tanstack/db'\nimport type { ComputedRef, MaybeRefOrGetter } from 'vue'\n\nconst DEFAULT_GC_TIME_MS = 1\n\ntype InternalCollection = Collection<object, string | number, UtilsRecord>\n\ntype PreviousController = {\n getSnapshot: () => { pages: ReadonlyArray<ReadonlyArray<unknown>> }\n}\n\ntype InfiniteQueryOptions = {\n pageSize?: number\n /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\n}\n\n// Keep the generic parameter for existing typed config wrappers.\nexport type LiveInfiniteQueryConfig<_TRow> = InfiniteQueryOptions\n\nexport type UseLiveInfiniteQueryConfig<\n TContext extends Context & NonSingleResult,\n> = LiveInfiniteQueryConfig<InferResultType<TContext>[number]>\n\nexport interface UseLiveInfiniteQueryReturn<\n TContext extends Context & NonSingleResult,\n> {\n state: ComputedRef<Map<string | number, GetResult<TContext>>>\n data: ComputedRef<InferResultType<TContext>>\n collection: ComputedRef<\n Collection<GetResult<TContext>, string | number, UtilsRecord>\n >\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n pages: ComputedRef<Array<Array<InferResultType<TContext>[number]>>>\n pageParams: ComputedRef<Array<number>>\n fetchNextPage: () => Promise<void>\n hasNextPage: ComputedRef<boolean>\n isFetchingNextPage: ComputedRef<boolean>\n error: ComputedRef<unknown>\n}\n\nexport interface UseLiveInfiniteQueryReturnWithCollection<\n TResult extends object,\n TKey extends string | number,\n TUtils extends UtilsRecord,\n> {\n state: ComputedRef<Map<TKey, TResult>>\n data: ComputedRef<Array<TResult>>\n collection: ComputedRef<Collection<TResult, TKey, TUtils>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n pages: ComputedRef<Array<Array<TResult>>>\n pageParams: ComputedRef<Array<number>>\n fetchNextPage: () => Promise<void>\n hasNextPage: ComputedRef<boolean>\n isFetchingNextPage: ComputedRef<boolean>\n error: ComputedRef<unknown>\n}\n\n/**\n * Create a Vue-native reactive view over the shared live-query window\n * controller. The query must include an `orderBy` clause.\n */\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends UtilsRecord,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & NonSingleResult\n >,\n config: LiveInfiniteQueryConfig<TResult>,\n): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>\n\nexport function useLiveInfiniteQuery<\n TContext extends Context & NonSingleResult,\n>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveInfiniteQueryReturn<TContext>\n\nexport function useLiveInfiniteQuery<\n TContext extends Context & NonSingleResult,\n>(\n queryFnOrCollection: unknown,\n config: InfiniteQueryOptions,\n deps: Array<MaybeRefOrGetter<unknown>> = [],\n): UseLiveInfiniteQueryReturn<TContext> {\n if (`getNextPageParam` in config) {\n throw new Error(\n `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`,\n )\n }\n let validatedCollection: InternalCollection | null = null\n let previousController: PreviousController | null = null\n let previousInput: ReturnType<\n typeof resolveLiveQueryWindowInput<TContext>\n > | null = null\n let previousDependencies: Array<unknown> | null = null\n let previousPageSize: number | null = null\n let previousInitialPageParam: number | null = null\n\n const controller = computed(() => {\n const dependencies = deps.map((dependency) => toValue(dependency))\n\n const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)\n const initialPageParam = config.initialPageParam ?? 0\n const unwrappedInput =\n typeof queryFnOrCollection === `function`\n ? queryFnOrCollection\n : toValue(queryFnOrCollection)\n const input = resolveLiveQueryWindowInput<TContext>(unwrappedInput)\n const dependencyComparison = compareLiveQueryWindowDependencies(\n previousDependencies,\n dependencies,\n )\n const dependenciesChanged = dependencyComparison.changed\n const dependenciesStructurallyEqual = dependencyComparison.structurallyEqual\n const pageShapeChanged =\n previousPageSize !== pageSize ||\n previousInitialPageParam !== initialPageParam\n const sameCollection =\n input.kind === `collection` &&\n previousInput?.kind === `collection` &&\n previousInput.collection === input.collection\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: previousController !== null,\n previousInputKind: previousInput?.kind,\n inputKind: input.kind,\n sameCollection,\n dependenciesChanged,\n dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n const previousPageCount = previousController\n ? Math.max(1, previousController.getSnapshot().pages.length)\n : 1\n const initialPageCount = canPreservePageCount ? previousPageCount : 1\n\n previousInput = input\n previousDependencies = [...dependencies]\n previousPageSize = pageSize\n previousInitialPageParam = initialPageParam\n\n if (input.kind === `collection`) {\n const collection = input.collection\n const warning = getLiveQueryWindowCollectionWarning(\n collection,\n pageSize + 1,\n )\n\n if (validatedCollection !== collection) {\n validatedCollection = collection\n if (warning) console.warn(warning)\n }\n\n const currentController = createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n })\n previousController = currentController\n return currentController\n }\n\n const collection = createLiveQueryCollection({\n query: input.query.limit(pageSize + 1).offset(0),\n startSync: false,\n gcTime: DEFAULT_GC_TIME_MS,\n })\n assertLiveQueryWindowManyResult(collection)\n const currentController = createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n })\n previousController = currentController\n return currentController\n })\n\n const snapshot = shallowRef(controller.value.getSnapshot())\n\n watchEffect(\n (onInvalidate) => {\n const currentController = controller.value\n const updateSnapshot = () => {\n snapshot.value = currentController.getSnapshot()\n }\n\n updateSnapshot()\n const unsubscribe = currentController.subscribe(updateSnapshot)\n updateSnapshot()\n\n onInvalidate(() => {\n unsubscribe()\n currentController.dispose()\n })\n },\n { flush: `sync` },\n )\n\n return {\n state: computed(\n () => snapshot.value.state as Map<string | number, GetResult<TContext>>,\n ),\n data: computed(() => snapshot.value.data as InferResultType<TContext>),\n collection: computed(\n () =>\n snapshot.value.collection as Collection<\n GetResult<TContext>,\n string | number,\n UtilsRecord\n >,\n ),\n status: computed(() => snapshot.value.status as CollectionStatus),\n isLoading: computed(() => snapshot.value.isLoading),\n isReady: computed(() => snapshot.value.isReady),\n isIdle: computed(() => snapshot.value.isIdle),\n isError: computed(() => snapshot.value.isError),\n isCleanedUp: computed(() => snapshot.value.isCleanedUp),\n pages: computed(\n () =>\n snapshot.value.pages as Array<Array<InferResultType<TContext>[number]>>,\n ),\n pageParams: computed(() => snapshot.value.pageParams as Array<number>),\n fetchNextPage: () => fetchNextLiveQueryWindowPage(controller.value),\n hasNextPage: computed(() => snapshot.value.hasNextPage),\n isFetchingNextPage: computed(() => snapshot.value.isFetchingNextPage),\n error: computed(() => snapshot.value.error),\n }\n}\n"],"names":["collection","currentController"],"mappings":"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"}
1
+ {"version":3,"file":"useLiveInfiniteQuery.js","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["import { computed, shallowRef, toValue, watchEffect } from 'vue'\nimport {\n assertLiveQueryWindowManyResult,\n compareLiveQueryWindowDependencies,\n createLiveQueryCollection,\n createLiveQueryWindowController,\n fetchNextLiveQueryWindowPage,\n getLiveQueryWindowCollectionWarning,\n normalizeLiveQueryWindowPageSize,\n resolveLiveQueryWindowInput,\n shouldPreserveLiveQueryWindowPageCount,\n} from '@tanstack/db'\nimport type {\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryPersistedStatus,\n NonSingleResult,\n QueryBuilder,\n UtilsRecord,\n} from '@tanstack/db'\nimport type { ComputedRef, MaybeRefOrGetter } from 'vue'\n\nconst DEFAULT_GC_TIME_MS = 1\n\ntype InternalCollection = Collection<object, string | number, UtilsRecord>\n\ntype PreviousController = {\n getSnapshot: () => { pages: ReadonlyArray<ReadonlyArray<unknown>> }\n}\n\ntype InfiniteQueryOptions = {\n pageSize?: number\n /** First result-page label, not a server cursor or remote offset. */\n initialPageParam?: number\n}\n\n// Keep the generic parameter for existing typed config wrappers.\nexport type LiveInfiniteQueryConfig<_TRow> = InfiniteQueryOptions\n\nexport type UseLiveInfiniteQueryConfig<\n TContext extends Context & NonSingleResult,\n> = LiveInfiniteQueryConfig<InferResultType<TContext>[number]>\n\nexport interface UseLiveInfiniteQueryReturn<\n TContext extends Context & NonSingleResult,\n> {\n state: ComputedRef<Map<string | number, GetResult<TContext>>>\n data: ComputedRef<InferResultType<TContext>>\n collection: ComputedRef<\n Collection<GetResult<TContext>, string | number, UtilsRecord>\n >\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n pages: ComputedRef<Array<Array<InferResultType<TContext>[number]>>>\n pageParams: ComputedRef<Array<number>>\n fetchNextPage: () => Promise<void>\n hasNextPage: ComputedRef<boolean>\n isFetchingNextPage: ComputedRef<boolean>\n error: ComputedRef<unknown>\n}\n\nexport interface UseLiveInfiniteQueryReturnWithCollection<\n TResult extends object,\n TKey extends string | number,\n TUtils extends UtilsRecord,\n> {\n state: ComputedRef<Map<TKey, TResult>>\n data: ComputedRef<Array<TResult>>\n collection: ComputedRef<Collection<TResult, TKey, TUtils>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n pages: ComputedRef<Array<Array<TResult>>>\n pageParams: ComputedRef<Array<number>>\n fetchNextPage: () => Promise<void>\n hasNextPage: ComputedRef<boolean>\n isFetchingNextPage: ComputedRef<boolean>\n error: ComputedRef<unknown>\n}\n\n/**\n * Create a Vue-native reactive view over the shared live-query window\n * controller. The query must include an `orderBy` clause.\n */\nexport function useLiveInfiniteQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends UtilsRecord,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & NonSingleResult\n >,\n config: LiveInfiniteQueryConfig<TResult>,\n): UseLiveInfiniteQueryReturnWithCollection<TResult, TKey, TUtils>\n\nexport function useLiveInfiniteQuery<\n TContext extends Context & NonSingleResult,\n>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveInfiniteQueryReturn<TContext>\n\nexport function useLiveInfiniteQuery<\n TContext extends Context & NonSingleResult,\n>(\n queryFnOrCollection: unknown,\n config: InfiniteQueryOptions,\n deps: Array<MaybeRefOrGetter<unknown>> = [],\n): UseLiveInfiniteQueryReturn<TContext> {\n if (`getNextPageParam` in config) {\n throw new Error(\n `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.`,\n )\n }\n let validatedCollection: InternalCollection | null = null\n let previousController: PreviousController | null = null\n let previousInput: ReturnType<\n typeof resolveLiveQueryWindowInput<TContext>\n > | null = null\n let previousDependencies: Array<unknown> | null = null\n let previousPageSize: number | null = null\n let previousInitialPageParam: number | null = null\n\n const controller = computed(() => {\n const dependencies = deps.map((dependency) => toValue(dependency))\n\n const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize)\n const initialPageParam = config.initialPageParam ?? 0\n const unwrappedInput =\n typeof queryFnOrCollection === `function`\n ? queryFnOrCollection\n : toValue(queryFnOrCollection)\n const input = resolveLiveQueryWindowInput<TContext>(unwrappedInput)\n const dependencyComparison = compareLiveQueryWindowDependencies(\n previousDependencies,\n dependencies,\n )\n const dependenciesChanged = dependencyComparison.changed\n const dependenciesStructurallyEqual = dependencyComparison.structurallyEqual\n const pageShapeChanged =\n previousPageSize !== pageSize ||\n previousInitialPageParam !== initialPageParam\n const sameCollection =\n input.kind === `collection` &&\n previousInput?.kind === `collection` &&\n previousInput.collection === input.collection\n const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({\n hasPreviousController: previousController !== null,\n previousInputKind: previousInput?.kind,\n inputKind: input.kind,\n sameCollection,\n dependenciesChanged,\n dependenciesStructurallyEqual,\n pageShapeChanged,\n })\n const previousPageCount = previousController\n ? Math.max(1, previousController.getSnapshot().pages.length)\n : 1\n const initialPageCount = canPreservePageCount ? previousPageCount : 1\n\n previousInput = input\n previousDependencies = [...dependencies]\n previousPageSize = pageSize\n previousInitialPageParam = initialPageParam\n\n if (input.kind === `collection`) {\n const collection = input.collection\n const warning = getLiveQueryWindowCollectionWarning(\n collection,\n pageSize + 1,\n )\n\n if (validatedCollection !== collection) {\n validatedCollection = collection\n if (warning) console.warn(warning)\n }\n\n const currentController = createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n })\n previousController = currentController\n return currentController\n }\n\n const collection = createLiveQueryCollection({\n query: input.query.limit(pageSize + 1).offset(0),\n startSync: false,\n gcTime: DEFAULT_GC_TIME_MS,\n })\n assertLiveQueryWindowManyResult(collection)\n const currentController = createLiveQueryWindowController(collection, {\n pageSize,\n initialPageParam,\n initialPageCount,\n })\n previousController = currentController\n return currentController\n })\n\n const snapshot = shallowRef(controller.value.getSnapshot())\n\n watchEffect(\n (onInvalidate) => {\n const currentController = controller.value\n const updateSnapshot = () => {\n snapshot.value = currentController.getSnapshot()\n }\n\n updateSnapshot()\n const unsubscribe = currentController.subscribe(updateSnapshot)\n updateSnapshot()\n\n onInvalidate(() => {\n unsubscribe()\n currentController.dispose()\n })\n },\n { flush: `sync` },\n )\n\n return {\n state: computed(\n () => snapshot.value.state as Map<string | number, GetResult<TContext>>,\n ),\n data: computed(() => snapshot.value.data as InferResultType<TContext>),\n collection: computed(\n () =>\n snapshot.value.collection as Collection<\n GetResult<TContext>,\n string | number,\n UtilsRecord\n >,\n ),\n status: computed(() => snapshot.value.status as CollectionStatus),\n isLoading: computed(() => snapshot.value.isLoading),\n isReady: computed(() => snapshot.value.isReady),\n persistedStatus: computed(() => snapshot.value.persistedStatus),\n isPersistedReady: computed(() => snapshot.value.isPersistedReady),\n persistedError: computed(() => snapshot.value.persistedError),\n isIdle: computed(() => snapshot.value.isIdle),\n isError: computed(() => snapshot.value.isError),\n isCleanedUp: computed(() => snapshot.value.isCleanedUp),\n pages: computed(\n () =>\n snapshot.value.pages as Array<Array<InferResultType<TContext>[number]>>,\n ),\n pageParams: computed(() => snapshot.value.pageParams as Array<number>),\n fetchNextPage: () => fetchNextLiveQueryWindowPage(controller.value),\n hasNextPage: computed(() => snapshot.value.hasNextPage),\n isFetchingNextPage: computed(() => snapshot.value.isFetchingNextPage),\n error: computed(() => snapshot.value.error),\n }\n}\n"],"names":["collection","currentController"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, LiveQueryCollectionConfig, NonSingleResult, QueryBuilder, SingleResult } from '@tanstack/db';
1
+ import { Collection, CollectionStatus, Context, GetResult, InferResultType, InitialQueryBuilder, LiveQueryCollectionConfig, LiveQueryPersistedStatus, NonSingleResult, QueryBuilder, SingleResult } from '@tanstack/db';
2
2
  import { ComputedRef, MaybeRefOrGetter } from 'vue';
3
3
  /**
4
4
  * Return type for useLiveQuery hook
@@ -19,10 +19,19 @@ export interface UseLiveQueryReturn<TContext extends Context> {
19
19
  status: ComputedRef<CollectionStatus>;
20
20
  isLoading: ComputedRef<boolean>;
21
21
  isReady: ComputedRef<boolean>;
22
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
23
+ isPersistedReady: ComputedRef<boolean>;
24
+ persistedError: ComputedRef<unknown | undefined>;
22
25
  isIdle: ComputedRef<boolean>;
23
26
  isError: ComputedRef<boolean>;
24
27
  isCleanedUp: ComputedRef<boolean>;
25
28
  }
29
+ type InferConditionalResultType<TContext extends Context> = TContext extends SingleResult ? InferResultType<TContext> | [] : InferResultType<TContext>;
30
+ export type ConditionalUseLiveQueryReturn<TContext extends Context> = Omit<UseLiveQueryReturn<TContext>, `data` | `collection` | `status`> & {
31
+ data: ComputedRef<InferConditionalResultType<TContext>>;
32
+ collection: ComputedRef<Collection<GetResult<TContext>, string | number, {}> | null>;
33
+ status: ComputedRef<CollectionStatus | `disabled`>;
34
+ };
26
35
  export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends string | number, TUtils extends Record<string, any>> {
27
36
  state: ComputedRef<Map<TKey, T>>;
28
37
  data: ComputedRef<Array<T>>;
@@ -30,6 +39,9 @@ export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends
30
39
  status: ComputedRef<CollectionStatus>;
31
40
  isLoading: ComputedRef<boolean>;
32
41
  isReady: ComputedRef<boolean>;
42
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
43
+ isPersistedReady: ComputedRef<boolean>;
44
+ persistedError: ComputedRef<unknown | undefined>;
33
45
  isIdle: ComputedRef<boolean>;
34
46
  isError: ComputedRef<boolean>;
35
47
  isCleanedUp: ComputedRef<boolean>;
@@ -41,6 +53,9 @@ export interface UseLiveQueryReturnWithSingleResultCollection<T extends object,
41
53
  status: ComputedRef<CollectionStatus>;
42
54
  isLoading: ComputedRef<boolean>;
43
55
  isReady: ComputedRef<boolean>;
56
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>;
57
+ isPersistedReady: ComputedRef<boolean>;
58
+ persistedError: ComputedRef<unknown | undefined>;
44
59
  isIdle: ComputedRef<boolean>;
45
60
  isError: ComputedRef<boolean>;
46
61
  isCleanedUp: ComputedRef<boolean>;
@@ -95,7 +110,7 @@ export interface UseLiveQueryReturnWithSingleResultCollection<T extends object,
95
110
  * // </ul>
96
111
  */
97
112
  export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<TContext>;
98
- export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<TContext>;
113
+ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null, deps?: Array<MaybeRefOrGetter<unknown>>): ConditionalUseLiveQueryReturn<TContext>;
99
114
  /**
100
115
  * Create a live query using configuration object
101
116
  * @param config - Configuration object with query and options
@@ -170,3 +185,4 @@ export declare function useLiveQuery<TContext extends Context>(config: LiveQuery
170
185
  */
171
186
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils> & NonSingleResult>): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils>;
172
187
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils> & SingleResult>): UseLiveQueryReturnWithSingleResultCollection<TResult, TKey, TUtils>;
188
+ export {};
@@ -1,4 +1,4 @@
1
- import { computed, toValue, reactive, ref, watchEffect, getCurrentInstance, onUnmounted } from "vue";
1
+ import { computed, toValue, reactive, ref, shallowRef, watchEffect, getCurrentInstance, onUnmounted } from "vue";
2
2
  import { isCollection, createLiveQueryCollection, isSingleResultCollection, createLiveQueryObserver } from "@tanstack/db";
3
3
  function useLiveQuery(configOrQueryOrCollection, deps = []) {
4
4
  const collection = computed(() => {
@@ -33,10 +33,11 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
33
33
  }
34
34
  deps.forEach((dep) => toValue(dep));
35
35
  if (typeof unwrappedParam === `function`) {
36
+ const disabledQuery = /* @__PURE__ */ Symbol();
36
37
  const wrappedQuery = (q) => {
37
38
  const result = unwrappedParam(q);
38
39
  if (result === void 0 || result === null) {
39
- throw new Error(`__DISABLED_QUERY__`);
40
+ throw disabledQuery;
40
41
  }
41
42
  return result;
42
43
  };
@@ -46,7 +47,7 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
46
47
  startSync: true
47
48
  });
48
49
  } catch (error) {
49
- if (error instanceof Error && error.message === `__DISABLED_QUERY__`) {
50
+ if (error === disabledQuery) {
50
51
  return null;
51
52
  }
52
53
  throw error;
@@ -59,25 +60,26 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
59
60
  }
60
61
  });
61
62
  const state = reactive(/* @__PURE__ */ new Map());
62
- const internalData = reactive([]);
63
+ const internalData = ref([]);
63
64
  const data = computed(() => {
64
65
  const currentCollection = collection.value;
65
66
  if (!currentCollection) {
66
- return internalData;
67
+ return internalData.value;
67
68
  }
68
- return isSingleResultCollection(currentCollection) ? internalData[0] : internalData;
69
+ return isSingleResultCollection(currentCollection) ? internalData.value[0] : internalData.value;
69
70
  });
70
71
  const status = ref(
71
72
  collection.value ? collection.value.status : `disabled`
72
73
  );
73
- const syncDataFromCollection = (currentCollection) => {
74
- internalData.length = 0;
75
- internalData.push(...Array.from(currentCollection.values()));
76
- };
74
+ const persistedStatus = ref(`unavailable`);
75
+ const persistedError = shallowRef(void 0);
77
76
  let currentObserver = null;
78
77
  const syncFromObserver = (observer, currentCollection) => {
79
- status.value = observer.getSnapshot().status;
80
- syncDataFromCollection(currentCollection);
78
+ const snapshot = observer.getSnapshot();
79
+ status.value = snapshot.status;
80
+ persistedStatus.value = snapshot.persistedStatus;
81
+ persistedError.value = snapshot.persistedError;
82
+ internalData.value = Array.from(currentCollection.values());
81
83
  };
82
84
  watchEffect((onInvalidate) => {
83
85
  const currentCollection = collection.value;
@@ -85,8 +87,10 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
85
87
  currentObserver = null;
86
88
  if (!currentCollection) {
87
89
  status.value = `disabled`;
90
+ persistedStatus.value = `unavailable`;
91
+ persistedError.value = void 0;
88
92
  state.clear();
89
- internalData.length = 0;
93
+ internalData.value = [];
90
94
  return;
91
95
  }
92
96
  const observer = createLiveQueryObserver(currentCollection);
@@ -135,6 +139,9 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
135
139
  isReady: computed(
136
140
  () => status.value === `ready` || status.value === `disabled`
137
141
  ),
142
+ persistedStatus: computed(() => persistedStatus.value),
143
+ isPersistedReady: computed(() => persistedStatus.value === `ready`),
144
+ persistedError: computed(() => persistedError.value),
138
145
  isIdle: computed(() => status.value === `idle`),
139
146
  isError: computed(() => status.value === `error`),
140
147
  isCleanedUp: computed(() => status.value === `cleaned-up`)
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n computed,\n getCurrentInstance,\n onUnmounted,\n reactive,\n ref,\n toValue,\n watchEffect,\n} from 'vue'\nimport {\n createLiveQueryCollection,\n createLiveQueryObserver,\n isCollection,\n isSingleResultCollection,\n} from '@tanstack/db'\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\nimport type { ComputedRef, MaybeRefOrGetter } from 'vue'\n\n/**\n * Return type for useLiveQuery hook\n * @property state - Reactive Map of query results (key → item)\n * @property data - Reactive array of query results in order, or single result for findOne queries\n * @property collection - The underlying query collection instance\n * @property status - Current query status\n * @property isLoading - True while initial query data is loading\n * @property isReady - True when query has received first data and is ready\n * @property isIdle - True when query hasn't started yet\n * @property isError - True when query encountered an error\n * @property isCleanedUp - True when query has been cleaned up\n */\nexport interface UseLiveQueryReturn<TContext extends Context> {\n state: ComputedRef<Map<string | number, GetResult<TContext>>>\n data: ComputedRef<InferResultType<TContext>>\n collection: ComputedRef<Collection<GetResult<TContext>, string | number, {}>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\nexport interface UseLiveQueryReturnWithCollection<\n T extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> {\n state: ComputedRef<Map<TKey, T>>\n data: ComputedRef<Array<T>>\n collection: ComputedRef<Collection<T, TKey, TUtils>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\nexport interface UseLiveQueryReturnWithSingleResultCollection<\n T extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> {\n state: ComputedRef<Map<TKey, T>>\n data: ComputedRef<T | undefined>\n collection: ComputedRef<Collection<T, TKey, TUtils> & SingleResult>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\n/**\n * Create a live query using a query function\n * @param queryFn - Query function that defines what data to fetch\n * @param deps - Array of reactive dependencies that trigger query re-execution when changed\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Basic query with object syntax\n * const { data, isLoading } = useLiveQuery((q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * )\n *\n * @example\n * // With reactive dependencies\n * const minPriority = ref(5)\n * const { data, state } = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority.value)),\n * [minPriority] // Re-run when minPriority changes\n * )\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery((q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * )\n *\n * @example\n * // Handle loading and error states in template\n * const { data, isLoading, isError, status } = useLiveQuery((q) =>\n * q.from({ todos: todoCollection })\n * )\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Error: {{ status }}</div>\n * // <ul v-else>\n * // <li v-for=\"todo in data\" :key=\"todo.id\">{{ todo.text }}</li>\n * // </ul>\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n// Overload 1b: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - Array of reactive dependencies that trigger query re-execution when changed\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Basic config object usage\n * const { data, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * })\n *\n * @example\n * // With reactive dependencies\n * const filter = ref('active')\n * const { data, isReady } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.status, filter.value))\n * }, [filter])\n *\n * @example\n * // Handle all states uniformly\n * const { data, isLoading, isReady, isError } = useLiveQuery({\n * query: (q) => q.from({ items: itemCollection })\n * })\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Something went wrong</div>\n * // <div v-else-if=\"!isReady\">Preparing...</div>\n * // <div v-else>{{ data.length }} items loaded</div>\n */\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n/**\n * Subscribe to an existing query collection (can be reactive)\n * @param liveQueryCollection - Pre-created query collection to subscribe to (can be a ref)\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Using pre-created query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const { data, collection } = useLiveQuery(myLiveQuery)\n *\n * @example\n * // Reactive query collection reference\n * const selectedQuery = ref(todosQuery)\n * const { data, collection } = useLiveQuery(selectedQuery)\n *\n * // Switch queries reactively\n * selectedQuery.value = archiveQuery\n *\n * @example\n * // Access query collection methods directly\n * const { data, collection, isReady } = useLiveQuery(existingQuery)\n *\n * // Use underlying collection for mutations\n * const handleToggle = (id) => {\n * collection.value.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const { data, isLoading, isError } = useLiveQuery(sharedQuery)\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Error loading data</div>\n * // <div v-else>\n * // <Item v-for=\"item in data\" :key=\"item.id\" v-bind=\"item\" />\n * // </div>\n */\n// Overload 3: Accept pre-created live query collection (can be reactive) - non-single result\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & NonSingleResult\n >,\n): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils>\n\n// Overload 4: Accept pre-created live query collection with singleResult: true\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & SingleResult\n >,\n): UseLiveQueryReturnWithSingleResultCollection<TResult, TKey, TUtils>\n\n// Implementation\nexport function useLiveQuery(\n configOrQueryOrCollection: any,\n deps: Array<MaybeRefOrGetter<unknown>> = [],\n): UseLiveQueryReturn<any> | UseLiveQueryReturnWithCollection<any, any, any> {\n const collection = computed(() => {\n // First check if the original parameter might be a ref/getter\n // by seeing if toValue returns something different than the original\n // NOTE: Don't call toValue on functions - toValue treats functions as getters and calls them!\n let unwrappedParam = configOrQueryOrCollection\n if (typeof configOrQueryOrCollection !== `function`) {\n try {\n const potentiallyUnwrapped = toValue(configOrQueryOrCollection)\n if (potentiallyUnwrapped !== configOrQueryOrCollection) {\n unwrappedParam = potentiallyUnwrapped\n }\n } catch {\n // If toValue fails, use original parameter\n unwrappedParam = configOrQueryOrCollection\n }\n }\n\n // Check if it's already a collection\n const inputIsCollection = isCollection(unwrappedParam)\n\n if (inputIsCollection) {\n // Warn when passing a collection directly with on-demand sync mode\n // In on-demand mode, data is only loaded when queries with predicates request it\n // Passing the collection directly doesn't provide any predicates, so no data loads\n const syncMode = (unwrappedParam as { config?: { syncMode?: string } })\n .config?.syncMode\n if (syncMode === `on-demand`) {\n console.warn(\n `[useLiveQuery] Warning: Passing a collection with syncMode \"on-demand\" directly to useLiveQuery ` +\n `will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.\\n\\n` +\n `Instead, use a query builder function:\\n` +\n ` const { data } = useLiveQuery((q) => q.from({ c: myCollection }).select(({ c }) => c))\\n\\n` +\n `Or switch to syncMode \"eager\" if you want all data to sync automatically.`,\n )\n }\n // It's already a collection, ensure sync is started for Vue hooks\n // Only start sync if the collection is in idle state\n if (unwrappedParam.status === `idle`) {\n unwrappedParam.startSyncImmediate()\n }\n return unwrappedParam\n }\n\n // Reference deps to make computed reactive to them\n deps.forEach((dep) => toValue(dep))\n\n // Ensure we always start sync for Vue hooks\n if (typeof unwrappedParam === `function`) {\n // To avoid calling the query function twice, we wrap it to handle null/undefined returns\n // The wrapper will be called once by createLiveQueryCollection\n const wrappedQuery = (q: InitialQueryBuilder) => {\n const result = unwrappedParam(q)\n // If the query function returns null/undefined, throw a special error\n // that we'll catch to return null collection\n if (result === undefined || result === null) {\n throw new Error(`__DISABLED_QUERY__`)\n }\n return result\n }\n\n try {\n return createLiveQueryCollection({\n query: wrappedQuery,\n startSync: true,\n })\n } catch (error) {\n // Check if this is our special disabled query marker\n if (error instanceof Error && error.message === `__DISABLED_QUERY__`) {\n return null\n }\n // Re-throw other errors\n throw error\n }\n } else {\n return createLiveQueryCollection({\n ...unwrappedParam,\n startSync: true,\n })\n }\n })\n\n // Reactive state that gets updated granularly through change events\n const state = reactive(new Map<string | number, any>())\n\n // Reactive data array that maintains sorted order\n const internalData = reactive<Array<any>>([])\n\n // Computed wrapper for the data to match expected return type\n // Returns single item for singleResult collections, array otherwise\n const data = computed(() => {\n const currentCollection = collection.value\n if (!currentCollection) {\n return internalData\n }\n return isSingleResultCollection(currentCollection)\n ? internalData[0]\n : internalData\n })\n\n // Track collection status reactively\n const status = ref(\n collection.value ? collection.value.status : (`disabled` as const),\n )\n\n // Helper to sync data array from collection in correct order\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>,\n ) => {\n internalData.length = 0\n internalData.push(...Array.from(currentCollection.values()))\n }\n\n // The shared observer owns subscription, the ready-race, and status; Vue\n // materializes into its own reactive map (granular) + ordered array.\n let currentObserver: LiveQueryObserver<any, any> | null = null\n\n const syncFromObserver = (\n observer: LiveQueryObserver<any, any>,\n currentCollection: Collection<any, any, any>,\n ) => {\n status.value = observer.getSnapshot().status as CollectionStatus\n syncDataFromCollection(currentCollection)\n }\n\n // Watch for collection changes and subscribe to updates\n watchEffect((onInvalidate) => {\n const currentCollection = collection.value\n\n // Tear down any previous observer.\n currentObserver?.dispose()\n currentObserver = null\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n status.value = `disabled` as const\n state.clear()\n internalData.length = 0\n return\n }\n\n const observer = createLiveQueryObserver(currentCollection)\n currentObserver = observer\n\n // Initial rows arrive as the observer's first delta (includeInitialState);\n // apply them and every subsequent delta granularly to the reactive map.\n state.clear()\n\n const unsubscribe = observer.subscribe(\n (changes: Array<ChangeMessage<any>> | undefined) => {\n if (changes) {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n case `update`:\n state.set(change.key, change.value)\n break\n case `delete`:\n state.delete(change.key)\n break\n }\n }\n } else {\n // Cleanup and other status-only publications carry no row deltas.\n // Rebuild the keyed view so it cannot diverge from ordered data.\n state.clear()\n for (const [key, value] of observer.getSnapshot().state ?? []) {\n state.set(key, value)\n }\n }\n syncFromObserver(observer, currentCollection)\n },\n )\n syncFromObserver(observer, currentCollection)\n\n // Cleanup when effect is invalidated\n onInvalidate(() => {\n unsubscribe()\n observer.dispose()\n currentObserver = null\n })\n })\n\n // Cleanup on unmount (only if we're in a component context)\n const instance = getCurrentInstance()\n if (instance) {\n onUnmounted(() => currentObserver?.dispose())\n }\n\n return {\n state: computed(() => state),\n data,\n collection: computed(() => collection.value),\n status: computed(() => status.value),\n isLoading: computed(() => status.value === `loading`),\n isReady: computed(\n () => status.value === `ready` || status.value === `disabled`,\n ),\n isIdle: computed(() => status.value === `idle`),\n isError: computed(() => status.value === `error`),\n isCleanedUp: computed(() => status.value === `cleaned-up`),\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n computed,\n getCurrentInstance,\n onUnmounted,\n reactive,\n ref,\n shallowRef,\n toValue,\n watchEffect,\n} from 'vue'\nimport {\n createLiveQueryCollection,\n createLiveQueryObserver,\n isCollection,\n isSingleResultCollection,\n} from '@tanstack/db'\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n LiveQueryObserver,\n LiveQueryPersistedStatus,\n NonSingleResult,\n QueryBuilder,\n SingleResult,\n} from '@tanstack/db'\nimport type { ComputedRef, MaybeRefOrGetter } from 'vue'\n\n/**\n * Return type for useLiveQuery hook\n * @property state - Reactive Map of query results (key → item)\n * @property data - Reactive array of query results in order, or single result for findOne queries\n * @property collection - The underlying query collection instance\n * @property status - Current query status\n * @property isLoading - True while initial query data is loading\n * @property isReady - True when query has received first data and is ready\n * @property isIdle - True when query hasn't started yet\n * @property isError - True when query encountered an error\n * @property isCleanedUp - True when query has been cleaned up\n */\nexport interface UseLiveQueryReturn<TContext extends Context> {\n state: ComputedRef<Map<string | number, GetResult<TContext>>>\n data: ComputedRef<InferResultType<TContext>>\n collection: ComputedRef<Collection<GetResult<TContext>, string | number, {}>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\ntype InferConditionalResultType<TContext extends Context> =\n TContext extends SingleResult\n ? InferResultType<TContext> | []\n : InferResultType<TContext>\n\nexport type ConditionalUseLiveQueryReturn<TContext extends Context> = Omit<\n UseLiveQueryReturn<TContext>,\n `data` | `collection` | `status`\n> & {\n data: ComputedRef<InferConditionalResultType<TContext>>\n collection: ComputedRef<Collection<\n GetResult<TContext>,\n string | number,\n {}\n > | null>\n status: ComputedRef<CollectionStatus | `disabled`>\n}\n\nexport interface UseLiveQueryReturnWithCollection<\n T extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> {\n state: ComputedRef<Map<TKey, T>>\n data: ComputedRef<Array<T>>\n collection: ComputedRef<Collection<T, TKey, TUtils>>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\nexport interface UseLiveQueryReturnWithSingleResultCollection<\n T extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n> {\n state: ComputedRef<Map<TKey, T>>\n data: ComputedRef<T | undefined>\n collection: ComputedRef<Collection<T, TKey, TUtils> & SingleResult>\n status: ComputedRef<CollectionStatus>\n isLoading: ComputedRef<boolean>\n isReady: ComputedRef<boolean>\n persistedStatus: ComputedRef<LiveQueryPersistedStatus>\n isPersistedReady: ComputedRef<boolean>\n persistedError: ComputedRef<unknown | undefined>\n isIdle: ComputedRef<boolean>\n isError: ComputedRef<boolean>\n isCleanedUp: ComputedRef<boolean>\n}\n\n/**\n * Create a live query using a query function\n * @param queryFn - Query function that defines what data to fetch\n * @param deps - Array of reactive dependencies that trigger query re-execution when changed\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Basic query with object syntax\n * const { data, isLoading } = useLiveQuery((q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * )\n *\n * @example\n * // With reactive dependencies\n * const minPriority = ref(5)\n * const { data, state } = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority.value)),\n * [minPriority] // Re-run when minPriority changes\n * )\n *\n * @example\n * // Join pattern\n * const { data } = useLiveQuery((q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * )\n *\n * @example\n * // Handle loading and error states in template\n * const { data, isLoading, isError, status } = useLiveQuery((q) =>\n * q.from({ todos: todoCollection })\n * )\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Error: {{ status }}</div>\n * // <ul v-else>\n * // <li v-for=\"todo in data\" :key=\"todo.id\">{{ todo.text }}</li>\n * // </ul>\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n// Overload 1b: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): ConditionalUseLiveQueryReturn<TContext>\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @param deps - Array of reactive dependencies that trigger query re-execution when changed\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Basic config object usage\n * const { data, status } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * })\n *\n * @example\n * // With reactive dependencies\n * const filter = ref('active')\n * const { data, isReady } = useLiveQuery({\n * query: (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.status, filter.value))\n * }, [filter])\n *\n * @example\n * // Handle all states uniformly\n * const { data, isLoading, isReady, isError } = useLiveQuery({\n * query: (q) => q.from({ items: itemCollection })\n * })\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Something went wrong</div>\n * // <div v-else-if=\"!isReady\">Preparing...</div>\n * // <div v-else>{{ data.length }} items loaded</div>\n */\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>,\n): UseLiveQueryReturn<TContext>\n\n/**\n * Subscribe to an existing query collection (can be reactive)\n * @param liveQueryCollection - Pre-created query collection to subscribe to (can be a ref)\n * @returns Reactive object with query data, state, and status information\n * @example\n * // Using pre-created query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const { data, collection } = useLiveQuery(myLiveQuery)\n *\n * @example\n * // Reactive query collection reference\n * const selectedQuery = ref(todosQuery)\n * const { data, collection } = useLiveQuery(selectedQuery)\n *\n * // Switch queries reactively\n * selectedQuery.value = archiveQuery\n *\n * @example\n * // Access query collection methods directly\n * const { data, collection, isReady } = useLiveQuery(existingQuery)\n *\n * // Use underlying collection for mutations\n * const handleToggle = (id) => {\n * collection.value.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const { data, isLoading, isError } = useLiveQuery(sharedQuery)\n *\n * // In template:\n * // <div v-if=\"isLoading\">Loading...</div>\n * // <div v-else-if=\"isError\">Error loading data</div>\n * // <div v-else>\n * // <Item v-for=\"item in data\" :key=\"item.id\" v-bind=\"item\" />\n * // </div>\n */\n// Overload 3: Accept pre-created live query collection (can be reactive) - non-single result\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & NonSingleResult\n >,\n): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils>\n\n// Overload 4: Accept pre-created live query collection with singleResult: true\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<\n Collection<TResult, TKey, TUtils> & SingleResult\n >,\n): UseLiveQueryReturnWithSingleResultCollection<TResult, TKey, TUtils>\n\n// Implementation\nexport function useLiveQuery(\n configOrQueryOrCollection: any,\n deps: Array<MaybeRefOrGetter<unknown>> = [],\n): UseLiveQueryReturn<any> | UseLiveQueryReturnWithCollection<any, any, any> {\n const collection = computed(() => {\n // First check if the original parameter might be a ref/getter\n // by seeing if toValue returns something different than the original\n // NOTE: Don't call toValue on functions - toValue treats functions as getters and calls them!\n let unwrappedParam = configOrQueryOrCollection\n if (typeof configOrQueryOrCollection !== `function`) {\n try {\n const potentiallyUnwrapped = toValue(configOrQueryOrCollection)\n if (potentiallyUnwrapped !== configOrQueryOrCollection) {\n unwrappedParam = potentiallyUnwrapped\n }\n } catch {\n // If toValue fails, use original parameter\n unwrappedParam = configOrQueryOrCollection\n }\n }\n\n // Check if it's already a collection\n const inputIsCollection = isCollection(unwrappedParam)\n\n if (inputIsCollection) {\n // Warn when passing a collection directly with on-demand sync mode\n // In on-demand mode, data is only loaded when queries with predicates request it\n // Passing the collection directly doesn't provide any predicates, so no data loads\n const syncMode = (unwrappedParam as { config?: { syncMode?: string } })\n .config?.syncMode\n if (syncMode === `on-demand`) {\n console.warn(\n `[useLiveQuery] Warning: Passing a collection with syncMode \"on-demand\" directly to useLiveQuery ` +\n `will not load any data. In on-demand mode, data is only loaded when queries with predicates request it.\\n\\n` +\n `Instead, use a query builder function:\\n` +\n ` const { data } = useLiveQuery((q) => q.from({ c: myCollection }).select(({ c }) => c))\\n\\n` +\n `Or switch to syncMode \"eager\" if you want all data to sync automatically.`,\n )\n }\n // It's already a collection, ensure sync is started for Vue hooks\n // Only start sync if the collection is in idle state\n if (unwrappedParam.status === `idle`) {\n unwrappedParam.startSyncImmediate()\n }\n return unwrappedParam\n }\n\n // Reference deps to make computed reactive to them\n deps.forEach((dep) => toValue(dep))\n\n // Ensure we always start sync for Vue hooks\n if (typeof unwrappedParam === `function`) {\n // To avoid calling the query function twice, we wrap it to handle null/undefined returns\n // The wrapper will be called once by createLiveQueryCollection\n const disabledQuery = Symbol()\n const wrappedQuery = (q: InitialQueryBuilder) => {\n const result = unwrappedParam(q)\n if (result === undefined || result === null) {\n throw disabledQuery\n }\n return result\n }\n\n try {\n return createLiveQueryCollection({\n query: wrappedQuery,\n startSync: true,\n })\n } catch (error) {\n if (error === disabledQuery) {\n return null\n }\n // Re-throw other errors\n throw error\n }\n } else {\n return createLiveQueryCollection({\n ...unwrappedParam,\n startSync: true,\n })\n }\n })\n\n // Reactive state that gets updated granularly through change events\n const state = reactive(new Map<string | number, any>())\n\n // Reactive data array that maintains sorted order\n const internalData = ref<Array<any>>([])\n\n // Computed wrapper for the data to match expected return type\n // Returns single item for singleResult collections, array otherwise\n const data = computed(() => {\n const currentCollection = collection.value\n if (!currentCollection) {\n return internalData.value\n }\n return isSingleResultCollection(currentCollection)\n ? internalData.value[0]\n : internalData.value\n })\n\n // Track collection status reactively\n const status = ref(\n collection.value ? collection.value.status : (`disabled` as const),\n )\n const persistedStatus = ref<LiveQueryPersistedStatus>(`unavailable`)\n const persistedError = shallowRef<unknown>(undefined)\n\n // The shared observer owns subscription, the ready-race, and status; Vue\n // materializes into its own reactive map (granular) + ordered array.\n let currentObserver: LiveQueryObserver<any, any> | null = null\n\n const syncFromObserver = (\n observer: LiveQueryObserver<any, any>,\n currentCollection: Collection<any, any, any>,\n ) => {\n const snapshot = observer.getSnapshot()\n status.value = snapshot.status as CollectionStatus\n persistedStatus.value = snapshot.persistedStatus\n persistedError.value = snapshot.persistedError\n internalData.value = Array.from(currentCollection.values())\n }\n\n // Watch for collection changes and subscribe to updates\n watchEffect((onInvalidate) => {\n const currentCollection = collection.value\n\n // Tear down any previous observer.\n currentObserver?.dispose()\n currentObserver = null\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n status.value = `disabled` as const\n persistedStatus.value = `unavailable`\n persistedError.value = undefined\n state.clear()\n internalData.value = []\n return\n }\n\n const observer = createLiveQueryObserver(currentCollection)\n currentObserver = observer\n\n // Initial rows arrive as the observer's first delta (includeInitialState);\n // apply them and every subsequent delta granularly to the reactive map.\n state.clear()\n\n const unsubscribe = observer.subscribe(\n (changes: Array<ChangeMessage<any>> | undefined) => {\n if (changes) {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n case `update`:\n state.set(change.key, change.value)\n break\n case `delete`:\n state.delete(change.key)\n break\n }\n }\n } else {\n // Cleanup and other status-only publications carry no row deltas.\n // Rebuild the keyed view so it cannot diverge from ordered data.\n state.clear()\n for (const [key, value] of observer.getSnapshot().state ?? []) {\n state.set(key, value)\n }\n }\n syncFromObserver(observer, currentCollection)\n },\n )\n syncFromObserver(observer, currentCollection)\n\n // Cleanup when effect is invalidated\n onInvalidate(() => {\n unsubscribe()\n observer.dispose()\n currentObserver = null\n })\n })\n\n // Cleanup on unmount (only if we're in a component context)\n const instance = getCurrentInstance()\n if (instance) {\n onUnmounted(() => currentObserver?.dispose())\n }\n\n return {\n state: computed(() => state),\n data,\n collection: computed(() => collection.value),\n status: computed(() => status.value),\n isLoading: computed(() => status.value === `loading`),\n isReady: computed(\n () => status.value === `ready` || status.value === `disabled`,\n ),\n persistedStatus: computed(() => persistedStatus.value),\n isPersistedReady: computed(() => persistedStatus.value === `ready`),\n persistedError: computed(() => persistedError.value),\n isIdle: computed(() => status.value === `idle`),\n isError: computed(() => status.value === `error`),\n isCleanedUp: computed(() => status.value === `cleaned-up`),\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/vue-db",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Vue integration for @tanstack/db",
5
5
  "author": "Kyle Mathews",
6
6
  "license": "MIT",
@@ -40,7 +40,7 @@
40
40
  "skills"
41
41
  ],
42
42
  "dependencies": {
43
- "@tanstack/db": "0.9.2"
43
+ "@tanstack/db": "0.11.1"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "vue": ">=3.3.0"
@@ -17,6 +17,7 @@ import type {
17
17
  GetResult,
18
18
  InferResultType,
19
19
  InitialQueryBuilder,
20
+ LiveQueryPersistedStatus,
20
21
  NonSingleResult,
21
22
  QueryBuilder,
22
23
  UtilsRecord,
@@ -55,6 +56,9 @@ export interface UseLiveInfiniteQueryReturn<
55
56
  status: ComputedRef<CollectionStatus>
56
57
  isLoading: ComputedRef<boolean>
57
58
  isReady: ComputedRef<boolean>
59
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>
60
+ isPersistedReady: ComputedRef<boolean>
61
+ persistedError: ComputedRef<unknown | undefined>
58
62
  isIdle: ComputedRef<boolean>
59
63
  isError: ComputedRef<boolean>
60
64
  isCleanedUp: ComputedRef<boolean>
@@ -77,6 +81,9 @@ export interface UseLiveInfiniteQueryReturnWithCollection<
77
81
  status: ComputedRef<CollectionStatus>
78
82
  isLoading: ComputedRef<boolean>
79
83
  isReady: ComputedRef<boolean>
84
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>
85
+ isPersistedReady: ComputedRef<boolean>
86
+ persistedError: ComputedRef<unknown | undefined>
80
87
  isIdle: ComputedRef<boolean>
81
88
  isError: ComputedRef<boolean>
82
89
  isCleanedUp: ComputedRef<boolean>
@@ -247,6 +254,9 @@ export function useLiveInfiniteQuery<
247
254
  status: computed(() => snapshot.value.status as CollectionStatus),
248
255
  isLoading: computed(() => snapshot.value.isLoading),
249
256
  isReady: computed(() => snapshot.value.isReady),
257
+ persistedStatus: computed(() => snapshot.value.persistedStatus),
258
+ isPersistedReady: computed(() => snapshot.value.isPersistedReady),
259
+ persistedError: computed(() => snapshot.value.persistedError),
250
260
  isIdle: computed(() => snapshot.value.isIdle),
251
261
  isError: computed(() => snapshot.value.isError),
252
262
  isCleanedUp: computed(() => snapshot.value.isCleanedUp),
@@ -4,6 +4,7 @@ import {
4
4
  onUnmounted,
5
5
  reactive,
6
6
  ref,
7
+ shallowRef,
7
8
  toValue,
8
9
  watchEffect,
9
10
  } from 'vue'
@@ -23,6 +24,7 @@ import type {
23
24
  InitialQueryBuilder,
24
25
  LiveQueryCollectionConfig,
25
26
  LiveQueryObserver,
27
+ LiveQueryPersistedStatus,
26
28
  NonSingleResult,
27
29
  QueryBuilder,
28
30
  SingleResult,
@@ -48,11 +50,32 @@ export interface UseLiveQueryReturn<TContext extends Context> {
48
50
  status: ComputedRef<CollectionStatus>
49
51
  isLoading: ComputedRef<boolean>
50
52
  isReady: ComputedRef<boolean>
53
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>
54
+ isPersistedReady: ComputedRef<boolean>
55
+ persistedError: ComputedRef<unknown | undefined>
51
56
  isIdle: ComputedRef<boolean>
52
57
  isError: ComputedRef<boolean>
53
58
  isCleanedUp: ComputedRef<boolean>
54
59
  }
55
60
 
61
+ type InferConditionalResultType<TContext extends Context> =
62
+ TContext extends SingleResult
63
+ ? InferResultType<TContext> | []
64
+ : InferResultType<TContext>
65
+
66
+ export type ConditionalUseLiveQueryReturn<TContext extends Context> = Omit<
67
+ UseLiveQueryReturn<TContext>,
68
+ `data` | `collection` | `status`
69
+ > & {
70
+ data: ComputedRef<InferConditionalResultType<TContext>>
71
+ collection: ComputedRef<Collection<
72
+ GetResult<TContext>,
73
+ string | number,
74
+ {}
75
+ > | null>
76
+ status: ComputedRef<CollectionStatus | `disabled`>
77
+ }
78
+
56
79
  export interface UseLiveQueryReturnWithCollection<
57
80
  T extends object,
58
81
  TKey extends string | number,
@@ -64,6 +87,9 @@ export interface UseLiveQueryReturnWithCollection<
64
87
  status: ComputedRef<CollectionStatus>
65
88
  isLoading: ComputedRef<boolean>
66
89
  isReady: ComputedRef<boolean>
90
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>
91
+ isPersistedReady: ComputedRef<boolean>
92
+ persistedError: ComputedRef<unknown | undefined>
67
93
  isIdle: ComputedRef<boolean>
68
94
  isError: ComputedRef<boolean>
69
95
  isCleanedUp: ComputedRef<boolean>
@@ -80,6 +106,9 @@ export interface UseLiveQueryReturnWithSingleResultCollection<
80
106
  status: ComputedRef<CollectionStatus>
81
107
  isLoading: ComputedRef<boolean>
82
108
  isReady: ComputedRef<boolean>
109
+ persistedStatus: ComputedRef<LiveQueryPersistedStatus>
110
+ isPersistedReady: ComputedRef<boolean>
111
+ persistedError: ComputedRef<unknown | undefined>
83
112
  isIdle: ComputedRef<boolean>
84
113
  isError: ComputedRef<boolean>
85
114
  isCleanedUp: ComputedRef<boolean>
@@ -146,7 +175,7 @@ export function useLiveQuery<TContext extends Context>(
146
175
  q: InitialQueryBuilder,
147
176
  ) => QueryBuilder<TContext> | undefined | null,
148
177
  deps?: Array<MaybeRefOrGetter<unknown>>,
149
- ): UseLiveQueryReturn<TContext>
178
+ ): ConditionalUseLiveQueryReturn<TContext>
150
179
 
151
180
  /**
152
181
  * Create a live query using configuration object
@@ -302,12 +331,11 @@ export function useLiveQuery(
302
331
  if (typeof unwrappedParam === `function`) {
303
332
  // To avoid calling the query function twice, we wrap it to handle null/undefined returns
304
333
  // The wrapper will be called once by createLiveQueryCollection
334
+ const disabledQuery = Symbol()
305
335
  const wrappedQuery = (q: InitialQueryBuilder) => {
306
336
  const result = unwrappedParam(q)
307
- // If the query function returns null/undefined, throw a special error
308
- // that we'll catch to return null collection
309
337
  if (result === undefined || result === null) {
310
- throw new Error(`__DISABLED_QUERY__`)
338
+ throw disabledQuery
311
339
  }
312
340
  return result
313
341
  }
@@ -318,8 +346,7 @@ export function useLiveQuery(
318
346
  startSync: true,
319
347
  })
320
348
  } catch (error) {
321
- // Check if this is our special disabled query marker
322
- if (error instanceof Error && error.message === `__DISABLED_QUERY__`) {
349
+ if (error === disabledQuery) {
323
350
  return null
324
351
  }
325
352
  // Re-throw other errors
@@ -337,32 +364,26 @@ export function useLiveQuery(
337
364
  const state = reactive(new Map<string | number, any>())
338
365
 
339
366
  // Reactive data array that maintains sorted order
340
- const internalData = reactive<Array<any>>([])
367
+ const internalData = ref<Array<any>>([])
341
368
 
342
369
  // Computed wrapper for the data to match expected return type
343
370
  // Returns single item for singleResult collections, array otherwise
344
371
  const data = computed(() => {
345
372
  const currentCollection = collection.value
346
373
  if (!currentCollection) {
347
- return internalData
374
+ return internalData.value
348
375
  }
349
376
  return isSingleResultCollection(currentCollection)
350
- ? internalData[0]
351
- : internalData
377
+ ? internalData.value[0]
378
+ : internalData.value
352
379
  })
353
380
 
354
381
  // Track collection status reactively
355
382
  const status = ref(
356
383
  collection.value ? collection.value.status : (`disabled` as const),
357
384
  )
358
-
359
- // Helper to sync data array from collection in correct order
360
- const syncDataFromCollection = (
361
- currentCollection: Collection<any, any, any>,
362
- ) => {
363
- internalData.length = 0
364
- internalData.push(...Array.from(currentCollection.values()))
365
- }
385
+ const persistedStatus = ref<LiveQueryPersistedStatus>(`unavailable`)
386
+ const persistedError = shallowRef<unknown>(undefined)
366
387
 
367
388
  // The shared observer owns subscription, the ready-race, and status; Vue
368
389
  // materializes into its own reactive map (granular) + ordered array.
@@ -372,8 +393,11 @@ export function useLiveQuery(
372
393
  observer: LiveQueryObserver<any, any>,
373
394
  currentCollection: Collection<any, any, any>,
374
395
  ) => {
375
- status.value = observer.getSnapshot().status as CollectionStatus
376
- syncDataFromCollection(currentCollection)
396
+ const snapshot = observer.getSnapshot()
397
+ status.value = snapshot.status as CollectionStatus
398
+ persistedStatus.value = snapshot.persistedStatus
399
+ persistedError.value = snapshot.persistedError
400
+ internalData.value = Array.from(currentCollection.values())
377
401
  }
378
402
 
379
403
  // Watch for collection changes and subscribe to updates
@@ -387,8 +411,10 @@ export function useLiveQuery(
387
411
  // Handle null collection (disabled query)
388
412
  if (!currentCollection) {
389
413
  status.value = `disabled` as const
414
+ persistedStatus.value = `unavailable`
415
+ persistedError.value = undefined
390
416
  state.clear()
391
- internalData.length = 0
417
+ internalData.value = []
392
418
  return
393
419
  }
394
420
 
@@ -449,6 +475,9 @@ export function useLiveQuery(
449
475
  isReady: computed(
450
476
  () => status.value === `ready` || status.value === `disabled`,
451
477
  ),
478
+ persistedStatus: computed(() => persistedStatus.value),
479
+ isPersistedReady: computed(() => persistedStatus.value === `ready`),
480
+ persistedError: computed(() => persistedError.value),
452
481
  isIdle: computed(() => status.value === `idle`),
453
482
  isError: computed(() => status.value === `error`),
454
483
  isCleanedUp: computed(() => status.value === `cleaned-up`),