@tanstack/solid-db 0.1.61 → 0.2.0
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/esm/useLiveQuery.d.ts +82 -52
- package/dist/esm/useLiveQuery.js +107 -58
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/package.json +2 -2
- package/src/useLiveQuery.ts +193 -129
|
@@ -4,7 +4,7 @@ import { Collection, CollectionStatus, Context, GetResult, InitialQueryBuilder,
|
|
|
4
4
|
/**
|
|
5
5
|
* Create a live query using a query function
|
|
6
6
|
* @param queryFn - Query function that defines what data to fetch
|
|
7
|
-
* @returns
|
|
7
|
+
* @returns Accessor that returns data with Suspense support, with state and status information as properties
|
|
8
8
|
* @example
|
|
9
9
|
* // Basic query with object syntax
|
|
10
10
|
* const todosQuery = useLiveQuery((q) =>
|
|
@@ -42,46 +42,68 @@ import { Collection, CollectionStatus, Context, GetResult, InitialQueryBuilder,
|
|
|
42
42
|
*
|
|
43
43
|
* return (
|
|
44
44
|
* <Switch>
|
|
45
|
-
* <Match when={todosQuery.isLoading
|
|
45
|
+
* <Match when={todosQuery.isLoading}>
|
|
46
46
|
* <div>Loading...</div>
|
|
47
47
|
* </Match>
|
|
48
|
-
* <Match when={todosQuery.isError
|
|
49
|
-
* <div>Error: {todosQuery.status
|
|
48
|
+
* <Match when={todosQuery.isError}>
|
|
49
|
+
* <div>Error: {todosQuery.status}</div>
|
|
50
50
|
* </Match>
|
|
51
|
-
* <Match when={todosQuery.isReady
|
|
52
|
-
* <For each={todosQuery
|
|
51
|
+
* <Match when={todosQuery.isReady}>
|
|
52
|
+
* <For each={todosQuery()}>
|
|
53
53
|
* {(todo) => <li key={todo.id}>{todo.text}</li>}
|
|
54
54
|
* </For>
|
|
55
55
|
* </Match>
|
|
56
56
|
* </Switch>
|
|
57
57
|
* )
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* // Use Suspense boundaries
|
|
61
|
+
* const todosQuery = useLiveQuery((q) =>
|
|
62
|
+
* q.from({ todos: todoCollection })
|
|
63
|
+
* )
|
|
64
|
+
*
|
|
65
|
+
* return (
|
|
66
|
+
* <Suspense fallback={<div>Loading...</div>}>
|
|
67
|
+
* <For each={todosQuery()}>
|
|
68
|
+
* {(todo) => <li key={todo.id}>{todo.text}</li>}
|
|
69
|
+
* </For>
|
|
70
|
+
* </Suspense>
|
|
71
|
+
* )
|
|
58
72
|
*/
|
|
59
|
-
export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>): {
|
|
60
|
-
|
|
73
|
+
export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>): Accessor<Array<GetResult<TContext>>> & {
|
|
74
|
+
/**
|
|
75
|
+
* @deprecated use function result instead
|
|
76
|
+
* query.data -> query()
|
|
77
|
+
*/
|
|
61
78
|
data: Array<GetResult<TContext>>;
|
|
62
|
-
collection: Accessor<Collection<GetResult<TContext>, string | number, {}>>;
|
|
63
|
-
status: Accessor<CollectionStatus>;
|
|
64
|
-
isLoading: Accessor<boolean>;
|
|
65
|
-
isReady: Accessor<boolean>;
|
|
66
|
-
isIdle: Accessor<boolean>;
|
|
67
|
-
isError: Accessor<boolean>;
|
|
68
|
-
isCleanedUp: Accessor<boolean>;
|
|
69
|
-
};
|
|
70
|
-
export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null): {
|
|
71
79
|
state: ReactiveMap<string | number, GetResult<TContext>>;
|
|
80
|
+
collection: Collection<GetResult<TContext>, string | number, {}>;
|
|
81
|
+
status: CollectionStatus;
|
|
82
|
+
isLoading: boolean;
|
|
83
|
+
isReady: boolean;
|
|
84
|
+
isIdle: boolean;
|
|
85
|
+
isError: boolean;
|
|
86
|
+
isCleanedUp: boolean;
|
|
87
|
+
};
|
|
88
|
+
export declare function useLiveQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | undefined | null): Accessor<Array<GetResult<TContext>>> & {
|
|
89
|
+
/**
|
|
90
|
+
* @deprecated use function result instead
|
|
91
|
+
* query.data -> query()
|
|
92
|
+
*/
|
|
72
93
|
data: Array<GetResult<TContext>>;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
94
|
+
state: ReactiveMap<string | number, GetResult<TContext>>;
|
|
95
|
+
collection: Collection<GetResult<TContext>, string | number, {}> | null;
|
|
96
|
+
status: CollectionStatus | `disabled`;
|
|
97
|
+
isLoading: boolean;
|
|
98
|
+
isReady: boolean;
|
|
99
|
+
isIdle: boolean;
|
|
100
|
+
isError: boolean;
|
|
101
|
+
isCleanedUp: boolean;
|
|
80
102
|
};
|
|
81
103
|
/**
|
|
82
104
|
* Create a live query using configuration object
|
|
83
105
|
* @param config - Configuration object with query and options
|
|
84
|
-
* @returns
|
|
106
|
+
* @returns Accessor that returns data with Suspense support, with state and status information as properties
|
|
85
107
|
* @example
|
|
86
108
|
* // Basic config object usage
|
|
87
109
|
* const todosQuery = useLiveQuery(() => ({
|
|
@@ -105,34 +127,38 @@ export declare function useLiveQuery<TContext extends Context>(queryFn: (q: Init
|
|
|
105
127
|
* }))
|
|
106
128
|
*
|
|
107
129
|
* return (
|
|
108
|
-
* <Switch fallback={<div>{itemsQuery.
|
|
109
|
-
* <Match when={itemsQuery.isLoading
|
|
130
|
+
* <Switch fallback={<div>{itemsQuery().length} items loaded</div>}>
|
|
131
|
+
* <Match when={itemsQuery.isLoading}>
|
|
110
132
|
* <div>Loading...</div>
|
|
111
133
|
* </Match>
|
|
112
|
-
* <Match when={itemsQuery.isError
|
|
134
|
+
* <Match when={itemsQuery.isError}>
|
|
113
135
|
* <div>Something went wrong</div>
|
|
114
136
|
* </Match>
|
|
115
|
-
* <Match when={!itemsQuery.isReady
|
|
137
|
+
* <Match when={!itemsQuery.isReady}>
|
|
116
138
|
* <div>Preparing...</div>
|
|
117
139
|
* </Match>
|
|
118
140
|
* </Switch>
|
|
119
141
|
* )
|
|
120
142
|
*/
|
|
121
|
-
export declare function useLiveQuery<TContext extends Context>(config: Accessor<LiveQueryCollectionConfig<TContext>>): {
|
|
122
|
-
|
|
143
|
+
export declare function useLiveQuery<TContext extends Context>(config: Accessor<LiveQueryCollectionConfig<TContext>>): Accessor<Array<GetResult<TContext>>> & {
|
|
144
|
+
/**
|
|
145
|
+
* @deprecated use function result instead
|
|
146
|
+
* query.data -> query()
|
|
147
|
+
*/
|
|
123
148
|
data: Array<GetResult<TContext>>;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
149
|
+
state: ReactiveMap<string | number, GetResult<TContext>>;
|
|
150
|
+
collection: Collection<GetResult<TContext>, string | number, {}>;
|
|
151
|
+
status: CollectionStatus;
|
|
152
|
+
isLoading: boolean;
|
|
153
|
+
isReady: boolean;
|
|
154
|
+
isIdle: boolean;
|
|
155
|
+
isError: boolean;
|
|
156
|
+
isCleanedUp: boolean;
|
|
131
157
|
};
|
|
132
158
|
/**
|
|
133
159
|
* Subscribe to an existing live query collection
|
|
134
160
|
* @param liveQueryCollection - Pre-created live query collection to subscribe to
|
|
135
|
-
* @returns
|
|
161
|
+
* @returns Accessor that returns data with Suspense support, with state and status information as properties
|
|
136
162
|
* @example
|
|
137
163
|
* // Using pre-created live query collection
|
|
138
164
|
* const myLiveQuery = createLiveQueryCollection((q) =>
|
|
@@ -146,7 +172,7 @@ export declare function useLiveQuery<TContext extends Context>(config: Accessor<
|
|
|
146
172
|
*
|
|
147
173
|
* // Use collection for mutations
|
|
148
174
|
* const handleToggle = (id) => {
|
|
149
|
-
* existingQuery.collection
|
|
175
|
+
* existingQuery.collection.update(id, draft => { draft.completed = !draft.completed })
|
|
150
176
|
* }
|
|
151
177
|
*
|
|
152
178
|
* @example
|
|
@@ -154,24 +180,28 @@ export declare function useLiveQuery<TContext extends Context>(config: Accessor<
|
|
|
154
180
|
* const sharedQuery = useLiveQuery(() => sharedCollection)
|
|
155
181
|
*
|
|
156
182
|
* return (
|
|
157
|
-
* <Switch fallback={<div><For each={sharedQuery
|
|
158
|
-
* <Match when={sharedQuery.isLoading
|
|
183
|
+
* <Switch fallback={<div><For each={sharedQuery()}>{(item) => <Item key={item.id} {...item} />}</For></div>}>
|
|
184
|
+
* <Match when={sharedQuery.isLoading}>
|
|
159
185
|
* <div>Loading...</div>
|
|
160
186
|
* </Match>
|
|
161
|
-
* <Match when={sharedQuery.isError
|
|
187
|
+
* <Match when={sharedQuery.isError}>
|
|
162
188
|
* <div>Error loading data</div>
|
|
163
189
|
* </Match>
|
|
164
190
|
* </Switch>
|
|
165
191
|
* )
|
|
166
192
|
*/
|
|
167
|
-
export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Accessor<Collection<TResult, TKey, TUtils>>): {
|
|
168
|
-
|
|
193
|
+
export declare function useLiveQuery<TResult extends object, TKey extends string | number, TUtils extends Record<string, any>>(liveQueryCollection: Accessor<Collection<TResult, TKey, TUtils>>): Accessor<Array<TResult>> & {
|
|
194
|
+
/**
|
|
195
|
+
* @deprecated use function result instead
|
|
196
|
+
* query.data -> query()
|
|
197
|
+
*/
|
|
169
198
|
data: Array<TResult>;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
199
|
+
state: ReactiveMap<TKey, TResult>;
|
|
200
|
+
collection: Collection<TResult, TKey, TUtils>;
|
|
201
|
+
status: CollectionStatus;
|
|
202
|
+
isLoading: boolean;
|
|
203
|
+
isReady: boolean;
|
|
204
|
+
isIdle: boolean;
|
|
205
|
+
isError: boolean;
|
|
206
|
+
isCleanedUp: boolean;
|
|
177
207
|
};
|
package/dist/esm/useLiveQuery.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMemo, createSignal,
|
|
1
|
+
import { createMemo, createSignal, createResource, batch, createEffect, onCleanup } from "solid-js";
|
|
2
2
|
import { ReactiveMap } from "@solid-primitives/map";
|
|
3
3
|
import { BaseQueryBuilder, createLiveQueryCollection, CollectionImpl } from "@tanstack/db";
|
|
4
4
|
import { createStore, reconcile } from "solid-js/store";
|
|
@@ -47,72 +47,121 @@ function useLiveQuery(configOrQueryOrCollection) {
|
|
|
47
47
|
(prev) => reconcile(Array.from(currentCollection.values()))(prev).filter(Boolean)
|
|
48
48
|
);
|
|
49
49
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
() => {
|
|
53
|
-
const currentCollection = collection();
|
|
50
|
+
const [getDataResource] = createResource(
|
|
51
|
+
() => ({ currentCollection: collection() }),
|
|
52
|
+
async ({ currentCollection }) => {
|
|
54
53
|
if (!currentCollection) {
|
|
55
|
-
|
|
56
|
-
state.clear();
|
|
57
|
-
setData([]);
|
|
58
|
-
if (currentUnsubscribe) {
|
|
59
|
-
currentUnsubscribe();
|
|
60
|
-
currentUnsubscribe = null;
|
|
61
|
-
}
|
|
62
|
-
return;
|
|
54
|
+
return [];
|
|
63
55
|
}
|
|
64
56
|
setStatus(currentCollection.status);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
try {
|
|
58
|
+
await currentCollection.toArrayWhenReady();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
setStatus(`error`);
|
|
61
|
+
throw error;
|
|
68
62
|
}
|
|
69
|
-
|
|
70
|
-
(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
63
|
+
batch(() => {
|
|
64
|
+
state.clear();
|
|
65
|
+
for (const [key, value] of currentCollection.entries()) {
|
|
66
|
+
state.set(key, value);
|
|
67
|
+
}
|
|
68
|
+
syncDataFromCollection(currentCollection);
|
|
69
|
+
setStatus(currentCollection.status);
|
|
70
|
+
});
|
|
71
|
+
return data;
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: `TanstackDBData`,
|
|
75
|
+
deferStream: false,
|
|
76
|
+
initialValue: data
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
createEffect(() => {
|
|
80
|
+
const currentCollection = collection();
|
|
81
|
+
if (!currentCollection) {
|
|
82
|
+
setStatus(`disabled`);
|
|
83
|
+
state.clear();
|
|
84
|
+
setData([]);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const subscription = currentCollection.subscribeChanges(
|
|
88
|
+
(changes) => {
|
|
89
|
+
batch(() => {
|
|
90
|
+
for (const change of changes) {
|
|
91
|
+
switch (change.type) {
|
|
92
|
+
case `insert`:
|
|
93
|
+
case `update`:
|
|
94
|
+
state.set(change.key, change.value);
|
|
95
|
+
break;
|
|
96
|
+
case `delete`:
|
|
97
|
+
state.delete(change.key);
|
|
98
|
+
break;
|
|
82
99
|
}
|
|
83
|
-
}
|
|
100
|
+
}
|
|
84
101
|
syncDataFromCollection(currentCollection);
|
|
85
102
|
setStatus(currentCollection.status);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
// Include initial state to ensure immediate population for pre-created collections
|
|
107
|
+
includeInitialState: true
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
onCleanup(() => {
|
|
111
|
+
subscription.unsubscribe();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
function getData() {
|
|
115
|
+
return getDataResource();
|
|
116
|
+
}
|
|
117
|
+
Object.defineProperties(getData, {
|
|
118
|
+
data: {
|
|
119
|
+
get() {
|
|
120
|
+
return getData();
|
|
94
121
|
}
|
|
95
|
-
onCleanup(() => {
|
|
96
|
-
if (currentUnsubscribe) {
|
|
97
|
-
currentUnsubscribe();
|
|
98
|
-
currentUnsubscribe = null;
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
122
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
status: {
|
|
124
|
+
get() {
|
|
125
|
+
return status();
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
collection: {
|
|
129
|
+
get() {
|
|
130
|
+
return collection();
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
state: {
|
|
134
|
+
get() {
|
|
135
|
+
return state;
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
isLoading: {
|
|
139
|
+
get() {
|
|
140
|
+
return status() === `loading`;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
isReady: {
|
|
144
|
+
get() {
|
|
145
|
+
return status() === `ready` || status() === `disabled`;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
isIdle: {
|
|
149
|
+
get() {
|
|
150
|
+
return status() === `idle`;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
isError: {
|
|
154
|
+
get() {
|
|
155
|
+
return status() === `error`;
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
isCleanedUp: {
|
|
159
|
+
get() {
|
|
160
|
+
return status() === `cleaned-up`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
return getData;
|
|
116
165
|
}
|
|
117
166
|
export {
|
|
118
167
|
useLiveQuery
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n batch,\n createComputed,\n createMemo,\n createResource,\n createSignal,\n onCleanup,\n} from 'solid-js'\nimport { ReactiveMap } from '@solid-primitives/map'\nimport {\n BaseQueryBuilder,\n CollectionImpl,\n createLiveQueryCollection,\n} from '@tanstack/db'\nimport { createStore, reconcile } from 'solid-js/store'\nimport type { Accessor } from 'solid-js'\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from '@tanstack/db'\n\n/**\n * Create a live query using a query function\n * @param queryFn - Query function that defines what data to fetch\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic query with object syntax\n * const todosQuery = useLiveQuery((q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * )\n *\n * @example\n * // With dependencies that trigger re-execution\n * const todosQuery = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority())),\n * )\n *\n * @example\n * // Join pattern\n * const personIssues = useLiveQuery((q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * )\n *\n * @example\n * // Handle loading and error states\n * const todosQuery = useLiveQuery((q) =>\n * q.from({ todos: todoCollection })\n * )\n *\n * return (\n * <Switch>\n * <Match when={todosQuery.isLoading()}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={todosQuery.isError()}>\n * <div>Error: {todosQuery.status()}</div>\n * </Match>\n * <Match when={todosQuery.isReady()}>\n * <For each={todosQuery.data()}>\n * {(todo) => <li key={todo.id}>{todo.text}</li>}\n * </For>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n): {\n state: ReactiveMap<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Accessor<Collection<GetResult<TContext>, string | number, {}>>\n status: Accessor<CollectionStatus>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n// Overload 1b: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n): {\n state: ReactiveMap<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Accessor<Collection<\n GetResult<TContext>,\n string | number,\n {}\n > | null>\n status: Accessor<CollectionStatus | `disabled`>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @returns Object with reactive data, state, and status information\n * @example\n * // Basic config object usage\n * const todosQuery = useLiveQuery(() => ({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * }))\n *\n * @example\n * // With query builder and options\n * const queryBuilder = new Query()\n * .from({ persons: collection })\n * .where(({ persons }) => gt(persons.age, 30))\n * .select(({ persons }) => ({ id: persons.id, name: persons.name }))\n *\n * const personsQuery = useLiveQuery(() => ({ query: queryBuilder }))\n *\n * @example\n * // Handle all states uniformly\n * const itemsQuery = useLiveQuery(() => ({\n * query: (q) => q.from({ items: itemCollection })\n * }))\n *\n * return (\n * <Switch fallback={<div>{itemsQuery.data.length} items loaded</div>}>\n * <Match when={itemsQuery.isLoading()}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={itemsQuery.isError()}>\n * <div>Something went wrong</div>\n * </Match>\n * <Match when={!itemsQuery.isReady()}>\n * <div>Preparing...</div>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: Accessor<LiveQueryCollectionConfig<TContext>>,\n): {\n state: ReactiveMap<string | number, GetResult<TContext>>\n data: Array<GetResult<TContext>>\n collection: Accessor<Collection<GetResult<TContext>, string | number, {}>>\n status: Accessor<CollectionStatus>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n/**\n * Subscribe to an existing live query collection\n * @param liveQueryCollection - Pre-created live query collection to subscribe to\n * @returns Object with reactive data, state, and status information\n * @example\n * // Using pre-created live query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const todosQuery = useLiveQuery(() => myLiveQuery)\n *\n * @example\n * // Access collection methods directly\n * const existingQuery = useLiveQuery(() => existingCollection)\n *\n * // Use collection for mutations\n * const handleToggle = (id) => {\n * existingQuery.collection().update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const sharedQuery = useLiveQuery(() => sharedCollection)\n *\n * return (\n * <Switch fallback={<div><For each={sharedQuery.data()}>{(item) => <Item key={item.id} {...item} />}</For></div>}>\n * <Match when={sharedQuery.isLoading()}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={sharedQuery.isError()}>\n * <div>Error loading data</div>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 3: Accept pre-created live query collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Accessor<Collection<TResult, TKey, TUtils>>,\n): {\n state: ReactiveMap<TKey, TResult>\n data: Array<TResult>\n collection: Accessor<Collection<TResult, TKey, TUtils>>\n status: Accessor<CollectionStatus>\n isLoading: Accessor<boolean>\n isReady: Accessor<boolean>\n isIdle: Accessor<boolean>\n isError: Accessor<boolean>\n isCleanedUp: Accessor<boolean>\n}\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(\n configOrQueryOrCollection: (queryFn?: any) => any,\n) {\n const collection = createMemo(\n () => {\n if (configOrQueryOrCollection.length === 1) {\n // This is a query function - check if it returns null/undefined\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = configOrQueryOrCollection(queryBuilder)\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return createLiveQueryCollection({\n query: configOrQueryOrCollection,\n startSync: true,\n })\n }\n\n const innerCollection = configOrQueryOrCollection()\n\n if (innerCollection === undefined || innerCollection === null) {\n // Disabled query - return null\n return null\n }\n\n if (innerCollection instanceof CollectionImpl) {\n innerCollection.startSyncImmediate()\n return innerCollection as Collection\n }\n\n return createLiveQueryCollection({\n ...innerCollection,\n startSync: true,\n })\n },\n undefined,\n { name: `TanstackDBCollectionMemo` },\n )\n\n // Reactive state that gets updated granularly through change events\n const state = new ReactiveMap<string | number, any>()\n\n // Reactive data array that maintains sorted order\n const [data, setData] = createStore<Array<any>>([], {\n name: `TanstackDBData`,\n })\n\n // Track collection status reactively\n const [status, setStatus] = createSignal(\n collection() ? collection()!.status : (`disabled` as const),\n {\n name: `TanstackDBStatus`,\n },\n )\n\n // Helper to sync data array from collection in correct order\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>,\n ) => {\n setData((prev) =>\n reconcile(Array.from(currentCollection.values()))(prev).filter(Boolean),\n )\n }\n\n // Track current unsubscribe function\n let currentUnsubscribe: (() => void) | null = null\n\n createComputed(\n () => {\n const currentCollection = collection()\n\n // Handle null collection (disabled query)\n if (!currentCollection) {\n setStatus(`disabled` as const)\n state.clear()\n setData([])\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n return\n }\n\n // Update status ref whenever the effect runs\n setStatus(currentCollection.status)\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 // Subscribe to collection changes with granular updates\n const subscription = currentCollection.subscribeChanges(\n (changes: Array<ChangeMessage<any>>) => {\n // Apply each change individually to the reactive state\n batch(() => {\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\n // Update the data array to maintain sorted order\n syncDataFromCollection(currentCollection)\n\n // Update status ref on every change\n setStatus(currentCollection.status)\n },\n {\n includeInitialState: true,\n },\n )\n\n currentUnsubscribe = subscription.unsubscribe.bind(subscription)\n\n // Preload collection data if not already started\n if (currentCollection.status === `idle`) {\n createResource(() => currentCollection.preload())\n }\n\n // Cleanup when computed is invalidated\n onCleanup(() => {\n if (currentUnsubscribe) {\n currentUnsubscribe()\n currentUnsubscribe = null\n }\n })\n },\n undefined,\n { name: `TanstackDBSyncComputed` },\n )\n\n return {\n state,\n data,\n collection,\n status,\n isLoading: () => status() === `loading`,\n isReady: () => status() === `ready` || status() === `disabled`,\n isIdle: () => status() === `idle`,\n isError: () => status() === `error`,\n isCleanedUp: () => status() === `cleaned-up`,\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useLiveQuery.js","sources":["../../src/useLiveQuery.ts"],"sourcesContent":["import {\n batch,\n createEffect,\n createMemo,\n createResource,\n createSignal,\n onCleanup,\n} from 'solid-js'\nimport { ReactiveMap } from '@solid-primitives/map'\nimport {\n BaseQueryBuilder,\n CollectionImpl,\n createLiveQueryCollection,\n} from '@tanstack/db'\nimport { createStore, reconcile } from 'solid-js/store'\nimport type { Accessor } from 'solid-js'\nimport type {\n ChangeMessage,\n Collection,\n CollectionStatus,\n Context,\n GetResult,\n InitialQueryBuilder,\n LiveQueryCollectionConfig,\n QueryBuilder,\n} from '@tanstack/db'\n\n/**\n * Create a live query using a query function\n * @param queryFn - Query function that defines what data to fetch\n * @returns Accessor that returns data with Suspense support, with state and status information as properties\n * @example\n * // Basic query with object syntax\n * const todosQuery = useLiveQuery((q) =>\n * q.from({ todos: todosCollection })\n * .where(({ todos }) => eq(todos.completed, false))\n * .select(({ todos }) => ({ id: todos.id, text: todos.text }))\n * )\n *\n * @example\n * // With dependencies that trigger re-execution\n * const todosQuery = useLiveQuery(\n * (q) => q.from({ todos: todosCollection })\n * .where(({ todos }) => gt(todos.priority, minPriority())),\n * )\n *\n * @example\n * // Join pattern\n * const personIssues = useLiveQuery((q) =>\n * q.from({ issues: issueCollection })\n * .join({ persons: personCollection }, ({ issues, persons }) =>\n * eq(issues.userId, persons.id)\n * )\n * .select(({ issues, persons }) => ({\n * id: issues.id,\n * title: issues.title,\n * userName: persons.name\n * }))\n * )\n *\n * @example\n * // Handle loading and error states\n * const todosQuery = useLiveQuery((q) =>\n * q.from({ todos: todoCollection })\n * )\n *\n * return (\n * <Switch>\n * <Match when={todosQuery.isLoading}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={todosQuery.isError}>\n * <div>Error: {todosQuery.status}</div>\n * </Match>\n * <Match when={todosQuery.isReady}>\n * <For each={todosQuery()}>\n * {(todo) => <li key={todo.id}>{todo.text}</li>}\n * </For>\n * </Match>\n * </Switch>\n * )\n *\n * @example\n * // Use Suspense boundaries\n * const todosQuery = useLiveQuery((q) =>\n * q.from({ todos: todoCollection })\n * )\n *\n * return (\n * <Suspense fallback={<div>Loading...</div>}>\n * <For each={todosQuery()}>\n * {(todo) => <li key={todo.id}>{todo.text}</li>}\n * </For>\n * </Suspense>\n * )\n */\n// Overload 1: Accept query function that always returns QueryBuilder\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n): Accessor<Array<GetResult<TContext>>> & {\n /**\n * @deprecated use function result instead\n * query.data -> query()\n */\n data: Array<GetResult<TContext>>\n state: ReactiveMap<string | number, GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n// Overload 1b: Accept query function that can return undefined/null\nexport function useLiveQuery<TContext extends Context>(\n queryFn: (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> | undefined | null,\n): Accessor<Array<GetResult<TContext>>> & {\n /**\n * @deprecated use function result instead\n * query.data -> query()\n */\n data: Array<GetResult<TContext>>\n state: ReactiveMap<string | number, GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}> | null\n status: CollectionStatus | `disabled`\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n/**\n * Create a live query using configuration object\n * @param config - Configuration object with query and options\n * @returns Accessor that returns data with Suspense support, with state and status information as properties\n * @example\n * // Basic config object usage\n * const todosQuery = useLiveQuery(() => ({\n * query: (q) => q.from({ todos: todosCollection }),\n * gcTime: 60000\n * }))\n *\n * @example\n * // With query builder and options\n * const queryBuilder = new Query()\n * .from({ persons: collection })\n * .where(({ persons }) => gt(persons.age, 30))\n * .select(({ persons }) => ({ id: persons.id, name: persons.name }))\n *\n * const personsQuery = useLiveQuery(() => ({ query: queryBuilder }))\n *\n * @example\n * // Handle all states uniformly\n * const itemsQuery = useLiveQuery(() => ({\n * query: (q) => q.from({ items: itemCollection })\n * }))\n *\n * return (\n * <Switch fallback={<div>{itemsQuery().length} items loaded</div>}>\n * <Match when={itemsQuery.isLoading}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={itemsQuery.isError}>\n * <div>Something went wrong</div>\n * </Match>\n * <Match when={!itemsQuery.isReady}>\n * <div>Preparing...</div>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 2: Accept config object\nexport function useLiveQuery<TContext extends Context>(\n config: Accessor<LiveQueryCollectionConfig<TContext>>,\n): Accessor<Array<GetResult<TContext>>> & {\n /**\n * @deprecated use function result instead\n * query.data -> query()\n */\n data: Array<GetResult<TContext>>\n state: ReactiveMap<string | number, GetResult<TContext>>\n collection: Collection<GetResult<TContext>, string | number, {}>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n/**\n * Subscribe to an existing live query collection\n * @param liveQueryCollection - Pre-created live query collection to subscribe to\n * @returns Accessor that returns data with Suspense support, with state and status information as properties\n * @example\n * // Using pre-created live query collection\n * const myLiveQuery = createLiveQueryCollection((q) =>\n * q.from({ todos: todosCollection }).where(({ todos }) => eq(todos.active, true))\n * )\n * const todosQuery = useLiveQuery(() => myLiveQuery)\n *\n * @example\n * // Access collection methods directly\n * const existingQuery = useLiveQuery(() => existingCollection)\n *\n * // Use collection for mutations\n * const handleToggle = (id) => {\n * existingQuery.collection.update(id, draft => { draft.completed = !draft.completed })\n * }\n *\n * @example\n * // Handle states consistently\n * const sharedQuery = useLiveQuery(() => sharedCollection)\n *\n * return (\n * <Switch fallback={<div><For each={sharedQuery()}>{(item) => <Item key={item.id} {...item} />}</For></div>}>\n * <Match when={sharedQuery.isLoading}>\n * <div>Loading...</div>\n * </Match>\n * <Match when={sharedQuery.isError}>\n * <div>Error loading data</div>\n * </Match>\n * </Switch>\n * )\n */\n// Overload 3: Accept pre-created live query collection\nexport function useLiveQuery<\n TResult extends object,\n TKey extends string | number,\n TUtils extends Record<string, any>,\n>(\n liveQueryCollection: Accessor<Collection<TResult, TKey, TUtils>>,\n): Accessor<Array<TResult>> & {\n /**\n * @deprecated use function result instead\n * query.data -> query()\n */\n data: Array<TResult>\n state: ReactiveMap<TKey, TResult>\n collection: Collection<TResult, TKey, TUtils>\n status: CollectionStatus\n isLoading: boolean\n isReady: boolean\n isIdle: boolean\n isError: boolean\n isCleanedUp: boolean\n}\n\n// Implementation - use function overloads to infer the actual collection type\nexport function useLiveQuery(\n configOrQueryOrCollection: (queryFn?: any) => any,\n) {\n const collection = createMemo(\n () => {\n if (configOrQueryOrCollection.length === 1) {\n // This is a query function - check if it returns null/undefined\n const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder\n const result = configOrQueryOrCollection(queryBuilder)\n\n if (result === undefined || result === null) {\n // Disabled query - return null\n return null\n }\n\n return createLiveQueryCollection({\n query: configOrQueryOrCollection,\n startSync: true,\n })\n }\n\n const innerCollection = configOrQueryOrCollection()\n\n if (innerCollection === undefined || innerCollection === null) {\n // Disabled query - return null\n return null\n }\n\n if (innerCollection instanceof CollectionImpl) {\n innerCollection.startSyncImmediate()\n return innerCollection as Collection\n }\n\n return createLiveQueryCollection({\n ...innerCollection,\n startSync: true,\n })\n },\n undefined,\n { name: `TanstackDBCollectionMemo` },\n )\n\n // Reactive state that gets updated granularly through change events\n const state = new ReactiveMap<string | number, any>()\n\n // Reactive data array that maintains sorted order\n const [data, setData] = createStore<Array<any>>([], {\n name: `TanstackDBData`,\n })\n\n // Track collection status reactively\n const [status, setStatus] = createSignal(\n collection() ? collection()!.status : (`disabled` as const),\n {\n name: `TanstackDBStatus`,\n },\n )\n\n // Helper to sync data array from collection in correct order\n const syncDataFromCollection = (\n currentCollection: Collection<any, any, any>,\n ) => {\n setData((prev) =>\n reconcile(Array.from(currentCollection.values()))(prev).filter(Boolean),\n )\n }\n\n const [getDataResource] = createResource(\n () => ({ currentCollection: collection() }),\n async ({ currentCollection }) => {\n if (!currentCollection) {\n return []\n }\n setStatus(currentCollection.status)\n try {\n await currentCollection.toArrayWhenReady()\n } catch (error) {\n setStatus(`error`)\n throw error\n }\n // Initialize state with current collection data\n batch(() => {\n state.clear()\n for (const [key, value] of currentCollection.entries()) {\n state.set(key, value)\n }\n syncDataFromCollection(currentCollection)\n setStatus(currentCollection.status)\n })\n return data\n },\n {\n name: `TanstackDBData`,\n deferStream: false,\n initialValue: data,\n },\n )\n\n createEffect(() => {\n const currentCollection = collection()\n if (!currentCollection) {\n setStatus(`disabled` as const)\n state.clear()\n setData([])\n return\n }\n const subscription = currentCollection.subscribeChanges(\n (changes: Array<ChangeMessage<any>>) => {\n // Apply each change individually to the reactive state\n batch(() => {\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 syncDataFromCollection(currentCollection)\n\n // Update status ref on every change\n setStatus(currentCollection.status)\n })\n },\n {\n // Include initial state to ensure immediate population for pre-created collections\n includeInitialState: true,\n },\n )\n\n onCleanup(() => {\n subscription.unsubscribe()\n })\n })\n\n // We have to remove getters from the resource function so we wrap it\n function getData() {\n return getDataResource()\n }\n\n Object.defineProperties(getData, {\n data: {\n get() {\n return getData()\n },\n },\n status: {\n get() {\n return status()\n },\n },\n collection: {\n get() {\n return collection()\n },\n },\n state: {\n get() {\n return state\n },\n },\n isLoading: {\n get() {\n return status() === `loading`\n },\n },\n isReady: {\n get() {\n return status() === `ready` || status() === `disabled`\n },\n },\n isIdle: {\n get() {\n return status() === `idle`\n },\n },\n isError: {\n get() {\n return status() === `error`\n },\n },\n isCleanedUp: {\n get() {\n return status() === `cleaned-up`\n },\n },\n })\n return getData\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/solid-db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Solid integration for @tanstack/db",
|
|
5
5
|
"author": "Kyle Mathews",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@solid-primitives/map": "^0.7.2",
|
|
38
|
-
"@tanstack/db": "0.5.
|
|
38
|
+
"@tanstack/db": "0.5.20"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"solid-js": ">=1.9.0"
|
package/src/useLiveQuery.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
batch,
|
|
3
|
-
|
|
3
|
+
createEffect,
|
|
4
4
|
createMemo,
|
|
5
5
|
createResource,
|
|
6
6
|
createSignal,
|
|
@@ -28,7 +28,7 @@ import type {
|
|
|
28
28
|
/**
|
|
29
29
|
* Create a live query using a query function
|
|
30
30
|
* @param queryFn - Query function that defines what data to fetch
|
|
31
|
-
* @returns
|
|
31
|
+
* @returns Accessor that returns data with Suspense support, with state and status information as properties
|
|
32
32
|
* @example
|
|
33
33
|
* // Basic query with object syntax
|
|
34
34
|
* const todosQuery = useLiveQuery((q) =>
|
|
@@ -66,33 +66,51 @@ import type {
|
|
|
66
66
|
*
|
|
67
67
|
* return (
|
|
68
68
|
* <Switch>
|
|
69
|
-
* <Match when={todosQuery.isLoading
|
|
69
|
+
* <Match when={todosQuery.isLoading}>
|
|
70
70
|
* <div>Loading...</div>
|
|
71
71
|
* </Match>
|
|
72
|
-
* <Match when={todosQuery.isError
|
|
73
|
-
* <div>Error: {todosQuery.status
|
|
72
|
+
* <Match when={todosQuery.isError}>
|
|
73
|
+
* <div>Error: {todosQuery.status}</div>
|
|
74
74
|
* </Match>
|
|
75
|
-
* <Match when={todosQuery.isReady
|
|
76
|
-
* <For each={todosQuery
|
|
75
|
+
* <Match when={todosQuery.isReady}>
|
|
76
|
+
* <For each={todosQuery()}>
|
|
77
77
|
* {(todo) => <li key={todo.id}>{todo.text}</li>}
|
|
78
78
|
* </For>
|
|
79
79
|
* </Match>
|
|
80
80
|
* </Switch>
|
|
81
81
|
* )
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* // Use Suspense boundaries
|
|
85
|
+
* const todosQuery = useLiveQuery((q) =>
|
|
86
|
+
* q.from({ todos: todoCollection })
|
|
87
|
+
* )
|
|
88
|
+
*
|
|
89
|
+
* return (
|
|
90
|
+
* <Suspense fallback={<div>Loading...</div>}>
|
|
91
|
+
* <For each={todosQuery()}>
|
|
92
|
+
* {(todo) => <li key={todo.id}>{todo.text}</li>}
|
|
93
|
+
* </For>
|
|
94
|
+
* </Suspense>
|
|
95
|
+
* )
|
|
82
96
|
*/
|
|
83
97
|
// Overload 1: Accept query function that always returns QueryBuilder
|
|
84
98
|
export function useLiveQuery<TContext extends Context>(
|
|
85
99
|
queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
|
|
86
|
-
): {
|
|
87
|
-
|
|
100
|
+
): Accessor<Array<GetResult<TContext>>> & {
|
|
101
|
+
/**
|
|
102
|
+
* @deprecated use function result instead
|
|
103
|
+
* query.data -> query()
|
|
104
|
+
*/
|
|
88
105
|
data: Array<GetResult<TContext>>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
state: ReactiveMap<string | number, GetResult<TContext>>
|
|
107
|
+
collection: Collection<GetResult<TContext>, string | number, {}>
|
|
108
|
+
status: CollectionStatus
|
|
109
|
+
isLoading: boolean
|
|
110
|
+
isReady: boolean
|
|
111
|
+
isIdle: boolean
|
|
112
|
+
isError: boolean
|
|
113
|
+
isCleanedUp: boolean
|
|
96
114
|
}
|
|
97
115
|
|
|
98
116
|
// Overload 1b: Accept query function that can return undefined/null
|
|
@@ -100,26 +118,26 @@ export function useLiveQuery<TContext extends Context>(
|
|
|
100
118
|
queryFn: (
|
|
101
119
|
q: InitialQueryBuilder,
|
|
102
120
|
) => QueryBuilder<TContext> | undefined | null,
|
|
103
|
-
): {
|
|
104
|
-
|
|
121
|
+
): Accessor<Array<GetResult<TContext>>> & {
|
|
122
|
+
/**
|
|
123
|
+
* @deprecated use function result instead
|
|
124
|
+
* query.data -> query()
|
|
125
|
+
*/
|
|
105
126
|
data: Array<GetResult<TContext>>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
isIdle: Accessor<boolean>
|
|
115
|
-
isError: Accessor<boolean>
|
|
116
|
-
isCleanedUp: Accessor<boolean>
|
|
127
|
+
state: ReactiveMap<string | number, GetResult<TContext>>
|
|
128
|
+
collection: Collection<GetResult<TContext>, string | number, {}> | null
|
|
129
|
+
status: CollectionStatus | `disabled`
|
|
130
|
+
isLoading: boolean
|
|
131
|
+
isReady: boolean
|
|
132
|
+
isIdle: boolean
|
|
133
|
+
isError: boolean
|
|
134
|
+
isCleanedUp: boolean
|
|
117
135
|
}
|
|
118
136
|
|
|
119
137
|
/**
|
|
120
138
|
* Create a live query using configuration object
|
|
121
139
|
* @param config - Configuration object with query and options
|
|
122
|
-
* @returns
|
|
140
|
+
* @returns Accessor that returns data with Suspense support, with state and status information as properties
|
|
123
141
|
* @example
|
|
124
142
|
* // Basic config object usage
|
|
125
143
|
* const todosQuery = useLiveQuery(() => ({
|
|
@@ -143,14 +161,14 @@ export function useLiveQuery<TContext extends Context>(
|
|
|
143
161
|
* }))
|
|
144
162
|
*
|
|
145
163
|
* return (
|
|
146
|
-
* <Switch fallback={<div>{itemsQuery.
|
|
147
|
-
* <Match when={itemsQuery.isLoading
|
|
164
|
+
* <Switch fallback={<div>{itemsQuery().length} items loaded</div>}>
|
|
165
|
+
* <Match when={itemsQuery.isLoading}>
|
|
148
166
|
* <div>Loading...</div>
|
|
149
167
|
* </Match>
|
|
150
|
-
* <Match when={itemsQuery.isError
|
|
168
|
+
* <Match when={itemsQuery.isError}>
|
|
151
169
|
* <div>Something went wrong</div>
|
|
152
170
|
* </Match>
|
|
153
|
-
* <Match when={!itemsQuery.isReady
|
|
171
|
+
* <Match when={!itemsQuery.isReady}>
|
|
154
172
|
* <div>Preparing...</div>
|
|
155
173
|
* </Match>
|
|
156
174
|
* </Switch>
|
|
@@ -159,22 +177,26 @@ export function useLiveQuery<TContext extends Context>(
|
|
|
159
177
|
// Overload 2: Accept config object
|
|
160
178
|
export function useLiveQuery<TContext extends Context>(
|
|
161
179
|
config: Accessor<LiveQueryCollectionConfig<TContext>>,
|
|
162
|
-
): {
|
|
163
|
-
|
|
180
|
+
): Accessor<Array<GetResult<TContext>>> & {
|
|
181
|
+
/**
|
|
182
|
+
* @deprecated use function result instead
|
|
183
|
+
* query.data -> query()
|
|
184
|
+
*/
|
|
164
185
|
data: Array<GetResult<TContext>>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
186
|
+
state: ReactiveMap<string | number, GetResult<TContext>>
|
|
187
|
+
collection: Collection<GetResult<TContext>, string | number, {}>
|
|
188
|
+
status: CollectionStatus
|
|
189
|
+
isLoading: boolean
|
|
190
|
+
isReady: boolean
|
|
191
|
+
isIdle: boolean
|
|
192
|
+
isError: boolean
|
|
193
|
+
isCleanedUp: boolean
|
|
172
194
|
}
|
|
173
195
|
|
|
174
196
|
/**
|
|
175
197
|
* Subscribe to an existing live query collection
|
|
176
198
|
* @param liveQueryCollection - Pre-created live query collection to subscribe to
|
|
177
|
-
* @returns
|
|
199
|
+
* @returns Accessor that returns data with Suspense support, with state and status information as properties
|
|
178
200
|
* @example
|
|
179
201
|
* // Using pre-created live query collection
|
|
180
202
|
* const myLiveQuery = createLiveQueryCollection((q) =>
|
|
@@ -188,7 +210,7 @@ export function useLiveQuery<TContext extends Context>(
|
|
|
188
210
|
*
|
|
189
211
|
* // Use collection for mutations
|
|
190
212
|
* const handleToggle = (id) => {
|
|
191
|
-
* existingQuery.collection
|
|
213
|
+
* existingQuery.collection.update(id, draft => { draft.completed = !draft.completed })
|
|
192
214
|
* }
|
|
193
215
|
*
|
|
194
216
|
* @example
|
|
@@ -196,11 +218,11 @@ export function useLiveQuery<TContext extends Context>(
|
|
|
196
218
|
* const sharedQuery = useLiveQuery(() => sharedCollection)
|
|
197
219
|
*
|
|
198
220
|
* return (
|
|
199
|
-
* <Switch fallback={<div><For each={sharedQuery
|
|
200
|
-
* <Match when={sharedQuery.isLoading
|
|
221
|
+
* <Switch fallback={<div><For each={sharedQuery()}>{(item) => <Item key={item.id} {...item} />}</For></div>}>
|
|
222
|
+
* <Match when={sharedQuery.isLoading}>
|
|
201
223
|
* <div>Loading...</div>
|
|
202
224
|
* </Match>
|
|
203
|
-
* <Match when={sharedQuery.isError
|
|
225
|
+
* <Match when={sharedQuery.isError}>
|
|
204
226
|
* <div>Error loading data</div>
|
|
205
227
|
* </Match>
|
|
206
228
|
* </Switch>
|
|
@@ -213,16 +235,20 @@ export function useLiveQuery<
|
|
|
213
235
|
TUtils extends Record<string, any>,
|
|
214
236
|
>(
|
|
215
237
|
liveQueryCollection: Accessor<Collection<TResult, TKey, TUtils>>,
|
|
216
|
-
): {
|
|
217
|
-
|
|
238
|
+
): Accessor<Array<TResult>> & {
|
|
239
|
+
/**
|
|
240
|
+
* @deprecated use function result instead
|
|
241
|
+
* query.data -> query()
|
|
242
|
+
*/
|
|
218
243
|
data: Array<TResult>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
244
|
+
state: ReactiveMap<TKey, TResult>
|
|
245
|
+
collection: Collection<TResult, TKey, TUtils>
|
|
246
|
+
status: CollectionStatus
|
|
247
|
+
isLoading: boolean
|
|
248
|
+
isReady: boolean
|
|
249
|
+
isIdle: boolean
|
|
250
|
+
isError: boolean
|
|
251
|
+
isCleanedUp: boolean
|
|
226
252
|
}
|
|
227
253
|
|
|
228
254
|
// Implementation - use function overloads to infer the actual collection type
|
|
@@ -293,91 +319,129 @@ export function useLiveQuery(
|
|
|
293
319
|
)
|
|
294
320
|
}
|
|
295
321
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
createComputed(
|
|
300
|
-
() => {
|
|
301
|
-
const currentCollection = collection()
|
|
302
|
-
|
|
303
|
-
// Handle null collection (disabled query)
|
|
322
|
+
const [getDataResource] = createResource(
|
|
323
|
+
() => ({ currentCollection: collection() }),
|
|
324
|
+
async ({ currentCollection }) => {
|
|
304
325
|
if (!currentCollection) {
|
|
305
|
-
|
|
306
|
-
state.clear()
|
|
307
|
-
setData([])
|
|
308
|
-
if (currentUnsubscribe) {
|
|
309
|
-
currentUnsubscribe()
|
|
310
|
-
currentUnsubscribe = null
|
|
311
|
-
}
|
|
312
|
-
return
|
|
326
|
+
return []
|
|
313
327
|
}
|
|
314
|
-
|
|
315
|
-
// Update status ref whenever the effect runs
|
|
316
328
|
setStatus(currentCollection.status)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
329
|
+
try {
|
|
330
|
+
await currentCollection.toArrayWhenReady()
|
|
331
|
+
} catch (error) {
|
|
332
|
+
setStatus(`error`)
|
|
333
|
+
throw error
|
|
322
334
|
}
|
|
335
|
+
// Initialize state with current collection data
|
|
336
|
+
batch(() => {
|
|
337
|
+
state.clear()
|
|
338
|
+
for (const [key, value] of currentCollection.entries()) {
|
|
339
|
+
state.set(key, value)
|
|
340
|
+
}
|
|
341
|
+
syncDataFromCollection(currentCollection)
|
|
342
|
+
setStatus(currentCollection.status)
|
|
343
|
+
})
|
|
344
|
+
return data
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: `TanstackDBData`,
|
|
348
|
+
deferStream: false,
|
|
349
|
+
initialValue: data,
|
|
350
|
+
},
|
|
351
|
+
)
|
|
323
352
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
353
|
+
createEffect(() => {
|
|
354
|
+
const currentCollection = collection()
|
|
355
|
+
if (!currentCollection) {
|
|
356
|
+
setStatus(`disabled` as const)
|
|
357
|
+
state.clear()
|
|
358
|
+
setData([])
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
const subscription = currentCollection.subscribeChanges(
|
|
362
|
+
(changes: Array<ChangeMessage<any>>) => {
|
|
363
|
+
// Apply each change individually to the reactive state
|
|
364
|
+
batch(() => {
|
|
365
|
+
for (const change of changes) {
|
|
366
|
+
switch (change.type) {
|
|
367
|
+
case `insert`:
|
|
368
|
+
case `update`:
|
|
369
|
+
state.set(change.key, change.value)
|
|
370
|
+
break
|
|
371
|
+
case `delete`:
|
|
372
|
+
state.delete(change.key)
|
|
373
|
+
break
|
|
339
374
|
}
|
|
340
|
-
}
|
|
375
|
+
}
|
|
341
376
|
|
|
342
|
-
// Update the data array to maintain sorted order
|
|
343
377
|
syncDataFromCollection(currentCollection)
|
|
344
378
|
|
|
345
379
|
// Update status ref on every change
|
|
346
380
|
setStatus(currentCollection.status)
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
381
|
+
})
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
// Include initial state to ensure immediate population for pre-created collections
|
|
385
|
+
includeInitialState: true,
|
|
386
|
+
},
|
|
387
|
+
)
|
|
352
388
|
|
|
353
|
-
|
|
389
|
+
onCleanup(() => {
|
|
390
|
+
subscription.unsubscribe()
|
|
391
|
+
})
|
|
392
|
+
})
|
|
354
393
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
394
|
+
// We have to remove getters from the resource function so we wrap it
|
|
395
|
+
function getData() {
|
|
396
|
+
return getDataResource()
|
|
397
|
+
}
|
|
359
398
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
})
|
|
399
|
+
Object.defineProperties(getData, {
|
|
400
|
+
data: {
|
|
401
|
+
get() {
|
|
402
|
+
return getData()
|
|
403
|
+
},
|
|
367
404
|
},
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
405
|
+
status: {
|
|
406
|
+
get() {
|
|
407
|
+
return status()
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
collection: {
|
|
411
|
+
get() {
|
|
412
|
+
return collection()
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
state: {
|
|
416
|
+
get() {
|
|
417
|
+
return state
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
isLoading: {
|
|
421
|
+
get() {
|
|
422
|
+
return status() === `loading`
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
isReady: {
|
|
426
|
+
get() {
|
|
427
|
+
return status() === `ready` || status() === `disabled`
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
isIdle: {
|
|
431
|
+
get() {
|
|
432
|
+
return status() === `idle`
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
isError: {
|
|
436
|
+
get() {
|
|
437
|
+
return status() === `error`
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
isCleanedUp: {
|
|
441
|
+
get() {
|
|
442
|
+
return status() === `cleaned-up`
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
})
|
|
446
|
+
return getData
|
|
383
447
|
}
|