nuxt-convex-module 0.0.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/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/chunks/index.mjs +64 -0
- package/dist/devtools-client/200.html +1 -0
- package/dist/devtools-client/404.html +1 -0
- package/dist/devtools-client/_nuxt/B29GGJ5n.js +1 -0
- package/dist/devtools-client/_nuxt/BSDEpQet.js +1 -0
- package/dist/devtools-client/_nuxt/BY23OQOo.js +1 -0
- package/dist/devtools-client/_nuxt/BZYRaL-G.js +1 -0
- package/dist/devtools-client/_nuxt/Byw6FxnC.js +1 -0
- package/dist/devtools-client/_nuxt/CJJ_hcZN.js +1 -0
- package/dist/devtools-client/_nuxt/CS6WMFOx.js +2 -0
- package/dist/devtools-client/_nuxt/CZbAGcNC.js +1 -0
- package/dist/devtools-client/_nuxt/Cr6_aklh.js +2 -0
- package/dist/devtools-client/_nuxt/DNfpyBVU.js +3 -0
- package/dist/devtools-client/_nuxt/DPWXUmcI.js +1 -0
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -0
- package/dist/devtools-client/_nuxt/builds/meta/a7708498-bd09-4de2-b5ab-19b380293c77.json +1 -0
- package/dist/devtools-client/_nuxt/dy_xXcv9.js +1 -0
- package/dist/devtools-client/_nuxt/entry.DbTJhkHT.css +1 -0
- package/dist/devtools-client/_nuxt/error-404.CwGGO2Kz.css +1 -0
- package/dist/devtools-client/_nuxt/error-500.8ZJqCQm8.css +1 -0
- package/dist/devtools-client/_nuxt/ncgEdbEt.js +1 -0
- package/dist/devtools-client/_nuxt/queries.CE_dtt9-.css +1 -0
- package/dist/devtools-client/auth/index.html +1 -0
- package/dist/devtools-client/icon.svg +6 -0
- package/dist/devtools-client/index.html +1 -0
- package/dist/devtools-client/logs/index.html +1 -0
- package/dist/devtools-client/queries/index.html +1 -0
- package/dist/module.d.ts +189 -0
- package/dist/module.js +489 -0
- package/dist/module.json +13 -0
- package/dist/runtime/auth0/vue/index.d.ts +58 -0
- package/dist/runtime/auth0/vue/index.js +33 -0
- package/dist/runtime/better-auth/nuxt/middleware.d.ts +14 -0
- package/dist/runtime/better-auth/nuxt/middleware.js +39 -0
- package/dist/runtime/better-auth/nuxt/proxy.d.ts +6 -0
- package/dist/runtime/better-auth/nuxt/proxy.js +11 -0
- package/dist/runtime/better-auth/nuxt/server.d.ts +106 -0
- package/dist/runtime/better-auth/nuxt/server.js +123 -0
- package/dist/runtime/better-auth/vue/auth-boundary.d.ts +131 -0
- package/dist/runtime/better-auth/vue/auth-boundary.js +78 -0
- package/dist/runtime/better-auth/vue/client.d.ts +358 -0
- package/dist/runtime/better-auth/vue/client.js +5 -0
- package/dist/runtime/better-auth/vue/cross-domain.d.ts +39 -0
- package/dist/runtime/better-auth/vue/cross-domain.js +45 -0
- package/dist/runtime/better-auth/vue/hydration.d.ts +20 -0
- package/dist/runtime/better-auth/vue/hydration.js +52 -0
- package/dist/runtime/better-auth/vue/index.d.ts +12 -0
- package/dist/runtime/better-auth/vue/index.js +6 -0
- package/dist/runtime/better-auth/vue/plugin.client.d.ts +6 -0
- package/dist/runtime/better-auth/vue/plugin.client.js +32 -0
- package/dist/runtime/better-auth/vue/plugin.server.d.ts +24 -0
- package/dist/runtime/better-auth/vue/plugin.server.js +42 -0
- package/dist/runtime/better-auth/vue/use-auth.d.ts +92 -0
- package/dist/runtime/better-auth/vue/use-auth.js +82 -0
- package/dist/runtime/clerk/vue/index.d.ts +81 -0
- package/dist/runtime/clerk/vue/index.js +49 -0
- package/dist/runtime/devtools/bridge.d.ts +12 -0
- package/dist/runtime/devtools/bridge.js +191 -0
- package/dist/runtime/devtools/plugin.client.d.ts +8 -0
- package/dist/runtime/devtools/plugin.client.js +34 -0
- package/dist/runtime/devtools/types.d.ts +78 -0
- package/dist/runtime/devtools/types.js +0 -0
- package/dist/runtime/nuxt/composables/use-async-query.d.ts +144 -0
- package/dist/runtime/nuxt/composables/use-async-query.js +104 -0
- package/dist/runtime/nuxt/config.d.ts +7 -0
- package/dist/runtime/nuxt/config.js +12 -0
- package/dist/runtime/nuxt/index.d.ts +136 -0
- package/dist/runtime/nuxt/index.js +91 -0
- package/dist/runtime/polar/vue/components.d.ts +184 -0
- package/dist/runtime/polar/vue/components.js +115 -0
- package/dist/runtime/vue/auth/helpers.d.ts +67 -0
- package/dist/runtime/vue/auth/helpers.js +50 -0
- package/dist/runtime/vue/auth/index.d.ts +125 -0
- package/dist/runtime/vue/auth/index.js +97 -0
- package/dist/runtime/vue/client.d.ts +305 -0
- package/dist/runtime/vue/client.js +382 -0
- package/dist/runtime/vue/composables/use-action.d.ts +62 -0
- package/dist/runtime/vue/composables/use-action.js +13 -0
- package/dist/runtime/vue/composables/use-connection-state.d.ts +30 -0
- package/dist/runtime/vue/composables/use-connection-state.js +14 -0
- package/dist/runtime/vue/composables/use-mutation.d.ts +76 -0
- package/dist/runtime/vue/composables/use-mutation.js +34 -0
- package/dist/runtime/vue/composables/use-paginated-query.d.ts +132 -0
- package/dist/runtime/vue/composables/use-paginated-query.js +513 -0
- package/dist/runtime/vue/composables/use-queries.d.ts +62 -0
- package/dist/runtime/vue/composables/use-queries.js +37 -0
- package/dist/runtime/vue/composables/use-query.d.ts +87 -0
- package/dist/runtime/vue/composables/use-query.js +53 -0
- package/dist/runtime/vue/composables/use-storage-url.d.ts +46 -0
- package/dist/runtime/vue/composables/use-storage-url.js +9 -0
- package/dist/runtime/vue/composables/use-subscription.d.ts +15 -0
- package/dist/runtime/vue/composables/use-subscription.js +33 -0
- package/dist/runtime/vue/composables/use-upload-queue.d.ts +117 -0
- package/dist/runtime/vue/composables/use-upload-queue.js +115 -0
- package/dist/runtime/vue/composables/use-upload.d.ts +134 -0
- package/dist/runtime/vue/composables/use-upload.js +114 -0
- package/dist/runtime/vue/hydration.d.ts +66 -0
- package/dist/runtime/vue/hydration.js +46 -0
- package/dist/runtime/vue/index.d.ts +94 -0
- package/dist/runtime/vue/index.js +42 -0
- package/dist/runtime/vue/plugin.d.ts +18 -0
- package/dist/runtime/vue/plugin.js +16 -0
- package/dist/runtime/vue/provide.d.ts +39 -0
- package/dist/runtime/vue/provide.js +15 -0
- package/dist/runtime/vue/queries-observer.d.ts +41 -0
- package/dist/runtime/vue/queries-observer.js +126 -0
- package/dist/types.d.ts +3 -0
- package/package.json +233 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { getFunctionName } from "convex/server";
|
|
2
|
+
import { ConvexError, compareValues, convexToJson } from "convex/values";
|
|
3
|
+
import { computed, shallowRef, toValue } from "vue";
|
|
4
|
+
import { useConvex } from "../client.js";
|
|
5
|
+
import { useConvexQueries } from "./use-queries.js";
|
|
6
|
+
const splitQuery = (key, splitCursor, continueCursor) => (prevState) => {
|
|
7
|
+
const queries = { ...prevState.queries };
|
|
8
|
+
const splitKey1 = prevState.nextPageKey;
|
|
9
|
+
const splitKey2 = prevState.nextPageKey + 1;
|
|
10
|
+
const nextPageKey = prevState.nextPageKey + 2;
|
|
11
|
+
queries[splitKey1] = {
|
|
12
|
+
query: prevState.query,
|
|
13
|
+
args: {
|
|
14
|
+
...prevState.args,
|
|
15
|
+
paginationOpts: {
|
|
16
|
+
...prevState.queries[key].args.paginationOpts,
|
|
17
|
+
endCursor: splitCursor
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
queries[splitKey2] = {
|
|
22
|
+
query: prevState.query,
|
|
23
|
+
args: {
|
|
24
|
+
...prevState.args,
|
|
25
|
+
paginationOpts: {
|
|
26
|
+
...prevState.queries[key].args.paginationOpts,
|
|
27
|
+
cursor: splitCursor,
|
|
28
|
+
endCursor: continueCursor
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const ongoingSplits = { ...prevState.ongoingSplits };
|
|
33
|
+
ongoingSplits[key] = [splitKey1, splitKey2];
|
|
34
|
+
return {
|
|
35
|
+
...prevState,
|
|
36
|
+
nextPageKey,
|
|
37
|
+
queries,
|
|
38
|
+
ongoingSplits
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const completeSplitQuery = (key) => (prevState) => {
|
|
42
|
+
const completedSplit = prevState.ongoingSplits[key];
|
|
43
|
+
if (completedSplit === void 0) {
|
|
44
|
+
return prevState;
|
|
45
|
+
}
|
|
46
|
+
const queries = { ...prevState.queries };
|
|
47
|
+
delete queries[key];
|
|
48
|
+
const ongoingSplits = { ...prevState.ongoingSplits };
|
|
49
|
+
delete ongoingSplits[key];
|
|
50
|
+
let pageKeys = prevState.pageKeys.slice();
|
|
51
|
+
const pageIndex = prevState.pageKeys.findIndex((v) => v === key);
|
|
52
|
+
if (pageIndex >= 0) {
|
|
53
|
+
pageKeys = [
|
|
54
|
+
...prevState.pageKeys.slice(0, pageIndex),
|
|
55
|
+
...completedSplit,
|
|
56
|
+
...prevState.pageKeys.slice(pageIndex + 1)
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
...prevState,
|
|
61
|
+
queries,
|
|
62
|
+
pageKeys,
|
|
63
|
+
ongoingSplits
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
function isPaginatedQueryValue(value) {
|
|
67
|
+
return typeof value === "object" && value !== null && "page" in value && Array.isArray(value.page) && "isDone" in value && "continueCursor" in value;
|
|
68
|
+
}
|
|
69
|
+
const INTERNAL_ERROR_STATUS = "Error";
|
|
70
|
+
const SPLIT_PAGE_STATUSES = ["SplitRecommended", "SplitRequired"];
|
|
71
|
+
export function usePaginatedQuery(query, args, options) {
|
|
72
|
+
const internal = usePaginatedQueryInternal(
|
|
73
|
+
() => query,
|
|
74
|
+
args,
|
|
75
|
+
() => options
|
|
76
|
+
);
|
|
77
|
+
return splitToRefs(internal);
|
|
78
|
+
}
|
|
79
|
+
function splitToRefs(internal) {
|
|
80
|
+
const current = () => {
|
|
81
|
+
const value = internal.value;
|
|
82
|
+
if (value.status === INTERNAL_ERROR_STATUS) throw value.error;
|
|
83
|
+
return value;
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
results: computed(() => current().results),
|
|
87
|
+
status: computed(() => current().status),
|
|
88
|
+
isLoading: computed(() => current().isLoading),
|
|
89
|
+
loadMore: (numItems) => {
|
|
90
|
+
current().loadMore(numItems);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function usePaginatedQueryInternal(query, args, options) {
|
|
95
|
+
const initialOptions = toValue(options);
|
|
96
|
+
if (typeof initialOptions?.initialNumItems !== "number" || initialOptions.initialNumItems < 0) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`\`options.initialNumItems\` must be a positive number. Received \`${initialOptions?.initialNumItems}\`.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const convex = useConvex();
|
|
102
|
+
const logger = convex.logger;
|
|
103
|
+
function createInitialState(q, currentArgs, skip, initialNumItems) {
|
|
104
|
+
const id = nextPaginationId();
|
|
105
|
+
return {
|
|
106
|
+
query: q,
|
|
107
|
+
args: currentArgs,
|
|
108
|
+
id,
|
|
109
|
+
nextPageKey: 1,
|
|
110
|
+
pageKeys: skip ? [] : [0],
|
|
111
|
+
queries: skip ? {} : {
|
|
112
|
+
0: {
|
|
113
|
+
query: q,
|
|
114
|
+
args: {
|
|
115
|
+
...currentArgs,
|
|
116
|
+
paginationOpts: {
|
|
117
|
+
numItems: initialNumItems,
|
|
118
|
+
cursor: null,
|
|
119
|
+
id
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
ongoingSplits: {},
|
|
125
|
+
skip
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const initialQuery = toValue(query);
|
|
129
|
+
const initialRawArgs = toValue(args);
|
|
130
|
+
const initialSkip = initialRawArgs === "skip";
|
|
131
|
+
const initialArgsObject = initialSkip ? {} : initialRawArgs;
|
|
132
|
+
const { initialNumItems: initialInitialNumItems } = toValue(options);
|
|
133
|
+
const state = shallowRef(
|
|
134
|
+
createInitialState(initialQuery, initialArgsObject, initialSkip, initialInitialNumItems)
|
|
135
|
+
);
|
|
136
|
+
const queriesInput = computed(() => {
|
|
137
|
+
const q = toValue(query);
|
|
138
|
+
const rawArgs = toValue(args);
|
|
139
|
+
const skip = rawArgs === "skip";
|
|
140
|
+
const argsObject = skip ? {} : rawArgs;
|
|
141
|
+
const { initialNumItems } = toValue(options);
|
|
142
|
+
const currentState = state.value;
|
|
143
|
+
const needsReset = getFunctionName(q) !== getFunctionName(currentState.query) || JSON.stringify(convexToJson(argsObject)) !== JSON.stringify(convexToJson(currentState.args)) || skip !== currentState.skip;
|
|
144
|
+
if (needsReset) {
|
|
145
|
+
state.value = createInitialState(q, argsObject, skip, initialNumItems);
|
|
146
|
+
}
|
|
147
|
+
return state.value.queries;
|
|
148
|
+
});
|
|
149
|
+
const allResults = useConvexQueries(queriesInput);
|
|
150
|
+
function resetState() {
|
|
151
|
+
const q = toValue(query);
|
|
152
|
+
const rawArgs = toValue(args);
|
|
153
|
+
const skip = rawArgs === "skip";
|
|
154
|
+
const argsObject = skip ? {} : rawArgs;
|
|
155
|
+
const { initialNumItems } = toValue(options);
|
|
156
|
+
state.value = createInitialState(q, argsObject, skip, initialNumItems);
|
|
157
|
+
}
|
|
158
|
+
return computed(() => {
|
|
159
|
+
const currState = state.value;
|
|
160
|
+
const { initialNumItems } = toValue(options);
|
|
161
|
+
const resultsObject = allResults.value;
|
|
162
|
+
let lastResult;
|
|
163
|
+
let maybeError;
|
|
164
|
+
const allItems = [];
|
|
165
|
+
for (const pageKey of currState.pageKeys) {
|
|
166
|
+
const currResult = resultsObject[pageKey];
|
|
167
|
+
if (currResult === void 0) {
|
|
168
|
+
lastResult = void 0;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
if (currResult instanceof Error) {
|
|
172
|
+
const isInvalidCursor = currResult.message.includes("InvalidCursor") || currResult instanceof ConvexError && typeof currResult.data === "object" && currResult.data?.isConvexSystemError === true && currResult.data?.paginationError === "InvalidCursor";
|
|
173
|
+
if (isInvalidCursor) {
|
|
174
|
+
logger.warn?.(
|
|
175
|
+
"usePaginatedQuery hit error, resetting pagination state: " + currResult.message
|
|
176
|
+
);
|
|
177
|
+
resetState();
|
|
178
|
+
return {
|
|
179
|
+
results: [],
|
|
180
|
+
status: "LoadingFirstPage",
|
|
181
|
+
isLoading: true,
|
|
182
|
+
loadMore: noopLoadMore
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
maybeError = currResult;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
if (!isPaginatedQueryValue(currResult)) {
|
|
189
|
+
throw new Error("Paginated query returned a non-pagination result.");
|
|
190
|
+
}
|
|
191
|
+
const result = currResult;
|
|
192
|
+
const ongoingSplit = currState.ongoingSplits[pageKey];
|
|
193
|
+
if (ongoingSplit !== void 0) {
|
|
194
|
+
if (resultsObject[ongoingSplit[0]] !== void 0 && resultsObject[ongoingSplit[1]] !== void 0) {
|
|
195
|
+
state.value = completeSplitQuery(pageKey)(state.value);
|
|
196
|
+
}
|
|
197
|
+
} else if (result.splitCursor && (SPLIT_PAGE_STATUSES.includes(result.pageStatus) || result.page.length > initialNumItems * 2)) {
|
|
198
|
+
state.value = splitQuery(pageKey, result.splitCursor, result.continueCursor)(state.value);
|
|
199
|
+
}
|
|
200
|
+
if (result.pageStatus === SPLIT_PAGE_STATUSES[1]) {
|
|
201
|
+
lastResult = void 0;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
allItems.push(...result.page);
|
|
205
|
+
lastResult = result;
|
|
206
|
+
}
|
|
207
|
+
if (maybeError !== void 0) {
|
|
208
|
+
return {
|
|
209
|
+
results: allItems,
|
|
210
|
+
status: INTERNAL_ERROR_STATUS,
|
|
211
|
+
isLoading: false,
|
|
212
|
+
error: maybeError,
|
|
213
|
+
loadMore: noopLoadMore
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (lastResult === void 0) {
|
|
217
|
+
if (currState.nextPageKey === 1) {
|
|
218
|
+
return {
|
|
219
|
+
results: allItems,
|
|
220
|
+
status: "LoadingFirstPage",
|
|
221
|
+
isLoading: true,
|
|
222
|
+
loadMore: noopLoadMore
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
results: allItems,
|
|
227
|
+
status: "LoadingMore",
|
|
228
|
+
isLoading: true,
|
|
229
|
+
loadMore: noopLoadMore
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (lastResult.isDone) {
|
|
233
|
+
return {
|
|
234
|
+
results: allItems,
|
|
235
|
+
status: "Exhausted",
|
|
236
|
+
isLoading: false,
|
|
237
|
+
loadMore: noopLoadMore
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const continueCursor = lastResult.continueCursor;
|
|
241
|
+
let alreadyLoadingMore = false;
|
|
242
|
+
return {
|
|
243
|
+
results: allItems,
|
|
244
|
+
status: "CanLoadMore",
|
|
245
|
+
isLoading: false,
|
|
246
|
+
loadMore: (numItems) => {
|
|
247
|
+
if (alreadyLoadingMore) return;
|
|
248
|
+
alreadyLoadingMore = true;
|
|
249
|
+
const prev = state.value;
|
|
250
|
+
const pageKeys = [...prev.pageKeys, prev.nextPageKey];
|
|
251
|
+
const queries = { ...prev.queries };
|
|
252
|
+
queries[prev.nextPageKey] = {
|
|
253
|
+
query: prev.query,
|
|
254
|
+
// Pair the cursor with the args snapshot captured in state (not the
|
|
255
|
+
// live args input) so the page query stays consistent with the
|
|
256
|
+
// session that produced the cursor, mirroring upstream's
|
|
257
|
+
// `prevState.args`.
|
|
258
|
+
args: {
|
|
259
|
+
...prev.args,
|
|
260
|
+
paginationOpts: {
|
|
261
|
+
numItems,
|
|
262
|
+
cursor: continueCursor,
|
|
263
|
+
id: prev.id
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
state.value = {
|
|
268
|
+
...prev,
|
|
269
|
+
nextPageKey: prev.nextPageKey + 1,
|
|
270
|
+
pageKeys,
|
|
271
|
+
queries
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
let paginationId = 0;
|
|
278
|
+
function nextPaginationId() {
|
|
279
|
+
return ++paginationId;
|
|
280
|
+
}
|
|
281
|
+
export function resetPaginationId() {
|
|
282
|
+
paginationId = 0;
|
|
283
|
+
}
|
|
284
|
+
function noopLoadMore(_numItems) {
|
|
285
|
+
}
|
|
286
|
+
export const useConvexPaginatedQuery = usePaginatedQuery;
|
|
287
|
+
export function optimisticallyUpdateValueInPaginatedQuery(localStore, query, args, updateValue) {
|
|
288
|
+
const expectedArgs = JSON.stringify(convexToJson(args));
|
|
289
|
+
for (const queryResult of localStore.getAllQueries(query)) {
|
|
290
|
+
if (queryResult.value !== void 0) {
|
|
291
|
+
const { paginationOpts: _, ...innerArgs } = queryResult.args;
|
|
292
|
+
if (JSON.stringify(convexToJson(innerArgs)) === expectedArgs) {
|
|
293
|
+
const value = queryResult.value;
|
|
294
|
+
if (typeof value === "object" && value !== null && Array.isArray(value.page)) {
|
|
295
|
+
localStore.setQuery(query, queryResult.args, {
|
|
296
|
+
...value,
|
|
297
|
+
page: value.page.map(updateValue)
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
export function insertAtTop(options) {
|
|
305
|
+
const { paginatedQuery, argsToMatch, localQueryStore, item } = options;
|
|
306
|
+
const queries = localQueryStore.getAllQueries(paginatedQuery);
|
|
307
|
+
const queriesThatMatch = queries.filter((q) => matchesArgs(q.args, argsToMatch));
|
|
308
|
+
const firstPage = queriesThatMatch.find(
|
|
309
|
+
(q) => q.args.paginationOpts.cursor === null
|
|
310
|
+
);
|
|
311
|
+
if (firstPage === void 0 || firstPage.value === void 0) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const value = firstPage.value;
|
|
315
|
+
localQueryStore.setQuery(paginatedQuery, firstPage.args, {
|
|
316
|
+
...value,
|
|
317
|
+
page: [item, ...value.page]
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
export function insertAtBottomIfLoaded(options) {
|
|
321
|
+
const { paginatedQuery, argsToMatch, localQueryStore, item } = options;
|
|
322
|
+
const queries = localQueryStore.getAllQueries(paginatedQuery);
|
|
323
|
+
const queriesThatMatch = queries.filter((q) => matchesArgs(q.args, argsToMatch));
|
|
324
|
+
const lastPage = queriesThatMatch.find(
|
|
325
|
+
(q) => q.value !== void 0 && q.value.isDone
|
|
326
|
+
);
|
|
327
|
+
if (lastPage === void 0) return;
|
|
328
|
+
const value = lastPage.value;
|
|
329
|
+
localQueryStore.setQuery(paginatedQuery, lastPage.args, {
|
|
330
|
+
...value,
|
|
331
|
+
page: [...value.page, item]
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
export function insertAtPosition(options) {
|
|
335
|
+
const {
|
|
336
|
+
paginatedQuery,
|
|
337
|
+
argsToMatch,
|
|
338
|
+
sortOrder,
|
|
339
|
+
sortKeyFromItem,
|
|
340
|
+
localQueryStore,
|
|
341
|
+
item
|
|
342
|
+
} = options;
|
|
343
|
+
const queries = localQueryStore.getAllQueries(paginatedQuery);
|
|
344
|
+
const queryGroups = {};
|
|
345
|
+
for (const q of queries) {
|
|
346
|
+
if (argsToMatch !== void 0 && !Object.keys(argsToMatch).every(
|
|
347
|
+
(k) => argsToMatch[k] === q.args[k]
|
|
348
|
+
)) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
const key = JSON.stringify(
|
|
352
|
+
Object.fromEntries(
|
|
353
|
+
Object.entries(q.args).map(([k, v]) => [
|
|
354
|
+
k,
|
|
355
|
+
k === "paginationOpts" ? v.id : v
|
|
356
|
+
])
|
|
357
|
+
)
|
|
358
|
+
);
|
|
359
|
+
queryGroups[key] ??= [];
|
|
360
|
+
queryGroups[key].push(q);
|
|
361
|
+
}
|
|
362
|
+
for (const pageQueries of Object.values(queryGroups)) {
|
|
363
|
+
insertAtPositionInPages({
|
|
364
|
+
pageQueries,
|
|
365
|
+
paginatedQuery,
|
|
366
|
+
sortOrder,
|
|
367
|
+
sortKeyFromItem,
|
|
368
|
+
localQueryStore,
|
|
369
|
+
item
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function insertAtPositionInPages(options) {
|
|
374
|
+
const {
|
|
375
|
+
pageQueries,
|
|
376
|
+
sortOrder,
|
|
377
|
+
sortKeyFromItem,
|
|
378
|
+
localQueryStore,
|
|
379
|
+
item,
|
|
380
|
+
paginatedQuery
|
|
381
|
+
} = options;
|
|
382
|
+
const insertedKey = sortKeyFromItem(item);
|
|
383
|
+
const loadedPages = pageQueries.filter(
|
|
384
|
+
(q) => q.value !== void 0 && q.value.page.length > 0
|
|
385
|
+
);
|
|
386
|
+
const sortedPages = loadedPages.slice().sort((a, b) => {
|
|
387
|
+
const aKey = sortKeyFromItem(
|
|
388
|
+
a.value.page[0]
|
|
389
|
+
);
|
|
390
|
+
const bKey = sortKeyFromItem(
|
|
391
|
+
b.value.page[0]
|
|
392
|
+
);
|
|
393
|
+
return sortOrder === "asc" ? compareValues(aKey, bKey) : compareValues(bKey, aKey);
|
|
394
|
+
});
|
|
395
|
+
const firstLoadedPage = sortedPages[0];
|
|
396
|
+
if (firstLoadedPage === void 0) return;
|
|
397
|
+
const firstValue = firstLoadedPage.value;
|
|
398
|
+
const firstPageKey = sortKeyFromItem(firstValue.page[0]);
|
|
399
|
+
const isBeforeFirstPage = sortOrder === "asc" ? compareValues(insertedKey, firstPageKey) <= 0 : compareValues(insertedKey, firstPageKey) >= 0;
|
|
400
|
+
if (isBeforeFirstPage) {
|
|
401
|
+
if (firstLoadedPage.args.paginationOpts.cursor === null) {
|
|
402
|
+
localQueryStore.setQuery(paginatedQuery, firstLoadedPage.args, {
|
|
403
|
+
...firstValue,
|
|
404
|
+
page: [item, ...firstValue.page]
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const lastLoadedPage = sortedPages[sortedPages.length - 1];
|
|
410
|
+
if (lastLoadedPage === void 0) return;
|
|
411
|
+
const lastValue = lastLoadedPage.value;
|
|
412
|
+
const lastPageKey = sortKeyFromItem(
|
|
413
|
+
lastValue.page[lastValue.page.length - 1]
|
|
414
|
+
);
|
|
415
|
+
const isAfterLastPage = sortOrder === "asc" ? compareValues(insertedKey, lastPageKey) >= 0 : compareValues(insertedKey, lastPageKey) <= 0;
|
|
416
|
+
if (isAfterLastPage) {
|
|
417
|
+
if (lastValue.isDone) {
|
|
418
|
+
localQueryStore.setQuery(paginatedQuery, lastLoadedPage.args, {
|
|
419
|
+
...lastValue,
|
|
420
|
+
page: [...lastValue.page, item]
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const successorPageIndex = sortedPages.findIndex((p) => {
|
|
426
|
+
const headKey = sortKeyFromItem(
|
|
427
|
+
p.value.page[0]
|
|
428
|
+
);
|
|
429
|
+
return sortOrder === "asc" ? compareValues(headKey, insertedKey) > 0 : compareValues(headKey, insertedKey) < 0;
|
|
430
|
+
});
|
|
431
|
+
const pageToUpdate = successorPageIndex === -1 ? sortedPages[sortedPages.length - 1] : sortedPages[successorPageIndex - 1];
|
|
432
|
+
if (pageToUpdate === void 0) return;
|
|
433
|
+
const pageValue = pageToUpdate.value;
|
|
434
|
+
const indexWithinPage = pageValue.page.findIndex((e) => {
|
|
435
|
+
const k = sortKeyFromItem(e);
|
|
436
|
+
return sortOrder === "asc" ? compareValues(k, insertedKey) >= 0 : compareValues(k, insertedKey) <= 0;
|
|
437
|
+
});
|
|
438
|
+
const newPage = indexWithinPage === -1 ? [...pageValue.page, item] : [
|
|
439
|
+
...pageValue.page.slice(0, indexWithinPage),
|
|
440
|
+
item,
|
|
441
|
+
...pageValue.page.slice(indexWithinPage)
|
|
442
|
+
];
|
|
443
|
+
localQueryStore.setQuery(paginatedQuery, pageToUpdate.args, {
|
|
444
|
+
...pageValue,
|
|
445
|
+
page: newPage
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
function matchesArgs(args, argsToMatch) {
|
|
449
|
+
if (argsToMatch === void 0) return true;
|
|
450
|
+
return Object.keys(argsToMatch).every(
|
|
451
|
+
(k) => compareValues(argsToMatch[k], args[k]) === 0
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
export function usePaginatedQuery_experimental(queryOrOptions, args, options) {
|
|
455
|
+
const firstValue = toValue(queryOrOptions);
|
|
456
|
+
const isObjectOptions = typeof firstValue === "object" && firstValue !== null && "query" in firstValue;
|
|
457
|
+
if (isObjectOptions) {
|
|
458
|
+
const optsGetter = () => toValue(
|
|
459
|
+
queryOrOptions
|
|
460
|
+
);
|
|
461
|
+
const internal2 = usePaginatedQueryInternal(
|
|
462
|
+
() => optsGetter().query,
|
|
463
|
+
() => optsGetter().args,
|
|
464
|
+
() => ({ initialNumItems: optsGetter().initialNumItems })
|
|
465
|
+
);
|
|
466
|
+
return computed(
|
|
467
|
+
() => reshapeToObjectForm2(internal2.value, optsGetter().throwOnError ?? false)
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
const query = queryOrOptions;
|
|
471
|
+
const internal = usePaginatedQueryInternal(
|
|
472
|
+
() => query,
|
|
473
|
+
args,
|
|
474
|
+
() => options
|
|
475
|
+
);
|
|
476
|
+
return splitToRefs(internal);
|
|
477
|
+
}
|
|
478
|
+
function reshapeToObjectForm2(internal, throwOnError) {
|
|
479
|
+
const { results, loadMore } = internal;
|
|
480
|
+
if (internal.status === INTERNAL_ERROR_STATUS) {
|
|
481
|
+
if (throwOnError) {
|
|
482
|
+
throw internal.error;
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
// Upstream returns an empty array on error, never the pages loaded
|
|
486
|
+
// before the failure.
|
|
487
|
+
data: [],
|
|
488
|
+
status: "error",
|
|
489
|
+
canLoadMore: false,
|
|
490
|
+
isLoading: false,
|
|
491
|
+
error: internal.error,
|
|
492
|
+
loadMore
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
if (internal.status === "LoadingFirstPage" || internal.status === "LoadingMore") {
|
|
496
|
+
return {
|
|
497
|
+
data: internal.status === "LoadingFirstPage" ? void 0 : results,
|
|
498
|
+
status: "pending",
|
|
499
|
+
canLoadMore: false,
|
|
500
|
+
isLoading: true,
|
|
501
|
+
error: void 0,
|
|
502
|
+
loadMore
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
data: results,
|
|
507
|
+
status: "success",
|
|
508
|
+
canLoadMore: internal.status === "CanLoadMore",
|
|
509
|
+
isLoading: false,
|
|
510
|
+
error: void 0,
|
|
511
|
+
loadMore
|
|
512
|
+
};
|
|
513
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter, type ShallowRef } from 'vue';
|
|
2
|
+
import { type CreateWatch } from '../queries-observer.js';
|
|
3
|
+
import type { RequestForQueries } from 'convex/react';
|
|
4
|
+
/**
|
|
5
|
+
* Load a variable number of reactive Convex queries.
|
|
6
|
+
*
|
|
7
|
+
* `useQueries` is similar to {@link useQuery} but it allows
|
|
8
|
+
* loading multiple queries which can be useful for loading a dynamic number
|
|
9
|
+
* of queries without violating the rules of composables.
|
|
10
|
+
*
|
|
11
|
+
* This composable accepts an object whose keys are identifiers for each query and the
|
|
12
|
+
* values are objects of `{ query: FunctionReference, args: Record<string, Value> }`. The
|
|
13
|
+
* `query` is a FunctionReference for the Convex query function to load, and the `args` are
|
|
14
|
+
* the arguments to that function.
|
|
15
|
+
*
|
|
16
|
+
* The composable returns an object that maps each identifier to the result of the query,
|
|
17
|
+
* `undefined` if the query is still loading, or an instance of `Error` if the query
|
|
18
|
+
* threw an exception.
|
|
19
|
+
*
|
|
20
|
+
* For example if you loaded a query like:
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const results = useQueries({
|
|
23
|
+
* messagesInGeneral: {
|
|
24
|
+
* query: "listMessages",
|
|
25
|
+
* args: { channel: "#general" }
|
|
26
|
+
* }
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
* then the result would look like:
|
|
30
|
+
* ```typescript
|
|
31
|
+
* {
|
|
32
|
+
* messagesInGeneral: [{
|
|
33
|
+
* channel: "#general",
|
|
34
|
+
* body: "hello"
|
|
35
|
+
* _id: ...,
|
|
36
|
+
* _creationTime: ...
|
|
37
|
+
* }]
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* This composable returns a reactive result that updates
|
|
42
|
+
* whenever any of the query results change.
|
|
43
|
+
*
|
|
44
|
+
* Throws an error if no Convex client has been provided (see {@link useConvex}).
|
|
45
|
+
*
|
|
46
|
+
* @param queries - An object mapping identifiers to objects of
|
|
47
|
+
* `{query: string, args: Record<string, Value> }` describing which query
|
|
48
|
+
* functions to fetch. Reactive (ref/computed/getter) or plain.
|
|
49
|
+
* @returns A shallow ref of an object with the same keys as the input. The
|
|
50
|
+
* values are the result of the query function, `undefined` if it's still
|
|
51
|
+
* loading, or an `Error` if it threw an exception.
|
|
52
|
+
*
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export declare function useQueries(queries: MaybeRefOrGetter<RequestForQueries>): ShallowRef<Record<string, any | undefined | Error>>;
|
|
56
|
+
/**
|
|
57
|
+
* Internal version of `useQueries` that is exported for testing.
|
|
58
|
+
*/
|
|
59
|
+
export declare function useQueriesHelper(queries: MaybeRefOrGetter<RequestForQueries>, createWatch: CreateWatch): ShallowRef<Record<string, any | undefined | Error>>;
|
|
60
|
+
export type { RequestForQueries } from 'convex/react';
|
|
61
|
+
/** @public */
|
|
62
|
+
export declare const useConvexQueries: typeof useQueries;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { onScopeDispose, shallowRef, toValue, watchEffect } from "vue";
|
|
2
|
+
import { useConvexOrThrow } from "../client.js";
|
|
3
|
+
import { QueriesObserver } from "../queries-observer.js";
|
|
4
|
+
export function useQueries(queries) {
|
|
5
|
+
const convex = useConvexOrThrow("useQuery");
|
|
6
|
+
const createWatch = (query, args, {
|
|
7
|
+
journal,
|
|
8
|
+
paginationOptions
|
|
9
|
+
}) => {
|
|
10
|
+
if (paginationOptions) {
|
|
11
|
+
return convex.watchPaginatedQuery(query, args, paginationOptions);
|
|
12
|
+
} else {
|
|
13
|
+
return convex.watchQuery(query, args, journal ? { journal } : {});
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
return useQueriesHelper(queries, createWatch);
|
|
17
|
+
}
|
|
18
|
+
export function useQueriesHelper(queries, createWatch) {
|
|
19
|
+
const observer = new QueriesObserver(createWatch);
|
|
20
|
+
const results = shallowRef({});
|
|
21
|
+
watchEffect((onCleanup) => {
|
|
22
|
+
const currentQueries = toValue(queries);
|
|
23
|
+
if (import.meta.server) {
|
|
24
|
+
results.value = observer.getLocalResults(currentQueries);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
observer.setQueries(currentQueries);
|
|
28
|
+
results.value = observer.getLocalResults(currentQueries);
|
|
29
|
+
const unsubscribe = observer.subscribe(() => {
|
|
30
|
+
results.value = observer.getLocalResults(toValue(queries));
|
|
31
|
+
});
|
|
32
|
+
onCleanup(unsubscribe);
|
|
33
|
+
});
|
|
34
|
+
onScopeDispose(() => observer.destroy());
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
export const useConvexQueries = useQueries;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { FunctionArgs, FunctionReference } from 'convex/server';
|
|
2
|
+
import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
|
|
3
|
+
import type { UseQueryResult as ConvexUseQueryResult } from 'convex/react';
|
|
4
|
+
export type OptionalRestArgsOrSkip<FuncRef extends FunctionReference<any>> = FuncRef['_args'] extends Record<string, never> ? [args?: MaybeRefOrGetter<Record<string, never> | 'skip'>] : [args: MaybeRefOrGetter<FuncRef['_args'] | 'skip'>];
|
|
5
|
+
/**
|
|
6
|
+
* Result returned by object-form {@link useQuery_experimental}.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export type UseQueryResult<QueryResult, ThrowOnError extends boolean = false> = ConvexUseQueryResult<QueryResult, ThrowOnError>;
|
|
11
|
+
type UseQueryOptions<Query extends FunctionReference<'query'>, ThrowOnError extends boolean> = {
|
|
12
|
+
query: Query;
|
|
13
|
+
args: MaybeRefOrGetter<FunctionArgs<Query> | 'skip'>;
|
|
14
|
+
throwOnError?: ThrowOnError;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Load a reactive query within a Vue component.
|
|
18
|
+
*
|
|
19
|
+
* This Vue composable subscribes to a Convex query and updates the returned
|
|
20
|
+
* ref whenever the query result changes. The subscription is managed
|
|
21
|
+
* automatically -- it starts when the component mounts and stops when it
|
|
22
|
+
* unmounts.
|
|
23
|
+
*
|
|
24
|
+
* Throws an error if no Convex client has been provided.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```vue
|
|
28
|
+
* <script setup lang="ts">
|
|
29
|
+
* import { useQuery } from '#imports'
|
|
30
|
+
* import { api } from '#convex/api'
|
|
31
|
+
*
|
|
32
|
+
* // Reactively loads tasks; the ref updates when data changes:
|
|
33
|
+
* const tasks = useQuery(api.tasks.list, { completed: false })
|
|
34
|
+
* // tasks.value is `undefined` while loading
|
|
35
|
+
*
|
|
36
|
+
* // Pass "skip" (or a reactive getter returning it) to conditionally
|
|
37
|
+
* // disable the query:
|
|
38
|
+
* const profile = useQuery(
|
|
39
|
+
* api.users.get,
|
|
40
|
+
* computed(() => userId.value ? { userId: userId.value } : 'skip'),
|
|
41
|
+
* )
|
|
42
|
+
* </script>
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @param query - a {@link server.FunctionReference} for the public query to run
|
|
46
|
+
* like `api.dir1.dir2.filename.func`.
|
|
47
|
+
* @param args - The arguments to the query function or the string `"skip"` if
|
|
48
|
+
* the query should not be loaded. Accepts a ref, computed, or getter for
|
|
49
|
+
* reactive args.
|
|
50
|
+
* @returns a computed ref with the result of the query. Contains `undefined`
|
|
51
|
+
* while loading.
|
|
52
|
+
*
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export declare function useQuery<Query extends FunctionReference<'query'>>(query: Query, ...args: OptionalRestArgsOrSkip<Query>): ComputedRef<Query['_returnType'] | undefined>;
|
|
56
|
+
/**
|
|
57
|
+
* Load a reactive query within a Vue component using an options object.
|
|
58
|
+
*
|
|
59
|
+
* This is an experimental form of {@link useQuery} that accepts a single
|
|
60
|
+
* {@link UseQueryOptions} object instead of positional arguments.
|
|
61
|
+
*
|
|
62
|
+
* Consumers are expected to check the returned object `status` field to
|
|
63
|
+
* make proper use of the result. If an error occurs, it will be present
|
|
64
|
+
* in the result object unless `throwOnError` is `true`, in which case
|
|
65
|
+
* the error will be thrown instead.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```vue
|
|
69
|
+
* <script setup lang="ts">
|
|
70
|
+
* import { useQuery_experimental as useQuery } from '#imports'
|
|
71
|
+
* import { api } from '#convex/api'
|
|
72
|
+
*
|
|
73
|
+
* const state = useQuery({ query: api.tasks.list, args: { completed: false } })
|
|
74
|
+
* // state.value.status: 'pending' | 'success' | 'error'
|
|
75
|
+
* </script>
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @param options - Query options. Pass `args: "skip"` to disable the query.
|
|
79
|
+
* @returns a computed ref with the current query state as a
|
|
80
|
+
* {@link UseQueryResult} object.
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
84
|
+
export declare function useQuery_experimental<Query extends FunctionReference<'query'>, ThrowOnError extends boolean = false>(options: UseQueryOptions<Query, ThrowOnError>): ComputedRef<UseQueryResult<Query['_returnType'], ThrowOnError>>;
|
|
85
|
+
/** @public */
|
|
86
|
+
export declare const useConvexQuery: typeof useQuery;
|
|
87
|
+
export {};
|