@tanstack/vue-db 0.0.13 → 0.0.15

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,34 +1,92 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const vue = require("vue");
4
- const vueStore = require("@tanstack/vue-store");
5
4
  const db = require("@tanstack/db");
6
- function useLiveQuery(queryFn, deps = []) {
7
- const compiledQuery = vue.computed(() => {
5
+ function useLiveQuery(configOrQueryOrCollection, deps = []) {
6
+ const collection = vue.computed(() => {
7
+ let unwrappedParam = configOrQueryOrCollection;
8
+ try {
9
+ const potentiallyUnwrapped = vue.toValue(configOrQueryOrCollection);
10
+ if (potentiallyUnwrapped !== configOrQueryOrCollection) {
11
+ unwrappedParam = potentiallyUnwrapped;
12
+ }
13
+ } catch {
14
+ unwrappedParam = configOrQueryOrCollection;
15
+ }
16
+ const isCollection = unwrappedParam && typeof unwrappedParam === `object` && typeof unwrappedParam.subscribeChanges === `function` && typeof unwrappedParam.startSyncImmediate === `function` && typeof unwrappedParam.id === `string`;
17
+ if (isCollection) {
18
+ unwrappedParam.startSyncImmediate();
19
+ return unwrappedParam;
20
+ }
8
21
  deps.forEach((dep) => vue.toValue(dep));
9
- const query = queryFn(db.queryBuilder());
10
- const compiled = db.compileQuery(query);
11
- compiled.start();
12
- return compiled;
13
- });
14
- const state = vue.computed(() => {
15
- return vueStore.useStore(compiledQuery.value.results.asStoreMap()).value;
16
- });
17
- const data = vue.computed(() => {
18
- return vueStore.useStore(compiledQuery.value.results.asStoreArray()).value;
22
+ if (typeof unwrappedParam === `function`) {
23
+ return db.createLiveQueryCollection({
24
+ query: unwrappedParam,
25
+ startSync: true
26
+ });
27
+ } else {
28
+ return db.createLiveQueryCollection({
29
+ ...unwrappedParam,
30
+ startSync: true
31
+ });
32
+ }
19
33
  });
20
- vue.watch(compiledQuery, (newQuery, oldQuery, onInvalidate) => {
21
- if (newQuery.state === `stopped`) {
22
- newQuery.start();
34
+ const state = vue.reactive(/* @__PURE__ */ new Map());
35
+ const internalData = vue.reactive([]);
36
+ const data = vue.computed(() => internalData);
37
+ const syncDataFromCollection = (currentCollection) => {
38
+ internalData.length = 0;
39
+ internalData.push(...Array.from(currentCollection.values()));
40
+ };
41
+ let currentUnsubscribe = null;
42
+ vue.watchEffect((onInvalidate) => {
43
+ const currentCollection = collection.value;
44
+ if (currentUnsubscribe) {
45
+ currentUnsubscribe();
46
+ }
47
+ state.clear();
48
+ for (const [key, value] of currentCollection.entries()) {
49
+ state.set(key, value);
50
+ }
51
+ syncDataFromCollection(currentCollection);
52
+ currentUnsubscribe = currentCollection.subscribeChanges(
53
+ (changes) => {
54
+ for (const change of changes) {
55
+ switch (change.type) {
56
+ case `insert`:
57
+ case `update`:
58
+ state.set(change.key, change.value);
59
+ break;
60
+ case `delete`:
61
+ state.delete(change.key);
62
+ break;
63
+ }
64
+ }
65
+ syncDataFromCollection(currentCollection);
66
+ }
67
+ );
68
+ if (currentCollection.status === `idle`) {
69
+ currentCollection.preload().catch(console.error);
23
70
  }
24
71
  onInvalidate(() => {
25
- oldQuery.stop();
72
+ if (currentUnsubscribe) {
73
+ currentUnsubscribe();
74
+ currentUnsubscribe = null;
75
+ }
26
76
  });
27
77
  });
78
+ const instance = vue.getCurrentInstance();
79
+ if (instance) {
80
+ vue.onUnmounted(() => {
81
+ if (currentUnsubscribe) {
82
+ currentUnsubscribe();
83
+ }
84
+ });
85
+ }
28
86
  return {
29
- state,
87
+ state: vue.computed(() => state),
30
88
  data,
31
- collection: vue.computed(() => compiledQuery.value.results)
89
+ collection: vue.computed(() => collection.value)
32
90
  };
33
91
  }
34
92
  exports.useLiveQuery = useLiveQuery;
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.cjs","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import { computed, toValue, watch } from \"vue\"\nimport { useStore } from \"@tanstack/vue-store\"\nimport { compileQuery, queryBuilder } from \"@tanstack/db\"\nimport type {\n Collection,\n Context,\n InitialQueryBuilder,\n QueryBuilder,\n ResultsFromContext,\n Schema,\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>>\n}\n\nexport function useLiveQuery<\n TResultContext extends Context<Schema> = Context<Schema>,\n>(\n queryFn: (\n q: InitialQueryBuilder<Context<Schema>>\n ) => QueryBuilder<TResultContext>,\n deps: Array<MaybeRefOrGetter<unknown>> = []\n): UseLiveQueryReturn<ResultsFromContext<TResultContext>> {\n const compiledQuery = computed(() => {\n // Just reference deps to make computed reactive to them\n deps.forEach((dep) => toValue(dep))\n\n const query = queryFn(queryBuilder())\n const compiled = compileQuery(query)\n compiled.start()\n return compiled\n })\n\n const state = computed(() => {\n return useStore(compiledQuery.value.results.asStoreMap()).value\n })\n const data = computed(() => {\n return useStore(compiledQuery.value.results.asStoreArray()).value\n })\n\n watch(compiledQuery, (newQuery, oldQuery, onInvalidate) => {\n if (newQuery.state === `stopped`) {\n newQuery.start()\n }\n\n onInvalidate(() => {\n oldQuery.stop()\n })\n })\n\n return {\n state,\n data,\n collection: computed(() => compiledQuery.value.results),\n }\n}\n"],"names":["computed","toValue","queryBuilder","compileQuery","useStore","watch"],"mappings":";;;;;AAmBO,SAAS,aAGd,SAGA,OAAyC,IACe;AAClD,QAAA,gBAAgBA,IAAAA,SAAS,MAAM;AAEnC,SAAK,QAAQ,CAAC,QAAQC,IAAA,QAAQ,GAAG,CAAC;AAE5B,UAAA,QAAQ,QAAQC,GAAAA,cAAc;AAC9B,UAAA,WAAWC,gBAAa,KAAK;AACnC,aAAS,MAAM;AACR,WAAA;AAAA,EAAA,CACR;AAEK,QAAA,QAAQH,IAAAA,SAAS,MAAM;AAC3B,WAAOI,SAAAA,SAAS,cAAc,MAAM,QAAQ,WAAY,CAAA,EAAE;AAAA,EAAA,CAC3D;AACK,QAAA,OAAOJ,IAAAA,SAAS,MAAM;AAC1B,WAAOI,SAAAA,SAAS,cAAc,MAAM,QAAQ,aAAc,CAAA,EAAE;AAAA,EAAA,CAC7D;AAEDC,MAAAA,MAAM,eAAe,CAAC,UAAU,UAAU,iBAAiB;AACrD,QAAA,SAAS,UAAU,WAAW;AAChC,eAAS,MAAM;AAAA,IAAA;AAGjB,iBAAa,MAAM;AACjB,eAAS,KAAK;AAAA,IAAA,CACf;AAAA,EAAA,CACF;AAEM,SAAA;AAAA,IACL;AAAA,IACA;AAAA,IACA,YAAYL,IAAA,SAAS,MAAM,cAAc,MAAM,OAAO;AAAA,EACxD;AACF;;"}
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,8 +1,15 @@
1
- import { Collection, Context, InitialQueryBuilder, QueryBuilder, ResultsFromContext, Schema } from '@tanstack/db';
1
+ import { Collection, 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
- collection: ComputedRef<Collection<T>>;
6
+ collection: ComputedRef<Collection<T, string | number, {}>>;
7
7
  }
8
- export declare function useLiveQuery<TResultContext extends Context<Schema> = Context<Schema>>(queryFn: (q: InitialQueryBuilder<Context<Schema>>) => QueryBuilder<TResultContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<ResultsFromContext<TResultContext>>;
8
+ export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends string | number, TUtils extends Record<string, any>> {
9
+ state: ComputedRef<Map<TKey, T>>;
10
+ data: ComputedRef<Array<T>>;
11
+ collection: ComputedRef<Collection<T, TKey, TUtils>>;
12
+ }
13
+ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
14
+ export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
15
+ 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,8 +1,15 @@
1
- import { Collection, Context, InitialQueryBuilder, QueryBuilder, ResultsFromContext, Schema } from '@tanstack/db';
1
+ import { Collection, 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
- collection: ComputedRef<Collection<T>>;
6
+ collection: ComputedRef<Collection<T, string | number, {}>>;
7
7
  }
8
- export declare function useLiveQuery<TResultContext extends Context<Schema> = Context<Schema>>(queryFn: (q: InitialQueryBuilder<Context<Schema>>) => QueryBuilder<TResultContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<ResultsFromContext<TResultContext>>;
8
+ export interface UseLiveQueryReturnWithCollection<T extends object, TKey extends string | number, TUtils extends Record<string, any>> {
9
+ state: ComputedRef<Map<TKey, T>>;
10
+ data: ComputedRef<Array<T>>;
11
+ collection: ComputedRef<Collection<T, TKey, TUtils>>;
12
+ }
13
+ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
14
+ export declare function useLiveQuery<TContext extends Context>(config: LiveQueryCollectionConfig<TContext>, deps?: Array<MaybeRefOrGetter<unknown>>): UseLiveQueryReturn<GetResult<TContext>>;
15
+ 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,32 +1,90 @@
1
- import { computed, toValue, watch } from "vue";
2
- import { useStore } from "@tanstack/vue-store";
3
- import { queryBuilder, compileQuery } from "@tanstack/db";
4
- function useLiveQuery(queryFn, deps = []) {
5
- const compiledQuery = computed(() => {
1
+ import { computed, toValue, reactive, watchEffect, getCurrentInstance, onUnmounted } from "vue";
2
+ import { createLiveQueryCollection } from "@tanstack/db";
3
+ function useLiveQuery(configOrQueryOrCollection, deps = []) {
4
+ const collection = computed(() => {
5
+ let unwrappedParam = configOrQueryOrCollection;
6
+ try {
7
+ const potentiallyUnwrapped = toValue(configOrQueryOrCollection);
8
+ if (potentiallyUnwrapped !== configOrQueryOrCollection) {
9
+ unwrappedParam = potentiallyUnwrapped;
10
+ }
11
+ } catch {
12
+ unwrappedParam = configOrQueryOrCollection;
13
+ }
14
+ const isCollection = unwrappedParam && typeof unwrappedParam === `object` && typeof unwrappedParam.subscribeChanges === `function` && typeof unwrappedParam.startSyncImmediate === `function` && typeof unwrappedParam.id === `string`;
15
+ if (isCollection) {
16
+ unwrappedParam.startSyncImmediate();
17
+ return unwrappedParam;
18
+ }
6
19
  deps.forEach((dep) => toValue(dep));
7
- const query = queryFn(queryBuilder());
8
- const compiled = compileQuery(query);
9
- compiled.start();
10
- return compiled;
11
- });
12
- const state = computed(() => {
13
- return useStore(compiledQuery.value.results.asStoreMap()).value;
14
- });
15
- const data = computed(() => {
16
- return useStore(compiledQuery.value.results.asStoreArray()).value;
20
+ if (typeof unwrappedParam === `function`) {
21
+ return createLiveQueryCollection({
22
+ query: unwrappedParam,
23
+ startSync: true
24
+ });
25
+ } else {
26
+ return createLiveQueryCollection({
27
+ ...unwrappedParam,
28
+ startSync: true
29
+ });
30
+ }
17
31
  });
18
- watch(compiledQuery, (newQuery, oldQuery, onInvalidate) => {
19
- if (newQuery.state === `stopped`) {
20
- newQuery.start();
32
+ const state = reactive(/* @__PURE__ */ new Map());
33
+ const internalData = reactive([]);
34
+ const data = computed(() => internalData);
35
+ const syncDataFromCollection = (currentCollection) => {
36
+ internalData.length = 0;
37
+ internalData.push(...Array.from(currentCollection.values()));
38
+ };
39
+ let currentUnsubscribe = null;
40
+ watchEffect((onInvalidate) => {
41
+ const currentCollection = collection.value;
42
+ if (currentUnsubscribe) {
43
+ currentUnsubscribe();
44
+ }
45
+ state.clear();
46
+ for (const [key, value] of currentCollection.entries()) {
47
+ state.set(key, value);
48
+ }
49
+ syncDataFromCollection(currentCollection);
50
+ currentUnsubscribe = currentCollection.subscribeChanges(
51
+ (changes) => {
52
+ for (const change of changes) {
53
+ switch (change.type) {
54
+ case `insert`:
55
+ case `update`:
56
+ state.set(change.key, change.value);
57
+ break;
58
+ case `delete`:
59
+ state.delete(change.key);
60
+ break;
61
+ }
62
+ }
63
+ syncDataFromCollection(currentCollection);
64
+ }
65
+ );
66
+ if (currentCollection.status === `idle`) {
67
+ currentCollection.preload().catch(console.error);
21
68
  }
22
69
  onInvalidate(() => {
23
- oldQuery.stop();
70
+ if (currentUnsubscribe) {
71
+ currentUnsubscribe();
72
+ currentUnsubscribe = null;
73
+ }
24
74
  });
25
75
  });
76
+ const instance = getCurrentInstance();
77
+ if (instance) {
78
+ onUnmounted(() => {
79
+ if (currentUnsubscribe) {
80
+ currentUnsubscribe();
81
+ }
82
+ });
83
+ }
26
84
  return {
27
- state,
85
+ state: computed(() => state),
28
86
  data,
29
- collection: computed(() => compiledQuery.value.results)
87
+ collection: computed(() => collection.value)
30
88
  };
31
89
  }
32
90
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import { computed, toValue, watch } from \"vue\"\nimport { useStore } from \"@tanstack/vue-store\"\nimport { compileQuery, queryBuilder } from \"@tanstack/db\"\nimport type {\n Collection,\n Context,\n InitialQueryBuilder,\n QueryBuilder,\n ResultsFromContext,\n Schema,\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>>\n}\n\nexport function useLiveQuery<\n TResultContext extends Context<Schema> = Context<Schema>,\n>(\n queryFn: (\n q: InitialQueryBuilder<Context<Schema>>\n ) => QueryBuilder<TResultContext>,\n deps: Array<MaybeRefOrGetter<unknown>> = []\n): UseLiveQueryReturn<ResultsFromContext<TResultContext>> {\n const compiledQuery = computed(() => {\n // Just reference deps to make computed reactive to them\n deps.forEach((dep) => toValue(dep))\n\n const query = queryFn(queryBuilder())\n const compiled = compileQuery(query)\n compiled.start()\n return compiled\n })\n\n const state = computed(() => {\n return useStore(compiledQuery.value.results.asStoreMap()).value\n })\n const data = computed(() => {\n return useStore(compiledQuery.value.results.asStoreArray()).value\n })\n\n watch(compiledQuery, (newQuery, oldQuery, onInvalidate) => {\n if (newQuery.state === `stopped`) {\n newQuery.start()\n }\n\n onInvalidate(() => {\n oldQuery.stop()\n })\n })\n\n return {\n state,\n data,\n collection: computed(() => compiledQuery.value.results),\n }\n}\n"],"names":[],"mappings":";;;AAmBO,SAAS,aAGd,SAGA,OAAyC,IACe;AAClD,QAAA,gBAAgB,SAAS,MAAM;AAEnC,SAAK,QAAQ,CAAC,QAAQ,QAAQ,GAAG,CAAC;AAE5B,UAAA,QAAQ,QAAQ,cAAc;AAC9B,UAAA,WAAW,aAAa,KAAK;AACnC,aAAS,MAAM;AACR,WAAA;AAAA,EAAA,CACR;AAEK,QAAA,QAAQ,SAAS,MAAM;AAC3B,WAAO,SAAS,cAAc,MAAM,QAAQ,WAAY,CAAA,EAAE;AAAA,EAAA,CAC3D;AACK,QAAA,OAAO,SAAS,MAAM;AAC1B,WAAO,SAAS,cAAc,MAAM,QAAQ,aAAc,CAAA,EAAE;AAAA,EAAA,CAC7D;AAED,QAAM,eAAe,CAAC,UAAU,UAAU,iBAAiB;AACrD,QAAA,SAAS,UAAU,WAAW;AAChC,eAAS,MAAM;AAAA,IAAA;AAGjB,iBAAa,MAAM;AACjB,eAAS,KAAK;AAAA,IAAA,CACf;AAAA,EAAA,CACF;AAEM,SAAA;AAAA,IACL;AAAA,IACA;AAAA,IACA,YAAY,SAAS,MAAM,cAAc,MAAM,OAAO;AAAA,EACxD;AACF;"}
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;"}
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.13",
4
+ "version": "0.0.15",
5
5
  "author": "Kyle Mathews",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -16,13 +16,12 @@
16
16
  "typescript"
17
17
  ],
18
18
  "dependencies": {
19
- "@tanstack/vue-store": "^0.7.0",
20
- "@tanstack/db": "0.0.14"
19
+ "@tanstack/db": "0.0.16"
21
20
  },
22
21
  "devDependencies": {
23
22
  "@electric-sql/client": "1.0.0",
24
- "@vitest/coverage-istanbul": "^3.0.9",
25
23
  "@vitejs/plugin-vue": "^5.2.4",
24
+ "@vitest/coverage-istanbul": "^3.0.9",
26
25
  "vue": "^3.5.13"
27
26
  },
28
27
  "exports": {
@@ -1,60 +1,196 @@
1
- import { computed, toValue, watch } from "vue"
2
- import { useStore } from "@tanstack/vue-store"
3
- import { compileQuery, queryBuilder } from "@tanstack/db"
1
+ import {
2
+ computed,
3
+ getCurrentInstance,
4
+ onUnmounted,
5
+ reactive,
6
+ toValue,
7
+ watchEffect,
8
+ } from "vue"
9
+ import { createLiveQueryCollection } from "@tanstack/db"
4
10
  import type {
11
+ ChangeMessage,
5
12
  Collection,
6
13
  Context,
14
+ GetResult,
7
15
  InitialQueryBuilder,
16
+ LiveQueryCollectionConfig,
8
17
  QueryBuilder,
9
- ResultsFromContext,
10
- Schema,
11
18
  } from "@tanstack/db"
12
19
  import type { ComputedRef, MaybeRefOrGetter } from "vue"
13
20
 
14
21
  export interface UseLiveQueryReturn<T extends object> {
15
22
  state: ComputedRef<Map<string | number, T>>
16
23
  data: ComputedRef<Array<T>>
17
- collection: ComputedRef<Collection<T>>
24
+ collection: ComputedRef<Collection<T, string | number, {}>>
18
25
  }
19
26
 
27
+ export interface UseLiveQueryReturnWithCollection<
28
+ T extends object,
29
+ TKey extends string | number,
30
+ TUtils extends Record<string, any>,
31
+ > {
32
+ state: ComputedRef<Map<TKey, T>>
33
+ data: ComputedRef<Array<T>>
34
+ collection: ComputedRef<Collection<T, TKey, TUtils>>
35
+ }
36
+
37
+ // Overload 1: Accept just the query function
38
+ export function useLiveQuery<TContext extends Context>(
39
+ queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
40
+ deps?: Array<MaybeRefOrGetter<unknown>>
41
+ ): UseLiveQueryReturn<GetResult<TContext>>
42
+
43
+ // Overload 2: Accept config object
44
+ export function useLiveQuery<TContext extends Context>(
45
+ config: LiveQueryCollectionConfig<TContext>,
46
+ deps?: Array<MaybeRefOrGetter<unknown>>
47
+ ): UseLiveQueryReturn<GetResult<TContext>>
48
+
49
+ // Overload 3: Accept pre-created live query collection (can be reactive)
20
50
  export function useLiveQuery<
21
- TResultContext extends Context<Schema> = Context<Schema>,
51
+ TResult extends object,
52
+ TKey extends string | number,
53
+ TUtils extends Record<string, any>,
22
54
  >(
23
- queryFn: (
24
- q: InitialQueryBuilder<Context<Schema>>
25
- ) => QueryBuilder<TResultContext>,
55
+ liveQueryCollection: MaybeRefOrGetter<Collection<TResult, TKey, TUtils>>
56
+ ): UseLiveQueryReturnWithCollection<TResult, TKey, TUtils>
57
+
58
+ // Implementation
59
+ export function useLiveQuery(
60
+ configOrQueryOrCollection: any,
26
61
  deps: Array<MaybeRefOrGetter<unknown>> = []
27
- ): UseLiveQueryReturn<ResultsFromContext<TResultContext>> {
28
- const compiledQuery = computed(() => {
29
- // Just reference deps to make computed reactive to them
62
+ ): UseLiveQueryReturn<any> | UseLiveQueryReturnWithCollection<any, any, any> {
63
+ const collection = computed(() => {
64
+ // First check if the original parameter might be a ref/getter
65
+ // by seeing if toValue returns something different than the original
66
+ let unwrappedParam = configOrQueryOrCollection
67
+ try {
68
+ const potentiallyUnwrapped = toValue(configOrQueryOrCollection)
69
+ if (potentiallyUnwrapped !== configOrQueryOrCollection) {
70
+ unwrappedParam = potentiallyUnwrapped
71
+ }
72
+ } catch {
73
+ // If toValue fails, use original parameter
74
+ unwrappedParam = configOrQueryOrCollection
75
+ }
76
+
77
+ // Check if it's already a collection by checking for specific collection methods
78
+ const isCollection =
79
+ unwrappedParam &&
80
+ typeof unwrappedParam === `object` &&
81
+ typeof unwrappedParam.subscribeChanges === `function` &&
82
+ typeof unwrappedParam.startSyncImmediate === `function` &&
83
+ typeof unwrappedParam.id === `string`
84
+
85
+ if (isCollection) {
86
+ // It's already a collection, ensure sync is started for Vue hooks
87
+ unwrappedParam.startSyncImmediate()
88
+ return unwrappedParam
89
+ }
90
+
91
+ // Reference deps to make computed reactive to them
30
92
  deps.forEach((dep) => toValue(dep))
31
93
 
32
- const query = queryFn(queryBuilder())
33
- const compiled = compileQuery(query)
34
- compiled.start()
35
- return compiled
94
+ // Ensure we always start sync for Vue hooks
95
+ if (typeof unwrappedParam === `function`) {
96
+ return createLiveQueryCollection({
97
+ query: unwrappedParam,
98
+ startSync: true,
99
+ })
100
+ } else {
101
+ return createLiveQueryCollection({
102
+ ...unwrappedParam,
103
+ startSync: true,
104
+ })
105
+ }
36
106
  })
37
107
 
38
- const state = computed(() => {
39
- return useStore(compiledQuery.value.results.asStoreMap()).value
40
- })
41
- const data = computed(() => {
42
- return useStore(compiledQuery.value.results.asStoreArray()).value
43
- })
108
+ // Reactive state that gets updated granularly through change events
109
+ const state = reactive(new Map<string | number, any>())
110
+
111
+ // Reactive data array that maintains sorted order
112
+ const internalData = reactive<Array<any>>([])
44
113
 
45
- watch(compiledQuery, (newQuery, oldQuery, onInvalidate) => {
46
- if (newQuery.state === `stopped`) {
47
- newQuery.start()
114
+ // Computed wrapper for the data to match expected return type
115
+ const data = computed(() => internalData)
116
+
117
+ // Helper to sync data array from collection in correct order
118
+ const syncDataFromCollection = (
119
+ currentCollection: Collection<any, any, any>
120
+ ) => {
121
+ internalData.length = 0
122
+ internalData.push(...Array.from(currentCollection.values()))
123
+ }
124
+
125
+ // Track current unsubscribe function
126
+ let currentUnsubscribe: (() => void) | null = null
127
+
128
+ // Watch for collection changes and subscribe to updates
129
+ watchEffect((onInvalidate) => {
130
+ const currentCollection = collection.value
131
+
132
+ // Clean up previous subscription
133
+ if (currentUnsubscribe) {
134
+ currentUnsubscribe()
135
+ }
136
+
137
+ // Initialize state with current collection data
138
+ state.clear()
139
+ for (const [key, value] of currentCollection.entries()) {
140
+ state.set(key, value)
48
141
  }
49
142
 
143
+ // Initialize data array in correct order
144
+ syncDataFromCollection(currentCollection)
145
+
146
+ // Subscribe to collection changes with granular updates
147
+ currentUnsubscribe = currentCollection.subscribeChanges(
148
+ (changes: Array<ChangeMessage<any>>) => {
149
+ // Apply each change individually to the reactive state
150
+ for (const change of changes) {
151
+ switch (change.type) {
152
+ case `insert`:
153
+ case `update`:
154
+ state.set(change.key, change.value)
155
+ break
156
+ case `delete`:
157
+ state.delete(change.key)
158
+ break
159
+ }
160
+ }
161
+
162
+ // Update the data array to maintain sorted order
163
+ syncDataFromCollection(currentCollection)
164
+ }
165
+ )
166
+
167
+ // Preload collection data if not already started
168
+ if (currentCollection.status === `idle`) {
169
+ currentCollection.preload().catch(console.error)
170
+ }
171
+
172
+ // Cleanup when effect is invalidated
50
173
  onInvalidate(() => {
51
- oldQuery.stop()
174
+ if (currentUnsubscribe) {
175
+ currentUnsubscribe()
176
+ currentUnsubscribe = null
177
+ }
52
178
  })
53
179
  })
54
180
 
181
+ // Cleanup on unmount (only if we're in a component context)
182
+ const instance = getCurrentInstance()
183
+ if (instance) {
184
+ onUnmounted(() => {
185
+ if (currentUnsubscribe) {
186
+ currentUnsubscribe()
187
+ }
188
+ })
189
+ }
190
+
55
191
  return {
56
- state,
192
+ state: computed(() => state),
57
193
  data,
58
- collection: computed(() => compiledQuery.value.results),
194
+ collection: computed(() => collection.value),
59
195
  }
60
196
  }