@tanstack/vue-db 0.0.12 → 0.0.14
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.
- package/dist/cjs/useLiveQuery.cjs +77 -19
- package/dist/cjs/useLiveQuery.cjs.map +1 -1
- package/dist/cjs/useLiveQuery.d.cts +10 -3
- package/dist/esm/useLiveQuery.d.ts +10 -3
- package/dist/esm/useLiveQuery.js +79 -21
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/package.json +3 -4
- package/src/useLiveQuery.ts +165 -29
|
@@ -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(
|
|
7
|
-
const
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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.
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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(() =>
|
|
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 {
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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>;
|
package/dist/esm/useLiveQuery.js
CHANGED
|
@@ -1,32 +1,90 @@
|
|
|
1
|
-
import { computed, toValue,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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(() =>
|
|
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 {
|
|
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.
|
|
4
|
+
"version": "0.0.14",
|
|
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/
|
|
20
|
-
"@tanstack/db": "0.0.13"
|
|
19
|
+
"@tanstack/db": "0.0.15"
|
|
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": {
|
package/src/useLiveQuery.ts
CHANGED
|
@@ -1,60 +1,196 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
51
|
+
TResult extends object,
|
|
52
|
+
TKey extends string | number,
|
|
53
|
+
TUtils extends Record<string, any>,
|
|
22
54
|
>(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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<
|
|
28
|
-
const
|
|
29
|
-
//
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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(() =>
|
|
194
|
+
collection: computed(() => collection.value),
|
|
59
195
|
}
|
|
60
196
|
}
|