@tanstack/vue-db 0.0.16 → 0.0.18

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.
@@ -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 { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from \"@tanstack/db\"\nimport type { ComputedRef, MaybeRefOrGetter } from \"vue\"\n\nexport interface UseLiveQueryReturn<T extends object> {\n state: ComputedRef<Map<string | number, T>>\n data: ComputedRef<Array<T>>\n collection: ComputedRef<Collection<T, 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\n// Overload 1: Accept just the query function\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>\n): UseLiveQueryReturn<GetResult<TContext>>\n\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>\n): UseLiveQueryReturn<GetResult<TContext>>\n\n// Overload 3: Accept pre-created live query collection (can be reactive)\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils>>\n): UseLiveQueryReturnWithCollection<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 let unwrappedParam = configOrQueryOrCollection\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 // Check if it's already a collection by checking for specific collection methods\n const isCollection =\n unwrappedParam &&\n typeof unwrappedParam === `object` &&\n typeof unwrappedParam.subscribeChanges === `function` &&\n typeof unwrappedParam.startSyncImmediate === `function` &&\n typeof unwrappedParam.id === `string`\n\n if (isCollection) {\n // It's already a collection, ensure sync is started for Vue hooks\n unwrappedParam.startSyncImmediate()\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 return createLiveQueryCollection({\n query: unwrappedParam,\n startSync: true,\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 const data = computed(() => internalData)\n\n // Track collection status reactively\n const status = ref(collection.value.status)\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 // Track current unsubscribe function\n let currentUnsubscribe: (() => void) | null = null\n\n // Watch for collection changes and subscribe to updates\n watchEffect((onInvalidate) => {\n const currentCollection = collection.value\n\n // Update status ref whenever the effect runs\n status.value = currentCollection.status\n\n // Clean up previous subscription\n if (currentUnsubscribe) {\n currentUnsubscribe()\n }\n\n // Initialize state with current collection data\n state.clear()\n for (const [key, value] of currentCollection.entries()) {\n state.set(key, value)\n }\n\n // Initialize data array in correct order\n syncDataFromCollection(currentCollection)\n\n // Subscribe to collection changes with granular updates\n currentUnsubscribe = currentCollection.subscribeChanges(\n (changes: Array<ChangeMessage<any>>) => {\n // Apply each change individually to the reactive state\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\n // Update the data array to maintain sorted order\n syncDataFromCollection(currentCollection)\n // Update status ref on every change\n status.value = currentCollection.status\n }\n )\n\n // Preload collection data if not already started\n if (currentCollection.status === `idle`) {\n currentCollection.preload().catch(console.error)\n }\n\n // Cleanup when effect is invalidated\n onInvalidate(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n })\n })\n\n // Cleanup on unmount (only if we're in a component context)\n const instance = getCurrentInstance()\n if (instance) {\n onUnmounted(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n }\n })\n }\n\n return {\n state: computed(() => state),\n data,\n collection: computed(() => collection.value),\n status: computed(() => status.value),\n isLoading: computed(\n () => status.value === `loading` || status.value === `initialCommit`\n ),\n isReady: computed(() => status.value === `ready`),\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","createLiveQueryCollection","reactive","ref","watchEffect","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 toValue,\n watchEffect,\n} from \"vue\"\nimport { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\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\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<T extends object> {\n state: ComputedRef<Map<string | number, T>>\n data: ComputedRef<Array<T>>\n collection: ComputedRef<Collection<T, 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\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 just the query function\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>\n): UseLiveQueryReturn<GetResult<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<GetResult<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)\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils>>\n): UseLiveQueryReturnWithCollection<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 let unwrappedParam = configOrQueryOrCollection\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 // Check if it's already a collection by checking for specific collection methods\n const isCollection =\n unwrappedParam &&\n typeof unwrappedParam === `object` &&\n typeof unwrappedParam.subscribeChanges === `function` &&\n typeof unwrappedParam.startSyncImmediate === `function` &&\n typeof unwrappedParam.id === `string`\n\n if (isCollection) {\n // It's already a collection, ensure sync is started for Vue hooks\n unwrappedParam.startSyncImmediate()\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 return createLiveQueryCollection({\n query: unwrappedParam,\n startSync: true,\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 const data = computed(() => internalData)\n\n // Track collection status reactively\n const status = ref(collection.value.status)\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 // Track current unsubscribe function\n let currentUnsubscribe: (() => void) | null = null\n\n // Watch for collection changes and subscribe to updates\n watchEffect((onInvalidate) => {\n const currentCollection = collection.value\n\n // Update status ref whenever the effect runs\n status.value = currentCollection.status\n\n // Clean up previous subscription\n if (currentUnsubscribe) {\n currentUnsubscribe()\n }\n\n // Initialize state with current collection data\n state.clear()\n for (const [key, value] of currentCollection.entries()) {\n state.set(key, value)\n }\n\n // Initialize data array in correct order\n syncDataFromCollection(currentCollection)\n\n // Subscribe to collection changes with granular updates\n currentUnsubscribe = currentCollection.subscribeChanges(\n (changes: Array<ChangeMessage<any>>) => {\n // Apply each change individually to the reactive state\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\n // Update the data array to maintain sorted order\n syncDataFromCollection(currentCollection)\n // Update status ref on every change\n status.value = currentCollection.status\n }\n )\n\n // Preload collection data if not already started\n if (currentCollection.status === `idle`) {\n currentCollection.preload().catch(console.error)\n }\n\n // Cleanup when effect is invalidated\n onInvalidate(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n })\n })\n\n // Cleanup on unmount (only if we're in a component context)\n const instance = getCurrentInstance()\n if (instance) {\n onUnmounted(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n }\n })\n }\n\n return {\n state: computed(() => state),\n data,\n collection: computed(() => collection.value),\n status: computed(() => status.value),\n isLoading: computed(\n () => status.value === `loading` || status.value === `initialCommit`\n ),\n isReady: computed(() => status.value === `ready`),\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","createLiveQueryCollection","reactive","ref","watchEffect","getCurrentInstance","onUnmounted"],"mappings":"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"}
@@ -1,5 +1,17 @@
1
1
  import { Collection, CollectionStatus, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
2
2
  import { ComputedRef, MaybeRefOrGetter } from 'vue';
3
+ /**
4
+ * Return type for useLiveQuery hook
5
+ * @property state - Reactive Map of query results (key → item)
6
+ * @property data - Reactive array of query results in order
7
+ * @property collection - The underlying query collection instance
8
+ * @property status - Current query status
9
+ * @property isLoading - True while initial query data is loading
10
+ * @property isReady - True when query has received first data and is ready
11
+ * @property isIdle - True when query hasn't started yet
12
+ * @property isError - True when query encountered an error
13
+ * @property isCleanedUp - True when query has been cleaned up
14
+ */
3
15
  export interface UseLiveQueryReturn<T extends object> {
4
16
  state: ComputedRef<Map<string | number, T>>;
5
17
  data: ComputedRef<Array<T>>;
@@ -22,6 +34,126 @@ export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends
22
34
  isError: ComputedRef<boolean>;
23
35
  isCleanedUp: ComputedRef<boolean>;
24
36
  }
37
+ /**
38
+ * Create a live query using a query function
39
+ * @param queryFn - Query function that defines what data to fetch
40
+ * @param deps - Array of reactive dependencies that trigger query re-execution when changed
41
+ * @returns Reactive object with query data, state, and status information
42
+ * @example
43
+ * // Basic query with object syntax
44
+ * const { data, isLoading } = useLiveQuery((q) =>
45
+ * q.from({ todos: todosCollection })
46
+ * .where(({ todos }) => eq(todos.completed, false))
47
+ * .select(({ todos }) => ({ id: todos.id, text: todos.text }))
48
+ * )
49
+ *
50
+ * @example
51
+ * // With reactive dependencies
52
+ * const minPriority = ref(5)
53
+ * const { data, state } = useLiveQuery(
54
+ * (q) => q.from({ todos: todosCollection })
55
+ * .where(({ todos }) => gt(todos.priority, minPriority.value)),
56
+ * [minPriority] // Re-run when minPriority changes
57
+ * )
58
+ *
59
+ * @example
60
+ * // Join pattern
61
+ * const { data } = useLiveQuery((q) =>
62
+ * q.from({ issues: issueCollection })
63
+ * .join({ persons: personCollection }, ({ issues, persons }) =>
64
+ * eq(issues.userId, persons.id)
65
+ * )
66
+ * .select(({ issues, persons }) => ({
67
+ * id: issues.id,
68
+ * title: issues.title,
69
+ * userName: persons.name
70
+ * }))
71
+ * )
72
+ *
73
+ * @example
74
+ * // Handle loading and error states in template
75
+ * const { data, isLoading, isError, status } = useLiveQuery((q) =>
76
+ * q.from({ todos: todoCollection })
77
+ * )
78
+ *
79
+ * // In template:
80
+ * // <div v-if="isLoading">Loading...</div>
81
+ * // <div v-else-if="isError">Error: {{ status }}</div>
82
+ * // <ul v-else>
83
+ * // <li v-for="todo in data" :key="todo.id">{{ todo.text }}</li>
84
+ * // </ul>
85
+ */
25
86
  export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
87
+ /**
88
+ * Create a live query using configuration object
89
+ * @param config - Configuration object with query and options
90
+ * @param deps - Array of reactive dependencies that trigger query re-execution when changed
91
+ * @returns Reactive object with query data, state, and status information
92
+ * @example
93
+ * // Basic config object usage
94
+ * const { data, status } = useLiveQuery({
95
+ * query: (q) => q.from({ todos: todosCollection }),
96
+ * gcTime: 60000
97
+ * })
98
+ *
99
+ * @example
100
+ * // With reactive dependencies
101
+ * const filter = ref('active')
102
+ * const { data, isReady } = useLiveQuery({
103
+ * query: (q) => q.from({ todos: todosCollection })
104
+ * .where(({ todos }) => eq(todos.status, filter.value))
105
+ * }, [filter])
106
+ *
107
+ * @example
108
+ * // Handle all states uniformly
109
+ * const { data, isLoading, isReady, isError } = useLiveQuery({
110
+ * query: (q) => q.from({ items: itemCollection })
111
+ * })
112
+ *
113
+ * // In template:
114
+ * // <div v-if="isLoading">Loading...</div>
115
+ * // <div v-else-if="isError">Something went wrong</div>
116
+ * // <div v-else-if="!isReady">Preparing...</div>
117
+ * // <div v-else>{{ data.length }} items loaded</div>
118
+ */
26
119
  export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
120
+ /**
121
+ * Subscribe to an existing query collection (can be reactive)
122
+ * @param liveQueryCollection - Pre-created query collection to subscribe to (can be a ref)
123
+ * @returns Reactive object with query data, state, and status information
124
+ * @example
125
+ * // Using pre-created query collection
126
+ * const myLiveQuery = createLiveQueryCollection((q) =>
127
+ * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))
128
+ * )
129
+ * const { data, collection } = useLiveQuery(myLiveQuery)
130
+ *
131
+ * @example
132
+ * // Reactive query collection reference
133
+ * const selectedQuery = ref(todosQuery)
134
+ * const { data, collection } = useLiveQuery(selectedQuery)
135
+ *
136
+ * // Switch queries reactively
137
+ * selectedQuery.value = archiveQuery
138
+ *
139
+ * @example
140
+ * // Access query collection methods directly
141
+ * const { data, collection, isReady } = useLiveQuery(existingQuery)
142
+ *
143
+ * // Use underlying collection for mutations
144
+ * const handleToggle = (id) => {
145
+ * collection.value.update(id, draft => { draft.completed = !draft.completed })
146
+ * }
147
+ *
148
+ * @example
149
+ * // Handle states consistently
150
+ * const { data, isLoading, isError } = useLiveQuery(sharedQuery)
151
+ *
152
+ * // In template:
153
+ * // <div v-if="isLoading">Loading...</div>
154
+ * // <div v-else-if="isError">Error loading data</div>
155
+ * // <div v-else>
156
+ * // <Item v-for="item in data" :key="item.id" v-bind="item" />
157
+ * // </div>
158
+ */
27
159
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils>>): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils>;
@@ -1,5 +1,17 @@
1
1
  import { Collection, CollectionStatus, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
2
2
  import { ComputedRef, MaybeRefOrGetter } from 'vue';
3
+ /**
4
+ * Return type for useLiveQuery hook
5
+ * @property state - Reactive Map of query results (key → item)
6
+ * @property data - Reactive array of query results in order
7
+ * @property collection - The underlying query collection instance
8
+ * @property status - Current query status
9
+ * @property isLoading - True while initial query data is loading
10
+ * @property isReady - True when query has received first data and is ready
11
+ * @property isIdle - True when query hasn't started yet
12
+ * @property isError - True when query encountered an error
13
+ * @property isCleanedUp - True when query has been cleaned up
14
+ */
3
15
  export interface UseLiveQueryReturn<T extends object> {
4
16
  state: ComputedRef<Map<string | number, T>>;
5
17
  data: ComputedRef<Array<T>>;
@@ -22,6 +34,126 @@ export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends
22
34
  isError: ComputedRef<boolean>;
23
35
  isCleanedUp: ComputedRef<boolean>;
24
36
  }
37
+ /**
38
+ * Create a live query using a query function
39
+ * @param queryFn - Query function that defines what data to fetch
40
+ * @param deps - Array of reactive dependencies that trigger query re-execution when changed
41
+ * @returns Reactive object with query data, state, and status information
42
+ * @example
43
+ * // Basic query with object syntax
44
+ * const { data, isLoading } = useLiveQuery((q) =>
45
+ * q.from({ todos: todosCollection })
46
+ * .where(({ todos }) => eq(todos.completed, false))
47
+ * .select(({ todos }) => ({ id: todos.id, text: todos.text }))
48
+ * )
49
+ *
50
+ * @example
51
+ * // With reactive dependencies
52
+ * const minPriority = ref(5)
53
+ * const { data, state } = useLiveQuery(
54
+ * (q) => q.from({ todos: todosCollection })
55
+ * .where(({ todos }) => gt(todos.priority, minPriority.value)),
56
+ * [minPriority] // Re-run when minPriority changes
57
+ * )
58
+ *
59
+ * @example
60
+ * // Join pattern
61
+ * const { data } = useLiveQuery((q) =>
62
+ * q.from({ issues: issueCollection })
63
+ * .join({ persons: personCollection }, ({ issues, persons }) =>
64
+ * eq(issues.userId, persons.id)
65
+ * )
66
+ * .select(({ issues, persons }) => ({
67
+ * id: issues.id,
68
+ * title: issues.title,
69
+ * userName: persons.name
70
+ * }))
71
+ * )
72
+ *
73
+ * @example
74
+ * // Handle loading and error states in template
75
+ * const { data, isLoading, isError, status } = useLiveQuery((q) =>
76
+ * q.from({ todos: todoCollection })
77
+ * )
78
+ *
79
+ * // In template:
80
+ * // <div v-if="isLoading">Loading...</div>
81
+ * // <div v-else-if="isError">Error: {{ status }}</div>
82
+ * // <ul v-else>
83
+ * // <li v-for="todo in data" :key="todo.id">{{ todo.text }}</li>
84
+ * // </ul>
85
+ */
25
86
  export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
87
+ /**
88
+ * Create a live query using configuration object
89
+ * @param config - Configuration object with query and options
90
+ * @param deps - Array of reactive dependencies that trigger query re-execution when changed
91
+ * @returns Reactive object with query data, state, and status information
92
+ * @example
93
+ * // Basic config object usage
94
+ * const { data, status } = useLiveQuery({
95
+ * query: (q) => q.from({ todos: todosCollection }),
96
+ * gcTime: 60000
97
+ * })
98
+ *
99
+ * @example
100
+ * // With reactive dependencies
101
+ * const filter = ref('active')
102
+ * const { data, isReady } = useLiveQuery({
103
+ * query: (q) => q.from({ todos: todosCollection })
104
+ * .where(({ todos }) => eq(todos.status, filter.value))
105
+ * }, [filter])
106
+ *
107
+ * @example
108
+ * // Handle all states uniformly
109
+ * const { data, isLoading, isReady, isError } = useLiveQuery({
110
+ * query: (q) => q.from({ items: itemCollection })
111
+ * })
112
+ *
113
+ * // In template:
114
+ * // <div v-if="isLoading">Loading...</div>
115
+ * // <div v-else-if="isError">Something went wrong</div>
116
+ * // <div v-else-if="!isReady">Preparing...</div>
117
+ * // <div v-else>{{ data.length }} items loaded</div>
118
+ */
26
119
  export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
120
+ /**
121
+ * Subscribe to an existing query collection (can be reactive)
122
+ * @param liveQueryCollection - Pre-created query collection to subscribe to (can be a ref)
123
+ * @returns Reactive object with query data, state, and status information
124
+ * @example
125
+ * // Using pre-created query collection
126
+ * const myLiveQuery = createLiveQueryCollection((q) =>
127
+ * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))
128
+ * )
129
+ * const { data, collection } = useLiveQuery(myLiveQuery)
130
+ *
131
+ * @example
132
+ * // Reactive query collection reference
133
+ * const selectedQuery = ref(todosQuery)
134
+ * const { data, collection } = useLiveQuery(selectedQuery)
135
+ *
136
+ * // Switch queries reactively
137
+ * selectedQuery.value = archiveQuery
138
+ *
139
+ * @example
140
+ * // Access query collection methods directly
141
+ * const { data, collection, isReady } = useLiveQuery(existingQuery)
142
+ *
143
+ * // Use underlying collection for mutations
144
+ * const handleToggle = (id) => {
145
+ * collection.value.update(id, draft => { draft.completed = !draft.completed })
146
+ * }
147
+ *
148
+ * @example
149
+ * // Handle states consistently
150
+ * const { data, isLoading, isError } = useLiveQuery(sharedQuery)
151
+ *
152
+ * // In template:
153
+ * // <div v-if="isLoading">Loading...</div>
154
+ * // <div v-else-if="isError">Error loading data</div>
155
+ * // <div v-else>
156
+ * // <Item v-for="item in data" :key="item.id" v-bind="item" />
157
+ * // </div>
158
+ */
27
159
  export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils>>): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils>;
@@ -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 { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from \"@tanstack/db\"\nimport type { ComputedRef, MaybeRefOrGetter } from \"vue\"\n\nexport interface UseLiveQueryReturn<T extends object> {\n state: ComputedRef<Map<string | number, T>>\n data: ComputedRef<Array<T>>\n collection: ComputedRef<Collection<T, 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\n// Overload 1: Accept just the query function\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>\n): UseLiveQueryReturn<GetResult<TContext>>\n\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: LiveQueryCollectionConfig<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>\n): UseLiveQueryReturn<GetResult<TContext>>\n\n// Overload 3: Accept pre-created live query collection (can be reactive)\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils>>\n): UseLiveQueryReturnWithCollection<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 let unwrappedParam = configOrQueryOrCollection\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 // Check if it's already a collection by checking for specific collection methods\n const isCollection =\n unwrappedParam &&\n typeof unwrappedParam === `object` &&\n typeof unwrappedParam.subscribeChanges === `function` &&\n typeof unwrappedParam.startSyncImmediate === `function` &&\n typeof unwrappedParam.id === `string`\n\n if (isCollection) {\n // It's already a collection, ensure sync is started for Vue hooks\n unwrappedParam.startSyncImmediate()\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 return createLiveQueryCollection({\n query: unwrappedParam,\n startSync: true,\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 const data = computed(() => internalData)\n\n // Track collection status reactively\n const status = ref(collection.value.status)\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 // Track current unsubscribe function\n let currentUnsubscribe: (() => void) | null = null\n\n // Watch for collection changes and subscribe to updates\n watchEffect((onInvalidate) => {\n const currentCollection = collection.value\n\n // Update status ref whenever the effect runs\n status.value = currentCollection.status\n\n // Clean up previous subscription\n if (currentUnsubscribe) {\n currentUnsubscribe()\n }\n\n // Initialize state with current collection data\n state.clear()\n for (const [key, value] of currentCollection.entries()) {\n state.set(key, value)\n }\n\n // Initialize data array in correct order\n syncDataFromCollection(currentCollection)\n\n // Subscribe to collection changes with granular updates\n currentUnsubscribe = currentCollection.subscribeChanges(\n (changes: Array<ChangeMessage<any>>) => {\n // Apply each change individually to the reactive state\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\n // Update the data array to maintain sorted order\n syncDataFromCollection(currentCollection)\n // Update status ref on every change\n status.value = currentCollection.status\n }\n )\n\n // Preload collection data if not already started\n if (currentCollection.status === `idle`) {\n currentCollection.preload().catch(console.error)\n }\n\n // Cleanup when effect is invalidated\n onInvalidate(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n })\n })\n\n // Cleanup on unmount (only if we're in a component context)\n const instance = getCurrentInstance()\n if (instance) {\n onUnmounted(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n }\n })\n }\n\n return {\n state: computed(() => state),\n data,\n collection: computed(() => collection.value),\n status: computed(() => status.value),\n isLoading: computed(\n () => status.value === `loading` || status.value === `initialCommit`\n ),\n isReady: computed(() => status.value === `ready`),\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 toValue,\n watchEffect,\n} from \"vue\"\nimport { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\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\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<T extends object> {\n state: ComputedRef<Map<string | number, T>>\n data: ComputedRef<Array<T>>\n collection: ComputedRef<Collection<T, 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\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 just the query function\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n deps?: Array<MaybeRefOrGetter<unknown>>\n): UseLiveQueryReturn<GetResult<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<GetResult<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)\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils>>\n): UseLiveQueryReturnWithCollection<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 let unwrappedParam = configOrQueryOrCollection\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 // Check if it's already a collection by checking for specific collection methods\n const isCollection =\n unwrappedParam &&\n typeof unwrappedParam === `object` &&\n typeof unwrappedParam.subscribeChanges === `function` &&\n typeof unwrappedParam.startSyncImmediate === `function` &&\n typeof unwrappedParam.id === `string`\n\n if (isCollection) {\n // It's already a collection, ensure sync is started for Vue hooks\n unwrappedParam.startSyncImmediate()\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 return createLiveQueryCollection({\n query: unwrappedParam,\n startSync: true,\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 const data = computed(() => internalData)\n\n // Track collection status reactively\n const status = ref(collection.value.status)\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 // Track current unsubscribe function\n let currentUnsubscribe: (() => void) | null = null\n\n // Watch for collection changes and subscribe to updates\n watchEffect((onInvalidate) => {\n const currentCollection = collection.value\n\n // Update status ref whenever the effect runs\n status.value = currentCollection.status\n\n // Clean up previous subscription\n if (currentUnsubscribe) {\n currentUnsubscribe()\n }\n\n // Initialize state with current collection data\n state.clear()\n for (const [key, value] of currentCollection.entries()) {\n state.set(key, value)\n }\n\n // Initialize data array in correct order\n syncDataFromCollection(currentCollection)\n\n // Subscribe to collection changes with granular updates\n currentUnsubscribe = currentCollection.subscribeChanges(\n (changes: Array<ChangeMessage<any>>) => {\n // Apply each change individually to the reactive state\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\n // Update the data array to maintain sorted order\n syncDataFromCollection(currentCollection)\n // Update status ref on every change\n status.value = currentCollection.status\n }\n )\n\n // Preload collection data if not already started\n if (currentCollection.status === `idle`) {\n currentCollection.preload().catch(console.error)\n }\n\n // Cleanup when effect is invalidated\n onInvalidate(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n })\n })\n\n // Cleanup on unmount (only if we're in a component context)\n const instance = getCurrentInstance()\n if (instance) {\n onUnmounted(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n }\n })\n }\n\n return {\n state: computed(() => state),\n data,\n collection: computed(() => collection.value),\n status: computed(() => status.value),\n isLoading: computed(\n () => status.value === `loading` || status.value === `initialCommit`\n ),\n isReady: computed(() => status.value === `ready`),\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,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/vue-db",
3
3
  "description": "Vue integration for @tanstack/db",
4
- "version": "0.0.16",
4
+ "version": "0.0.18",
5
5
  "author": "Kyle Mathews",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -16,7 +16,7 @@
16
16
  "typescript"
17
17
  ],
18
18
  "dependencies": {
19
- "@tanstack/db": "0.0.17"
19
+ "@tanstack/db": "0.0.19"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@electric-sql/client": "1.0.0",
@@ -20,6 +20,18 @@ import type {
20
20
  } from "@tanstack/db"
21
21
  import type { ComputedRef, MaybeRefOrGetter } from "vue"
22
22
 
23
+ /**
24
+ * Return type for useLiveQuery hook
25
+ * @property state - Reactive Map of query results (key → item)
26
+ * @property data - Reactive array of query results in order
27
+ * @property collection - The underlying query collection instance
28
+ * @property status - Current query status
29
+ * @property isLoading - True while initial query data is loading
30
+ * @property isReady - True when query has received first data and is ready
31
+ * @property isIdle - True when query hasn't started yet
32
+ * @property isError - True when query encountered an error
33
+ * @property isCleanedUp - True when query has been cleaned up
34
+ */
23
35
  export interface UseLiveQueryReturn<T extends object> {
24
36
  state: ComputedRef<Map<string | number, T>>
25
37
  data: ComputedRef<Array<T>>
@@ -48,18 +60,138 @@ export interface UseLiveQueryReturnWithCollection<
48
60
  isCleanedUp: ComputedRef<boolean>
49
61
  }
50
62
 
63
+ /**
64
+ * Create a live query using a query function
65
+ * @param queryFn - Query function that defines what data to fetch
66
+ * @param deps - Array of reactive dependencies that trigger query re-execution when changed
67
+ * @returns Reactive object with query data, state, and status information
68
+ * @example
69
+ * // Basic query with object syntax
70
+ * const { data, isLoading } = useLiveQuery((q) =>
71
+ * q.from({ todos: todosCollection })
72
+ * .where(({ todos }) => eq(todos.completed, false))
73
+ * .select(({ todos }) => ({ id: todos.id, text: todos.text }))
74
+ * )
75
+ *
76
+ * @example
77
+ * // With reactive dependencies
78
+ * const minPriority = ref(5)
79
+ * const { data, state } = useLiveQuery(
80
+ * (q) => q.from({ todos: todosCollection })
81
+ * .where(({ todos }) => gt(todos.priority, minPriority.value)),
82
+ * [minPriority] // Re-run when minPriority changes
83
+ * )
84
+ *
85
+ * @example
86
+ * // Join pattern
87
+ * const { data } = useLiveQuery((q) =>
88
+ * q.from({ issues: issueCollection })
89
+ * .join({ persons: personCollection }, ({ issues, persons }) =>
90
+ * eq(issues.userId, persons.id)
91
+ * )
92
+ * .select(({ issues, persons }) => ({
93
+ * id: issues.id,
94
+ * title: issues.title,
95
+ * userName: persons.name
96
+ * }))
97
+ * )
98
+ *
99
+ * @example
100
+ * // Handle loading and error states in template
101
+ * const { data, isLoading, isError, status } = useLiveQuery((q) =>
102
+ * q.from({ todos: todoCollection })
103
+ * )
104
+ *
105
+ * // In template:
106
+ * // <div v-if="isLoading">Loading...</div>
107
+ * // <div v-else-if="isError">Error: {{ status }}</div>
108
+ * // <ul v-else>
109
+ * // <li v-for="todo in data" :key="todo.id">{{ todo.text }}</li>
110
+ * // </ul>
111
+ */
51
112
  // Overload 1: Accept just the query function
52
113
  export function useLiveQuery<TContext extends Context>(
53
114
  queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
54
115
  deps?: Array<MaybeRefOrGetter<unknown>>
55
116
  ): UseLiveQueryReturn<GetResult<TContext>>
56
117
 
118
+ /**
119
+ * Create a live query using configuration object
120
+ * @param config - Configuration object with query and options
121
+ * @param deps - Array of reactive dependencies that trigger query re-execution when changed
122
+ * @returns Reactive object with query data, state, and status information
123
+ * @example
124
+ * // Basic config object usage
125
+ * const { data, status } = useLiveQuery({
126
+ * query: (q) => q.from({ todos: todosCollection }),
127
+ * gcTime: 60000
128
+ * })
129
+ *
130
+ * @example
131
+ * // With reactive dependencies
132
+ * const filter = ref('active')
133
+ * const { data, isReady } = useLiveQuery({
134
+ * query: (q) => q.from({ todos: todosCollection })
135
+ * .where(({ todos }) => eq(todos.status, filter.value))
136
+ * }, [filter])
137
+ *
138
+ * @example
139
+ * // Handle all states uniformly
140
+ * const { data, isLoading, isReady, isError } = useLiveQuery({
141
+ * query: (q) => q.from({ items: itemCollection })
142
+ * })
143
+ *
144
+ * // In template:
145
+ * // <div v-if="isLoading">Loading...</div>
146
+ * // <div v-else-if="isError">Something went wrong</div>
147
+ * // <div v-else-if="!isReady">Preparing...</div>
148
+ * // <div v-else>{{ data.length }} items loaded</div>
149
+ */
57
150
  // Overload 2: Accept config object
58
151
  export function useLiveQuery<TContext extends Context>(
59
152
  config: LiveQueryCollectionConfig<TContext>,
60
153
  deps?: Array<MaybeRefOrGetter<unknown>>
61
154
  ): UseLiveQueryReturn<GetResult<TContext>>
62
155
 
156
+ /**
157
+ * Subscribe to an existing query collection (can be reactive)
158
+ * @param liveQueryCollection - Pre-created query collection to subscribe to (can be a ref)
159
+ * @returns Reactive object with query data, state, and status information
160
+ * @example
161
+ * // Using pre-created query collection
162
+ * const myLiveQuery = createLiveQueryCollection((q) =>
163
+ * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))
164
+ * )
165
+ * const { data, collection } = useLiveQuery(myLiveQuery)
166
+ *
167
+ * @example
168
+ * // Reactive query collection reference
169
+ * const selectedQuery = ref(todosQuery)
170
+ * const { data, collection } = useLiveQuery(selectedQuery)
171
+ *
172
+ * // Switch queries reactively
173
+ * selectedQuery.value = archiveQuery
174
+ *
175
+ * @example
176
+ * // Access query collection methods directly
177
+ * const { data, collection, isReady } = useLiveQuery(existingQuery)
178
+ *
179
+ * // Use underlying collection for mutations
180
+ * const handleToggle = (id) => {
181
+ * collection.value.update(id, draft => { draft.completed = !draft.completed })
182
+ * }
183
+ *
184
+ * @example
185
+ * // Handle states consistently
186
+ * const { data, isLoading, isError } = useLiveQuery(sharedQuery)
187
+ *
188
+ * // In template:
189
+ * // <div v-if="isLoading">Loading...</div>
190
+ * // <div v-else-if="isError">Error loading data</div>
191
+ * // <div v-else>
192
+ * // <Item v-for="item in data" :key="item.id" v-bind="item" />
193
+ * // </div>
194
+ */
63
195
  // Overload 3: Accept pre-created live query collection (can be reactive)
64
196
  export function useLiveQuery<
65
197
  TResult extends object,