@tanstack/vue-db 0.3.1 → 0.3.3
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.
|
@@ -35,25 +35,9 @@ 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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (result === void 0 || result === null) {
|
|
42
|
-
throw disabledQuery;
|
|
43
|
-
}
|
|
44
|
-
return result;
|
|
45
|
-
};
|
|
46
|
-
try {
|
|
47
|
-
return db.createLiveQueryCollection({
|
|
48
|
-
query: wrappedQuery,
|
|
49
|
-
startSync: true
|
|
50
|
-
});
|
|
51
|
-
} catch (error) {
|
|
52
|
-
if (error === disabledQuery) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
38
|
+
return db.resolveLiveQueryValue(
|
|
39
|
+
unwrappedParam(new db.BaseQueryBuilder())
|
|
40
|
+
);
|
|
57
41
|
} else {
|
|
58
42
|
return db.createLiveQueryCollection({
|
|
59
43
|
...unwrappedParam,
|
|
@@ -76,12 +60,12 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
|
|
|
76
60
|
const persistedStatus = vue.ref(`unavailable`);
|
|
77
61
|
const persistedError = vue.shallowRef(void 0);
|
|
78
62
|
let currentObserver = null;
|
|
79
|
-
const syncFromObserver = (observer
|
|
63
|
+
const syncFromObserver = (observer) => {
|
|
80
64
|
const snapshot = observer.getSnapshot();
|
|
81
65
|
status.value = snapshot.status;
|
|
82
66
|
persistedStatus.value = snapshot.persistedStatus;
|
|
83
67
|
persistedError.value = snapshot.persistedError;
|
|
84
|
-
internalData.value = Array.from(
|
|
68
|
+
internalData.value = Array.from(snapshot.state?.values() ?? []);
|
|
85
69
|
};
|
|
86
70
|
vue.watchEffect((onInvalidate) => {
|
|
87
71
|
const currentCollection = collection.value;
|
|
@@ -118,10 +102,10 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
|
|
|
118
102
|
state.set(key, value);
|
|
119
103
|
}
|
|
120
104
|
}
|
|
121
|
-
syncFromObserver(observer
|
|
105
|
+
syncFromObserver(observer);
|
|
122
106
|
}
|
|
123
107
|
);
|
|
124
|
-
syncFromObserver(observer
|
|
108
|
+
syncFromObserver(observer);
|
|
125
109
|
onInvalidate(() => {
|
|
126
110
|
unsubscribe();
|
|
127
111
|
observer.dispose();
|
|
@@ -135,7 +119,7 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
|
|
|
135
119
|
return {
|
|
136
120
|
state: vue.computed(() => state),
|
|
137
121
|
data,
|
|
138
|
-
collection: vue.computed(() => collection.value),
|
|
122
|
+
collection: vue.computed(() => db.getPublicCollection(collection.value)),
|
|
139
123
|
status: vue.computed(() => status.value),
|
|
140
124
|
isLoading: vue.computed(() => status.value === `loading`),
|
|
141
125
|
isReady: vue.computed(
|
|
@@ -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 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
|
+
{"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 BaseQueryBuilder,\n createLiveQueryCollection,\n createLiveQueryObserver,\n getPublicCollection,\n isCollection,\n isSingleResultCollection,\n resolveLiveQueryValue,\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 // A query function returning null or undefined disables the query.\n return resolveLiveQueryValue(\n unwrappedParam(new BaseQueryBuilder() as InitialQueryBuilder),\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 = (observer: LiveQueryObserver<any, any>) => {\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(snapshot.state?.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)\n },\n )\n syncFromObserver(observer)\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(() => getPublicCollection(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","resolveLiveQueryValue","BaseQueryBuilder","createLiveQueryCollection","reactive","ref","isSingleResultCollection","shallowRef","watchEffect","createLiveQueryObserver","getCurrentInstance","onUnmounted","getPublicCollection"],"mappings":"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"}
|
package/dist/esm/useLiveQuery.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computed, toValue, reactive, ref, shallowRef, watchEffect, getCurrentInstance, onUnmounted } from "vue";
|
|
2
|
-
import { isCollection, createLiveQueryCollection, isSingleResultCollection, createLiveQueryObserver } from "@tanstack/db";
|
|
2
|
+
import { isCollection, resolveLiveQueryValue, BaseQueryBuilder, createLiveQueryCollection, isSingleResultCollection, createLiveQueryObserver, getPublicCollection } from "@tanstack/db";
|
|
3
3
|
function useLiveQuery(configOrQueryOrCollection, deps = []) {
|
|
4
4
|
const collection = computed(() => {
|
|
5
5
|
let unwrappedParam = configOrQueryOrCollection;
|
|
@@ -33,25 +33,9 @@ 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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (result === void 0 || result === null) {
|
|
40
|
-
throw disabledQuery;
|
|
41
|
-
}
|
|
42
|
-
return result;
|
|
43
|
-
};
|
|
44
|
-
try {
|
|
45
|
-
return createLiveQueryCollection({
|
|
46
|
-
query: wrappedQuery,
|
|
47
|
-
startSync: true
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
if (error === disabledQuery) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
36
|
+
return resolveLiveQueryValue(
|
|
37
|
+
unwrappedParam(new BaseQueryBuilder())
|
|
38
|
+
);
|
|
55
39
|
} else {
|
|
56
40
|
return createLiveQueryCollection({
|
|
57
41
|
...unwrappedParam,
|
|
@@ -74,12 +58,12 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
|
|
|
74
58
|
const persistedStatus = ref(`unavailable`);
|
|
75
59
|
const persistedError = shallowRef(void 0);
|
|
76
60
|
let currentObserver = null;
|
|
77
|
-
const syncFromObserver = (observer
|
|
61
|
+
const syncFromObserver = (observer) => {
|
|
78
62
|
const snapshot = observer.getSnapshot();
|
|
79
63
|
status.value = snapshot.status;
|
|
80
64
|
persistedStatus.value = snapshot.persistedStatus;
|
|
81
65
|
persistedError.value = snapshot.persistedError;
|
|
82
|
-
internalData.value = Array.from(
|
|
66
|
+
internalData.value = Array.from(snapshot.state?.values() ?? []);
|
|
83
67
|
};
|
|
84
68
|
watchEffect((onInvalidate) => {
|
|
85
69
|
const currentCollection = collection.value;
|
|
@@ -116,10 +100,10 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
|
|
|
116
100
|
state.set(key, value);
|
|
117
101
|
}
|
|
118
102
|
}
|
|
119
|
-
syncFromObserver(observer
|
|
103
|
+
syncFromObserver(observer);
|
|
120
104
|
}
|
|
121
105
|
);
|
|
122
|
-
syncFromObserver(observer
|
|
106
|
+
syncFromObserver(observer);
|
|
123
107
|
onInvalidate(() => {
|
|
124
108
|
unsubscribe();
|
|
125
109
|
observer.dispose();
|
|
@@ -133,7 +117,7 @@ Or switch to syncMode "eager" if you want all data to sync automatically.`
|
|
|
133
117
|
return {
|
|
134
118
|
state: computed(() => state),
|
|
135
119
|
data,
|
|
136
|
-
collection: computed(() => collection.value),
|
|
120
|
+
collection: computed(() => getPublicCollection(collection.value)),
|
|
137
121
|
status: computed(() => status.value),
|
|
138
122
|
isLoading: computed(() => status.value === `loading`),
|
|
139
123
|
isReady: computed(
|
|
@@ -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 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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"}
|
|
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 BaseQueryBuilder,\n createLiveQueryCollection,\n createLiveQueryObserver,\n getPublicCollection,\n isCollection,\n isSingleResultCollection,\n resolveLiveQueryValue,\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 // A query function returning null or undefined disables the query.\n return resolveLiveQueryValue(\n unwrappedParam(new BaseQueryBuilder() as InitialQueryBuilder),\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 = (observer: LiveQueryObserver<any, any>) => {\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(snapshot.state?.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)\n },\n )\n syncFromObserver(observer)\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(() => getPublicCollection(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.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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.11.
|
|
43
|
+
"@tanstack/db": "0.11.3"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"vue": ">=3.3.0"
|
package/src/useLiveQuery.ts
CHANGED
|
@@ -9,10 +9,13 @@ import {
|
|
|
9
9
|
watchEffect,
|
|
10
10
|
} from 'vue'
|
|
11
11
|
import {
|
|
12
|
+
BaseQueryBuilder,
|
|
12
13
|
createLiveQueryCollection,
|
|
13
14
|
createLiveQueryObserver,
|
|
15
|
+
getPublicCollection,
|
|
14
16
|
isCollection,
|
|
15
17
|
isSingleResultCollection,
|
|
18
|
+
resolveLiveQueryValue,
|
|
16
19
|
} from '@tanstack/db'
|
|
17
20
|
import type {
|
|
18
21
|
ChangeMessage,
|
|
@@ -329,29 +332,10 @@ export function useLiveQuery(
|
|
|
329
332
|
|
|
330
333
|
// Ensure we always start sync for Vue hooks
|
|
331
334
|
if (typeof unwrappedParam === `function`) {
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const result = unwrappedParam(q)
|
|
337
|
-
if (result === undefined || result === null) {
|
|
338
|
-
throw disabledQuery
|
|
339
|
-
}
|
|
340
|
-
return result
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
return createLiveQueryCollection({
|
|
345
|
-
query: wrappedQuery,
|
|
346
|
-
startSync: true,
|
|
347
|
-
})
|
|
348
|
-
} catch (error) {
|
|
349
|
-
if (error === disabledQuery) {
|
|
350
|
-
return null
|
|
351
|
-
}
|
|
352
|
-
// Re-throw other errors
|
|
353
|
-
throw error
|
|
354
|
-
}
|
|
335
|
+
// A query function returning null or undefined disables the query.
|
|
336
|
+
return resolveLiveQueryValue(
|
|
337
|
+
unwrappedParam(new BaseQueryBuilder() as InitialQueryBuilder),
|
|
338
|
+
)
|
|
355
339
|
} else {
|
|
356
340
|
return createLiveQueryCollection({
|
|
357
341
|
...unwrappedParam,
|
|
@@ -389,15 +373,12 @@ export function useLiveQuery(
|
|
|
389
373
|
// materializes into its own reactive map (granular) + ordered array.
|
|
390
374
|
let currentObserver: LiveQueryObserver<any, any> | null = null
|
|
391
375
|
|
|
392
|
-
const syncFromObserver = (
|
|
393
|
-
observer: LiveQueryObserver<any, any>,
|
|
394
|
-
currentCollection: Collection<any, any, any>,
|
|
395
|
-
) => {
|
|
376
|
+
const syncFromObserver = (observer: LiveQueryObserver<any, any>) => {
|
|
396
377
|
const snapshot = observer.getSnapshot()
|
|
397
378
|
status.value = snapshot.status as CollectionStatus
|
|
398
379
|
persistedStatus.value = snapshot.persistedStatus
|
|
399
380
|
persistedError.value = snapshot.persistedError
|
|
400
|
-
internalData.value = Array.from(
|
|
381
|
+
internalData.value = Array.from(snapshot.state?.values() ?? [])
|
|
401
382
|
}
|
|
402
383
|
|
|
403
384
|
// Watch for collection changes and subscribe to updates
|
|
@@ -447,10 +428,10 @@ export function useLiveQuery(
|
|
|
447
428
|
state.set(key, value)
|
|
448
429
|
}
|
|
449
430
|
}
|
|
450
|
-
syncFromObserver(observer
|
|
431
|
+
syncFromObserver(observer)
|
|
451
432
|
},
|
|
452
433
|
)
|
|
453
|
-
syncFromObserver(observer
|
|
434
|
+
syncFromObserver(observer)
|
|
454
435
|
|
|
455
436
|
// Cleanup when effect is invalidated
|
|
456
437
|
onInvalidate(() => {
|
|
@@ -469,7 +450,7 @@ export function useLiveQuery(
|
|
|
469
450
|
return {
|
|
470
451
|
state: computed(() => state),
|
|
471
452
|
data,
|
|
472
|
-
collection: computed(() => collection.value),
|
|
453
|
+
collection: computed(() => getPublicCollection(collection.value)),
|
|
473
454
|
status: computed(() => status.value),
|
|
474
455
|
isLoading: computed(() => status.value === `loading`),
|
|
475
456
|
isReady: computed(
|