@tanstack/vue-db 0.0.14 → 0.0.16

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.
@@ -34,6 +34,7 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
34
34
  const state = vue.reactive(/* @__PURE__ */ new Map());
35
35
  const internalData = vue.reactive([]);
36
36
  const data = vue.computed(() => internalData);
37
+ const status = vue.ref(collection.value.status);
37
38
  const syncDataFromCollection = (currentCollection) => {
38
39
  internalData.length = 0;
39
40
  internalData.push(...Array.from(currentCollection.values()));
@@ -41,6 +42,7 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
41
42
  let currentUnsubscribe = null;
42
43
  vue.watchEffect((onInvalidate) => {
43
44
  const currentCollection = collection.value;
45
+ status.value = currentCollection.status;
44
46
  if (currentUnsubscribe) {
45
47
  currentUnsubscribe();
46
48
  }
@@ -63,6 +65,7 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
63
65
  }
64
66
  }
65
67
  syncDataFromCollection(currentCollection);
68
+ status.value = currentCollection.status;
66
69
  }
67
70
  );
68
71
  if (currentCollection.status === `idle`) {
@@ -86,7 +89,15 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
86
89
  return {
87
90
  state: vue.computed(() => state),
88
91
  data,
89
- collection: vue.computed(() => collection.value)
92
+ collection: vue.computed(() => collection.value),
93
+ status: vue.computed(() => status.value),
94
+ isLoading: vue.computed(
95
+ () => status.value === `loading` || status.value === `initialCommit`
96
+ ),
97
+ isReady: vue.computed(() => status.value === `ready`),
98
+ isIdle: vue.computed(() => status.value === `idle`),
99
+ isError: vue.computed(() => status.value === `error`),
100
+ isCleanedUp: vue.computed(() => status.value === `cleaned-up`)
90
101
  };
91
102
  }
92
103
  exports.useLiveQuery = useLiveQuery;
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n computed,\n getCurrentInstance,\n onUnmounted,\n reactive,\n toValue,\n watchEffect,\n} from \"vue\"\nimport { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n ChangeMessage,\n Collection,\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}\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}\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 // 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 // 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 }\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 }\n}\n"],"names":["computed","toValue","createLiveQueryCollection","reactive","watchEffect","getCurrentInstance","onUnmounted"],"mappings":";;;;AA0DO,SAAS,aACd,2BACA,OAAyC,IACkC;AACrE,QAAA,aAAaA,IAAAA,SAAS,MAAM;AAGhC,QAAI,iBAAiB;AACjB,QAAA;AACI,YAAA,uBAAuBC,YAAQ,yBAAyB;AAC9D,UAAI,yBAAyB,2BAA2B;AACrC,yBAAA;AAAA,MAAA;AAAA,IACnB,QACM;AAEW,uBAAA;AAAA,IAAA;AAInB,UAAM,eACJ,kBACA,OAAO,mBAAmB,YAC1B,OAAO,eAAe,qBAAqB,cAC3C,OAAO,eAAe,uBAAuB,cAC7C,OAAO,eAAe,OAAO;AAE/B,QAAI,cAAc;AAEhB,qBAAe,mBAAmB;AAC3B,aAAA;AAAA,IAAA;AAIT,SAAK,QAAQ,CAAC,QAAQA,IAAA,QAAQ,GAAG,CAAC;AAG9B,QAAA,OAAO,mBAAmB,YAAY;AACxC,aAAOC,6BAA0B;AAAA,QAC/B,OAAO;AAAA,QACP,WAAW;AAAA,MAAA,CACZ;AAAA,IAAA,OACI;AACL,aAAOA,6BAA0B;AAAA,QAC/B,GAAG;AAAA,QACH,WAAW;AAAA,MAAA,CACZ;AAAA,IAAA;AAAA,EACH,CACD;AAGD,QAAM,QAAQC,IAAAA,SAAa,oBAAA,KAA2B;AAGhD,QAAA,eAAeA,IAAqB,SAAA,EAAE;AAGtC,QAAA,OAAOH,aAAS,MAAM,YAAY;AAGlC,QAAA,yBAAyB,CAC7B,sBACG;AACH,iBAAa,SAAS;AACtB,iBAAa,KAAK,GAAG,MAAM,KAAK,kBAAkB,OAAA,CAAQ,CAAC;AAAA,EAC7D;AAGA,MAAI,qBAA0C;AAG9CI,MAAA,YAAY,CAAC,iBAAiB;AAC5B,UAAM,oBAAoB,WAAW;AAGrC,QAAI,oBAAoB;AACH,yBAAA;AAAA,IAAA;AAIrB,UAAM,MAAM;AACZ,eAAW,CAAC,KAAK,KAAK,KAAK,kBAAkB,WAAW;AAChD,YAAA,IAAI,KAAK,KAAK;AAAA,IAAA;AAItB,2BAAuB,iBAAiB;AAGxC,yBAAqB,kBAAkB;AAAA,MACrC,CAAC,YAAuC;AAEtC,mBAAW,UAAU,SAAS;AAC5B,kBAAQ,OAAO,MAAM;AAAA,YACnB,KAAK;AAAA,YACL,KAAK;AACH,oBAAM,IAAI,OAAO,KAAK,OAAO,KAAK;AAClC;AAAA,YACF,KAAK;AACG,oBAAA,OAAO,OAAO,GAAG;AACvB;AAAA,UAAA;AAAA,QACJ;AAIF,+BAAuB,iBAAiB;AAAA,MAAA;AAAA,IAE5C;AAGI,QAAA,kBAAkB,WAAW,QAAQ;AACvC,wBAAkB,QAAQ,EAAE,MAAM,QAAQ,KAAK;AAAA,IAAA;AAIjD,iBAAa,MAAM;AACjB,UAAI,oBAAoB;AACH,2BAAA;AACE,6BAAA;AAAA,MAAA;AAAA,IACvB,CACD;AAAA,EAAA,CACF;AAGD,QAAM,WAAWC,IAAAA,mBAAmB;AACpC,MAAI,UAAU;AACZC,QAAAA,YAAY,MAAM;AAChB,UAAI,oBAAoB;AACH,2BAAA;AAAA,MAAA;AAAA,IACrB,CACD;AAAA,EAAA;AAGI,SAAA;AAAA,IACL,OAAON,IAAAA,SAAS,MAAM,KAAK;AAAA,IAC3B;AAAA,IACA,YAAYA,IAAAA,SAAS,MAAM,WAAW,KAAK;AAAA,EAC7C;AACF;;"}
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,14 +1,26 @@
1
- import { Collection, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
1
+ import { Collection, CollectionStatus, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
2
2
  import { ComputedRef, MaybeRefOrGetter } from 'vue';
3
3
  export interface UseLiveQueryReturn<T extends object> {
4
4
  state: ComputedRef<Map<string | number, T>>;
5
5
  data: ComputedRef<Array<T>>;
6
6
  collection: ComputedRef<Collection<T, string | number, {}>>;
7
+ status: ComputedRef<CollectionStatus>;
8
+ isLoading: ComputedRef<boolean>;
9
+ isReady: ComputedRef<boolean>;
10
+ isIdle: ComputedRef<boolean>;
11
+ isError: ComputedRef<boolean>;
12
+ isCleanedUp: ComputedRef<boolean>;
7
13
  }
8
14
  export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends string | number, TUtils extends Record<string, any>> {
9
15
  state: ComputedRef<Map<TKey, T>>;
10
16
  data: ComputedRef<Array<T>>;
11
17
  collection: ComputedRef<Collection<T, TKey, TUtils>>;
18
+ status: ComputedRef<CollectionStatus>;
19
+ isLoading: ComputedRef<boolean>;
20
+ isReady: ComputedRef<boolean>;
21
+ isIdle: ComputedRef<boolean>;
22
+ isError: ComputedRef<boolean>;
23
+ isCleanedUp: ComputedRef<boolean>;
12
24
  }
13
25
  export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
14
26
  export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
@@ -1,14 +1,26 @@
1
- import { Collection, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
1
+ import { Collection, CollectionStatus, Context, GetResult, InitialQueryBuilder, LiveQueryCollectionConfig, QueryBuilder } from '@tanstack/db';
2
2
  import { ComputedRef, MaybeRefOrGetter } from 'vue';
3
3
  export interface UseLiveQueryReturn<T extends object> {
4
4
  state: ComputedRef<Map<string | number, T>>;
5
5
  data: ComputedRef<Array<T>>;
6
6
  collection: ComputedRef<Collection<T, string | number, {}>>;
7
+ status: ComputedRef<CollectionStatus>;
8
+ isLoading: ComputedRef<boolean>;
9
+ isReady: ComputedRef<boolean>;
10
+ isIdle: ComputedRef<boolean>;
11
+ isError: ComputedRef<boolean>;
12
+ isCleanedUp: ComputedRef<boolean>;
7
13
  }
8
14
  export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends string | number, TUtils extends Record<string, any>> {
9
15
  state: ComputedRef<Map<TKey, T>>;
10
16
  data: ComputedRef<Array<T>>;
11
17
  collection: ComputedRef<Collection<T, TKey, TUtils>>;
18
+ status: ComputedRef<CollectionStatus>;
19
+ isLoading: ComputedRef<boolean>;
20
+ isReady: ComputedRef<boolean>;
21
+ isIdle: ComputedRef<boolean>;
22
+ isError: ComputedRef<boolean>;
23
+ isCleanedUp: ComputedRef<boolean>;
12
24
  }
13
25
  export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
14
26
  export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
@@ -1,4 +1,4 @@
1
- import { computed, toValue, reactive, watchEffect, getCurrentInstance, onUnmounted } from "vue";
1
+ import { computed, toValue, reactive, ref, watchEffect, getCurrentInstance, onUnmounted } from "vue";
2
2
  import { createLiveQueryCollection } from "@tanstack/db";
3
3
  function useLiveQuery(configOrQueryOrCollection, deps = []) {
4
4
  const collection = computed(() => {
@@ -32,6 +32,7 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
32
32
  const state = reactive(/* @__PURE__ */ new Map());
33
33
  const internalData = reactive([]);
34
34
  const data = computed(() => internalData);
35
+ const status = ref(collection.value.status);
35
36
  const syncDataFromCollection = (currentCollection) => {
36
37
  internalData.length = 0;
37
38
  internalData.push(...Array.from(currentCollection.values()));
@@ -39,6 +40,7 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
39
40
  let currentUnsubscribe = null;
40
41
  watchEffect((onInvalidate) => {
41
42
  const currentCollection = collection.value;
43
+ status.value = currentCollection.status;
42
44
  if (currentUnsubscribe) {
43
45
  currentUnsubscribe();
44
46
  }
@@ -61,6 +63,7 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
61
63
  }
62
64
  }
63
65
  syncDataFromCollection(currentCollection);
66
+ status.value = currentCollection.status;
64
67
  }
65
68
  );
66
69
  if (currentCollection.status === `idle`) {
@@ -84,7 +87,15 @@ function useLiveQuery(configOrQueryOrCollection, deps = []) {
84
87
  return {
85
88
  state: computed(() => state),
86
89
  data,
87
- collection: computed(() => collection.value)
90
+ collection: computed(() => collection.value),
91
+ status: computed(() => status.value),
92
+ isLoading: computed(
93
+ () => status.value === `loading` || status.value === `initialCommit`
94
+ ),
95
+ isReady: computed(() => status.value === `ready`),
96
+ isIdle: computed(() => status.value === `idle`),
97
+ isError: computed(() => status.value === `error`),
98
+ isCleanedUp: computed(() => status.value === `cleaned-up`)
88
99
  };
89
100
  }
90
101
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n computed,\n getCurrentInstance,\n onUnmounted,\n reactive,\n toValue,\n watchEffect,\n} from \"vue\"\nimport { createLiveQueryCollection } from \"@tanstack/db\"\nimport type {\n ChangeMessage,\n Collection,\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}\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}\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 // 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 // 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 }\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 }\n}\n"],"names":[],"mappings":";;AA0DO,SAAS,aACd,2BACA,OAAyC,IACkC;AACrE,QAAA,aAAa,SAAS,MAAM;AAGhC,QAAI,iBAAiB;AACjB,QAAA;AACI,YAAA,uBAAuB,QAAQ,yBAAyB;AAC9D,UAAI,yBAAyB,2BAA2B;AACrC,yBAAA;AAAA,MAAA;AAAA,IACnB,QACM;AAEW,uBAAA;AAAA,IAAA;AAInB,UAAM,eACJ,kBACA,OAAO,mBAAmB,YAC1B,OAAO,eAAe,qBAAqB,cAC3C,OAAO,eAAe,uBAAuB,cAC7C,OAAO,eAAe,OAAO;AAE/B,QAAI,cAAc;AAEhB,qBAAe,mBAAmB;AAC3B,aAAA;AAAA,IAAA;AAIT,SAAK,QAAQ,CAAC,QAAQ,QAAQ,GAAG,CAAC;AAG9B,QAAA,OAAO,mBAAmB,YAAY;AACxC,aAAO,0BAA0B;AAAA,QAC/B,OAAO;AAAA,QACP,WAAW;AAAA,MAAA,CACZ;AAAA,IAAA,OACI;AACL,aAAO,0BAA0B;AAAA,QAC/B,GAAG;AAAA,QACH,WAAW;AAAA,MAAA,CACZ;AAAA,IAAA;AAAA,EACH,CACD;AAGD,QAAM,QAAQ,SAAa,oBAAA,KAA2B;AAGhD,QAAA,eAAe,SAAqB,EAAE;AAGtC,QAAA,OAAO,SAAS,MAAM,YAAY;AAGlC,QAAA,yBAAyB,CAC7B,sBACG;AACH,iBAAa,SAAS;AACtB,iBAAa,KAAK,GAAG,MAAM,KAAK,kBAAkB,OAAA,CAAQ,CAAC;AAAA,EAC7D;AAGA,MAAI,qBAA0C;AAG9C,cAAY,CAAC,iBAAiB;AAC5B,UAAM,oBAAoB,WAAW;AAGrC,QAAI,oBAAoB;AACH,yBAAA;AAAA,IAAA;AAIrB,UAAM,MAAM;AACZ,eAAW,CAAC,KAAK,KAAK,KAAK,kBAAkB,WAAW;AAChD,YAAA,IAAI,KAAK,KAAK;AAAA,IAAA;AAItB,2BAAuB,iBAAiB;AAGxC,yBAAqB,kBAAkB;AAAA,MACrC,CAAC,YAAuC;AAEtC,mBAAW,UAAU,SAAS;AAC5B,kBAAQ,OAAO,MAAM;AAAA,YACnB,KAAK;AAAA,YACL,KAAK;AACH,oBAAM,IAAI,OAAO,KAAK,OAAO,KAAK;AAClC;AAAA,YACF,KAAK;AACG,oBAAA,OAAO,OAAO,GAAG;AACvB;AAAA,UAAA;AAAA,QACJ;AAIF,+BAAuB,iBAAiB;AAAA,MAAA;AAAA,IAE5C;AAGI,QAAA,kBAAkB,WAAW,QAAQ;AACvC,wBAAkB,QAAQ,EAAE,MAAM,QAAQ,KAAK;AAAA,IAAA;AAIjD,iBAAa,MAAM;AACjB,UAAI,oBAAoB;AACH,2BAAA;AACE,6BAAA;AAAA,MAAA;AAAA,IACvB,CACD;AAAA,EAAA,CACF;AAGD,QAAM,WAAW,mBAAmB;AACpC,MAAI,UAAU;AACZ,gBAAY,MAAM;AAChB,UAAI,oBAAoB;AACH,2BAAA;AAAA,MAAA;AAAA,IACrB,CACD;AAAA,EAAA;AAGI,SAAA;AAAA,IACL,OAAO,SAAS,MAAM,KAAK;AAAA,IAC3B;AAAA,IACA,YAAY,SAAS,MAAM,WAAW,KAAK;AAAA,EAC7C;AACF;"}
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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"}
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.14",
4
+ "version": "0.0.16",
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.15"
19
+ "@tanstack/db": "0.0.17"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@electric-sql/client": "1.0.0",
@@ -3,6 +3,7 @@ import {
3
3
  getCurrentInstance,
4
4
  onUnmounted,
5
5
  reactive,
6
+ ref,
6
7
  toValue,
7
8
  watchEffect,
8
9
  } from "vue"
@@ -10,6 +11,7 @@ import { createLiveQueryCollection } from "@tanstack/db"
10
11
  import type {
11
12
  ChangeMessage,
12
13
  Collection,
14
+ CollectionStatus,
13
15
  Context,
14
16
  GetResult,
15
17
  InitialQueryBuilder,
@@ -22,6 +24,12 @@ export interface UseLiveQueryReturn<T extends object> {
22
24
  state: ComputedRef<Map<string | number, T>>
23
25
  data: ComputedRef<Array<T>>
24
26
  collection: ComputedRef<Collection<T, string | number, {}>>
27
+ status: ComputedRef<CollectionStatus>
28
+ isLoading: ComputedRef<boolean>
29
+ isReady: ComputedRef<boolean>
30
+ isIdle: ComputedRef<boolean>
31
+ isError: ComputedRef<boolean>
32
+ isCleanedUp: ComputedRef<boolean>
25
33
  }
26
34
 
27
35
  export interface UseLiveQueryReturnWithCollection<
@@ -32,6 +40,12 @@ export interface UseLiveQueryReturnWithCollection<
32
40
  state: ComputedRef<Map<TKey, T>>
33
41
  data: ComputedRef<Array<T>>
34
42
  collection: ComputedRef<Collection<T, TKey, TUtils>>
43
+ status: ComputedRef<CollectionStatus>
44
+ isLoading: ComputedRef<boolean>
45
+ isReady: ComputedRef<boolean>
46
+ isIdle: ComputedRef<boolean>
47
+ isError: ComputedRef<boolean>
48
+ isCleanedUp: ComputedRef<boolean>
35
49
  }
36
50
 
37
51
  // Overload 1: Accept just the query function
@@ -114,6 +128,9 @@ export function useLiveQuery(
114
128
  // Computed wrapper for the data to match expected return type
115
129
  const data = computed(() => internalData)
116
130
 
131
+ // Track collection status reactively
132
+ const status = ref(collection.value.status)
133
+
117
134
  // Helper to sync data array from collection in correct order
118
135
  const syncDataFromCollection = (
119
136
  currentCollection: Collection<any, any, any>
@@ -129,6 +146,9 @@ export function useLiveQuery(
129
146
  watchEffect((onInvalidate) => {
130
147
  const currentCollection = collection.value
131
148
 
149
+ // Update status ref whenever the effect runs
150
+ status.value = currentCollection.status
151
+
132
152
  // Clean up previous subscription
133
153
  if (currentUnsubscribe) {
134
154
  currentUnsubscribe()
@@ -161,6 +181,8 @@ export function useLiveQuery(
161
181
 
162
182
  // Update the data array to maintain sorted order
163
183
  syncDataFromCollection(currentCollection)
184
+ // Update status ref on every change
185
+ status.value = currentCollection.status
164
186
  }
165
187
  )
166
188
 
@@ -192,5 +214,13 @@ export function useLiveQuery(
192
214
  state: computed(() => state),
193
215
  data,
194
216
  collection: computed(() => collection.value),
217
+ status: computed(() => status.value),
218
+ isLoading: computed(
219
+ () => status.value === `loading` || status.value === `initialCommit`
220
+ ),
221
+ isReady: computed(() => status.value === `ready`),
222
+ isIdle: computed(() => status.value === `idle`),
223
+ isError: computed(() => status.value === `error`),
224
+ isCleanedUp: computed(() => status.value === `cleaned-up`),
195
225
  }
196
226
  }