convex-helpers 0.1.90 → 0.1.91

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "convex-helpers",
3
- "version": "0.1.90",
3
+ "version": "0.1.91",
4
4
  "description": "A collection of useful code to complement the official convex package.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- import type { OptionalRestArgsOrSkip, RequestForQueries } from "convex/react";
1
+ import type { OptionalRestArgsOrSkip, PaginatedQueryArgs, PaginatedQueryReference, RequestForQueries, UsePaginatedQueryReturnType } from "convex/react";
2
2
  import type { FunctionReference, FunctionReturnType } from "convex/server";
3
3
  /**
4
4
  * Load a variable number of reactive Convex queries, utilizing
@@ -70,4 +70,60 @@ export declare function useQueries(queries: RequestForQueries): Record<string, a
70
70
  * @public
71
71
  */
72
72
  export declare function useQuery<Query extends FunctionReference<"query">>(query: Query, ...queryArgs: OptionalRestArgsOrSkip<Query>): FunctionReturnType<Query> | undefined;
73
+ /**
74
+ * Load data reactively from a paginated query to a create a growing list.
75
+ *
76
+ * This can be used to power "infinite scroll" UIs.
77
+ *
78
+ * This hook must be used with public query references that match
79
+ * {@link PaginatedQueryReference}.
80
+ *
81
+ * `usePaginatedQuery` concatenates all the pages of results into a single list
82
+ * and manages the continuation cursors when requesting more items.
83
+ *
84
+ * Example usage:
85
+ * ```typescript
86
+ * const { results, status, isLoading, loadMore } = usePaginatedQuery(
87
+ * api.messages.list,
88
+ * { channel: "#general" },
89
+ * { initialNumItems: 5 }
90
+ * );
91
+ * ```
92
+ *
93
+ * If the query reference or arguments change, the pagination state will be reset
94
+ * to the first page. Similarly, if any of the pages result in an InvalidCursor
95
+ * error or an error associated with too much data, the pagination state will also
96
+ * reset to the first page.
97
+ *
98
+ * To learn more about pagination, see [Paginated Queries](https://docs.convex.dev/database/pagination).
99
+ *
100
+ * @param query - A FunctionReference to the public query function to run.
101
+ * @param args - The arguments object for the query function, excluding
102
+ * the `paginationOpts` property. That property is injected by this hook.
103
+ * @param options - An object specifying the `initialNumItems` to be loaded in
104
+ * the first page, and the `endCursorBehavior` to use.
105
+ * @param options.endCursorBehavior` controls how the `endCursor` is set on the
106
+ * last loaded page. The current behavior is to have the first request for a page
107
+ * "pin" the end of the page to the `endCursor` returned in the first request.
108
+ * This shows up as your first request growing as new items are added within
109
+ * the range of the initial page. This is tracked via a QueryJournal, which is
110
+ * not shared between clients and can have unexpected behavior, so we will be
111
+ * deprecating this behavior in favor of the new option `setOnLoadMore`.
112
+ * For `setOnLoadMore`, the `endCursor` is not inferred from the first request,
113
+ * instead the first call to `loadMore` will explicitly set the `endCursor` to
114
+ * the `continueCursor` of the last page. In the future this will not use the
115
+ * QueryJournal and will become the default behavior, resulting in the first
116
+ * page staying at the same size as `initialNumItems` until you call `loadMore`.
117
+ * Note: setting the `endCursor` on the request will re-request that page with
118
+ * the new argument, causing an effectively duplicate request per `loadMore`.
119
+ *
120
+ * @returns A {@link UsePaginatedQueryResult} that includes the currently loaded
121
+ * items, the status of the pagination, and a `loadMore` function.
122
+ *
123
+ * @public
124
+ */
125
+ export declare function usePaginatedQuery<Query extends PaginatedQueryReference>(query: Query, args: PaginatedQueryArgs<Query> | "skip", options: {
126
+ initialNumItems: number;
127
+ endCursorBehavior?: "setOnLoadMore" | "legacyQueryJournal";
128
+ }): UsePaginatedQueryReturnType<Query>;
73
129
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAE9E,OAAO,KAAK,EAEV,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,eAAe,CAAC;AAavB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,SAAS,GAAG,KAAK,CAAC,CAiCzC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EAC/D,KAAK,EAAE,KAAK,EACZ,GAAG,SAAS,EAAE,sBAAsB,CAAC,KAAK,CAAC,GAC1C,kBAAkB,CAAC,KAAK,CAAC,GAAG,SAAS,CAoBvC"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,cAAc,CAAC;AAMtB,OAAO,KAAK,EAEV,iBAAiB,EACjB,kBAAkB,EAInB,MAAM,eAAe,CAAC;AAkBvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,SAAS,GAAG,KAAK,CAAC,CAiCzC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EAC/D,KAAK,EAAE,KAAK,EACZ,GAAG,SAAS,EAAE,sBAAsB,CAAC,KAAK,CAAC,GAC1C,kBAAkB,CAAC,KAAK,CAAC,GAAG,SAAS,CAoBvC;AAoHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,SAAS,uBAAuB,EACrE,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC,GAAG,MAAM,EACxC,OAAO,EAAE;IACP,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,eAAe,GAAG,oBAAoB,CAAC;CAC5D,GACA,2BAA2B,CAAC,KAAK,CAAC,CAqPpC"}
@@ -1,8 +1,8 @@
1
- import { ConvexProvider, useQueries as useQueriesCore } from "convex/react";
1
+ import { ConvexProvider, useConvex, useQueries as useQueriesCore, } from "convex/react";
2
2
  import { getFunctionName } from "convex/server";
3
- import { useContext, useEffect, useMemo } from "react";
3
+ import { useContext, useEffect, useMemo, useState } from "react";
4
4
  import { ConvexQueryCacheContext } from "./provider.js";
5
- import { convexToJson } from "convex/values";
5
+ import { ConvexError, convexToJson, } from "convex/values";
6
6
  const uuid = typeof crypto !== "undefined" && crypto.randomUUID
7
7
  ? crypto.randomUUID.bind(crypto)
8
8
  : () => Math.random().toString(36).substring(2) +
@@ -134,3 +134,337 @@ function createQueryKey(query, args) {
134
134
  const queryKey = JSON.stringify(key);
135
135
  return queryKey;
136
136
  }
137
+ // NOTE!: We use the same ID so it's always cached, but it can mean a split is
138
+ // required off the bat if it's an old stale query result.
139
+ function nextPaginationId() {
140
+ return 0;
141
+ }
142
+ const splitQuery = (key, splitCursor, continueCursor) => (prevState) => {
143
+ const queries = { ...prevState.queries };
144
+ const splitKey1 = prevState.nextPageKey;
145
+ const splitKey2 = prevState.nextPageKey + 1;
146
+ const nextPageKey = prevState.nextPageKey + 2;
147
+ queries[splitKey1] = {
148
+ query: prevState.query,
149
+ args: {
150
+ ...prevState.args,
151
+ paginationOpts: {
152
+ ...prevState.queries[key].args.paginationOpts,
153
+ endCursor: splitCursor,
154
+ },
155
+ },
156
+ };
157
+ queries[splitKey2] = {
158
+ query: prevState.query,
159
+ args: {
160
+ ...prevState.args,
161
+ paginationOpts: {
162
+ ...prevState.queries[key].args.paginationOpts,
163
+ cursor: splitCursor,
164
+ endCursor: continueCursor,
165
+ },
166
+ },
167
+ };
168
+ const ongoingSplits = { ...prevState.ongoingSplits };
169
+ ongoingSplits[key] = [splitKey1, splitKey2];
170
+ return {
171
+ ...prevState,
172
+ nextPageKey,
173
+ queries,
174
+ ongoingSplits,
175
+ };
176
+ };
177
+ const completeSplitQuery = (key) => (prevState) => {
178
+ const completedSplit = prevState.ongoingSplits[key];
179
+ if (completedSplit === undefined) {
180
+ return prevState;
181
+ }
182
+ const queries = { ...prevState.queries };
183
+ delete queries[key];
184
+ const ongoingSplits = { ...prevState.ongoingSplits };
185
+ delete ongoingSplits[key];
186
+ let pageKeys = prevState.pageKeys.slice();
187
+ const pageIndex = prevState.pageKeys.findIndex((v) => v === key);
188
+ if (pageIndex >= 0) {
189
+ pageKeys = [
190
+ ...prevState.pageKeys.slice(0, pageIndex),
191
+ ...completedSplit,
192
+ ...prevState.pageKeys.slice(pageIndex + 1),
193
+ ];
194
+ }
195
+ return {
196
+ ...prevState,
197
+ queries,
198
+ pageKeys,
199
+ ongoingSplits,
200
+ };
201
+ };
202
+ /**
203
+ * Load data reactively from a paginated query to a create a growing list.
204
+ *
205
+ * This can be used to power "infinite scroll" UIs.
206
+ *
207
+ * This hook must be used with public query references that match
208
+ * {@link PaginatedQueryReference}.
209
+ *
210
+ * `usePaginatedQuery` concatenates all the pages of results into a single list
211
+ * and manages the continuation cursors when requesting more items.
212
+ *
213
+ * Example usage:
214
+ * ```typescript
215
+ * const { results, status, isLoading, loadMore } = usePaginatedQuery(
216
+ * api.messages.list,
217
+ * { channel: "#general" },
218
+ * { initialNumItems: 5 }
219
+ * );
220
+ * ```
221
+ *
222
+ * If the query reference or arguments change, the pagination state will be reset
223
+ * to the first page. Similarly, if any of the pages result in an InvalidCursor
224
+ * error or an error associated with too much data, the pagination state will also
225
+ * reset to the first page.
226
+ *
227
+ * To learn more about pagination, see [Paginated Queries](https://docs.convex.dev/database/pagination).
228
+ *
229
+ * @param query - A FunctionReference to the public query function to run.
230
+ * @param args - The arguments object for the query function, excluding
231
+ * the `paginationOpts` property. That property is injected by this hook.
232
+ * @param options - An object specifying the `initialNumItems` to be loaded in
233
+ * the first page, and the `endCursorBehavior` to use.
234
+ * @param options.endCursorBehavior` controls how the `endCursor` is set on the
235
+ * last loaded page. The current behavior is to have the first request for a page
236
+ * "pin" the end of the page to the `endCursor` returned in the first request.
237
+ * This shows up as your first request growing as new items are added within
238
+ * the range of the initial page. This is tracked via a QueryJournal, which is
239
+ * not shared between clients and can have unexpected behavior, so we will be
240
+ * deprecating this behavior in favor of the new option `setOnLoadMore`.
241
+ * For `setOnLoadMore`, the `endCursor` is not inferred from the first request,
242
+ * instead the first call to `loadMore` will explicitly set the `endCursor` to
243
+ * the `continueCursor` of the last page. In the future this will not use the
244
+ * QueryJournal and will become the default behavior, resulting in the first
245
+ * page staying at the same size as `initialNumItems` until you call `loadMore`.
246
+ * Note: setting the `endCursor` on the request will re-request that page with
247
+ * the new argument, causing an effectively duplicate request per `loadMore`.
248
+ *
249
+ * @returns A {@link UsePaginatedQueryResult} that includes the currently loaded
250
+ * items, the status of the pagination, and a `loadMore` function.
251
+ *
252
+ * @public
253
+ */
254
+ export function usePaginatedQuery(query, args, options) {
255
+ if (typeof options?.initialNumItems !== "number" ||
256
+ options.initialNumItems < 0) {
257
+ throw new Error(`\`options.initialNumItems\` must be a positive number. Received \`${options?.initialNumItems}\`.`);
258
+ }
259
+ const skip = args === "skip";
260
+ const argsObject = skip ? {} : args;
261
+ const queryName = getFunctionName(query);
262
+ const createInitialState = useMemo(() => {
263
+ return () => {
264
+ const id = nextPaginationId();
265
+ return {
266
+ query,
267
+ args: argsObject,
268
+ id,
269
+ nextPageKey: 1,
270
+ pageKeys: skip ? [] : [0],
271
+ queries: skip
272
+ ? {}
273
+ : {
274
+ 0: {
275
+ query,
276
+ args: {
277
+ ...argsObject,
278
+ paginationOpts: {
279
+ numItems: options.initialNumItems,
280
+ cursor: null,
281
+ id,
282
+ },
283
+ },
284
+ },
285
+ },
286
+ ongoingSplits: {},
287
+ skip,
288
+ };
289
+ };
290
+ // ESLint doesn't like that we're stringifying the args. We do this because
291
+ // we want to avoid rerendering if the args are a different
292
+ // object that serializes to the same result.
293
+ // eslint-disable-next-line react-hooks/exhaustive-deps
294
+ }, [
295
+ // eslint-disable-next-line react-hooks/exhaustive-deps
296
+ JSON.stringify(convexToJson(argsObject)),
297
+ queryName,
298
+ options.initialNumItems,
299
+ skip,
300
+ ]);
301
+ const [state, setState] = useState(createInitialState);
302
+ // `currState` is the state that we'll render based on.
303
+ let currState = state;
304
+ if (getFunctionName(query) !== getFunctionName(state.query) ||
305
+ JSON.stringify(convexToJson(argsObject)) !==
306
+ JSON.stringify(convexToJson(state.args)) ||
307
+ skip !== state.skip) {
308
+ currState = createInitialState();
309
+ setState(currState);
310
+ }
311
+ const convexClient = useConvex();
312
+ const logger = convexClient.logger;
313
+ const resultsObject = useQueries(currState.queries);
314
+ const [results, maybeLastResult] = useMemo(() => {
315
+ let currResult = undefined;
316
+ const allItems = [];
317
+ for (const pageKey of currState.pageKeys) {
318
+ currResult = resultsObject[pageKey];
319
+ if (currResult === undefined) {
320
+ break;
321
+ }
322
+ if (currResult instanceof Error) {
323
+ if (currResult.message.includes("InvalidCursor") ||
324
+ (currResult instanceof ConvexError &&
325
+ typeof currResult.data === "object" &&
326
+ currResult.data?.isConvexSystemError === true &&
327
+ currResult.data?.paginationError === "InvalidCursor")) {
328
+ // - InvalidCursor: If the cursor is invalid, probably the paginated
329
+ // database query was data-dependent and changed underneath us. The
330
+ // cursor in the params or journal no longer matches the current
331
+ // database query.
332
+ // In all cases, we want to restart pagination to throw away all our
333
+ // existing cursors.
334
+ logger.warn("usePaginatedQuery hit error, resetting pagination state: " +
335
+ currResult.message);
336
+ setState(createInitialState);
337
+ return [[], undefined];
338
+ }
339
+ else {
340
+ throw currResult;
341
+ }
342
+ }
343
+ const ongoingSplit = currState.ongoingSplits[pageKey];
344
+ if (ongoingSplit !== undefined) {
345
+ if (resultsObject[ongoingSplit[0]] !== undefined &&
346
+ resultsObject[ongoingSplit[1]] !== undefined) {
347
+ // Both pages of the split have results now. Swap them in.
348
+ setState(completeSplitQuery(pageKey));
349
+ }
350
+ }
351
+ else if (currResult.splitCursor &&
352
+ (currResult.pageStatus === "SplitRecommended" ||
353
+ currResult.pageStatus === "SplitRequired" ||
354
+ currResult.page.length > options.initialNumItems * 2)) {
355
+ // If a single page has more than double the expected number of items,
356
+ // or if the server requests a split, split the page into two.
357
+ setState(splitQuery(pageKey, currResult.splitCursor, currResult.continueCursor));
358
+ }
359
+ if (currResult.pageStatus === "SplitRequired") {
360
+ // If pageStatus is 'SplitRequired', it means the server was not able to
361
+ // fetch the full page. So we stop results before the incomplete
362
+ // page and return 'LoadingMore' while the page is splitting.
363
+ return [allItems, undefined];
364
+ }
365
+ allItems.push(...currResult.page);
366
+ }
367
+ return [allItems, currResult];
368
+ }, [
369
+ resultsObject,
370
+ currState.pageKeys,
371
+ currState.ongoingSplits,
372
+ options.initialNumItems,
373
+ createInitialState,
374
+ logger,
375
+ ]);
376
+ const statusObject = useMemo(() => {
377
+ if (maybeLastResult === undefined) {
378
+ if (currState.nextPageKey === 1) {
379
+ return {
380
+ status: "LoadingFirstPage",
381
+ isLoading: true,
382
+ loadMore: (_numItems) => {
383
+ // Intentional noop.
384
+ },
385
+ };
386
+ }
387
+ else {
388
+ return {
389
+ status: "LoadingMore",
390
+ isLoading: true,
391
+ loadMore: (_numItems) => {
392
+ // Intentional noop.
393
+ },
394
+ };
395
+ }
396
+ }
397
+ if (maybeLastResult.isDone) {
398
+ return {
399
+ status: "Exhausted",
400
+ isLoading: false,
401
+ loadMore: (_numItems) => {
402
+ // Intentional noop.
403
+ },
404
+ };
405
+ }
406
+ const continueCursor = maybeLastResult.continueCursor;
407
+ let alreadyLoadingMore = false;
408
+ return {
409
+ status: "CanLoadMore",
410
+ isLoading: false,
411
+ loadMore: (numItems) => {
412
+ if (!alreadyLoadingMore) {
413
+ alreadyLoadingMore = true;
414
+ setState((prevState) => {
415
+ let nextPageKey = prevState.nextPageKey;
416
+ const queries = { ...prevState.queries };
417
+ let ongoingSplits = prevState.ongoingSplits;
418
+ let pageKeys = prevState.pageKeys;
419
+ if (options.endCursorBehavior === "setOnLoadMore") {
420
+ const lastPageKey = prevState.pageKeys.at(-1);
421
+ const boundLastPageKey = nextPageKey;
422
+ queries[boundLastPageKey] = {
423
+ query: prevState.query,
424
+ args: {
425
+ ...prevState.args,
426
+ paginationOpts: {
427
+ ...queries[lastPageKey].args
428
+ .paginationOpts,
429
+ endCursor: continueCursor,
430
+ },
431
+ },
432
+ };
433
+ nextPageKey++;
434
+ ongoingSplits = {
435
+ ...ongoingSplits,
436
+ [lastPageKey]: [boundLastPageKey, nextPageKey],
437
+ };
438
+ }
439
+ else {
440
+ pageKeys = [...prevState.pageKeys, nextPageKey];
441
+ }
442
+ queries[nextPageKey] = {
443
+ query: prevState.query,
444
+ args: {
445
+ ...prevState.args,
446
+ paginationOpts: {
447
+ numItems,
448
+ cursor: continueCursor,
449
+ id: prevState.id,
450
+ },
451
+ },
452
+ };
453
+ nextPageKey++;
454
+ return {
455
+ ...prevState,
456
+ pageKeys,
457
+ nextPageKey,
458
+ queries,
459
+ ongoingSplits,
460
+ };
461
+ });
462
+ }
463
+ },
464
+ };
465
+ }, [maybeLastResult, currState.nextPageKey, options.endCursorBehavior]);
466
+ return {
467
+ results,
468
+ ...statusObject,
469
+ };
470
+ }
@@ -1,14 +1,32 @@
1
- import type { OptionalRestArgsOrSkip, RequestForQueries } from "convex/react";
2
- import { ConvexProvider, useQueries as useQueriesCore } from "convex/react";
1
+ import type {
2
+ OptionalRestArgsOrSkip,
3
+ PaginatedQueryArgs,
4
+ PaginatedQueryReference,
5
+ RequestForQueries,
6
+ UsePaginatedQueryReturnType,
7
+ } from "convex/react";
8
+ import {
9
+ ConvexProvider,
10
+ useConvex,
11
+ useQueries as useQueriesCore,
12
+ } from "convex/react";
3
13
  import type {
4
14
  FunctionArgs,
5
15
  FunctionReference,
6
16
  FunctionReturnType,
17
+ PaginationOptions,
18
+ paginationOptsValidator,
19
+ PaginationResult,
7
20
  } from "convex/server";
8
21
  import { getFunctionName } from "convex/server";
9
- import { useContext, useEffect, useMemo } from "react";
22
+ import { useContext, useEffect, useMemo, useState } from "react";
10
23
  import { ConvexQueryCacheContext } from "./provider.js";
11
- import { convexToJson } from "convex/values";
24
+ import {
25
+ ConvexError,
26
+ convexToJson,
27
+ type Infer,
28
+ type Value,
29
+ } from "convex/values";
12
30
 
13
31
  const uuid =
14
32
  typeof crypto !== "undefined" && crypto.randomUUID
@@ -162,3 +180,407 @@ function createQueryKey<Query extends FunctionReference<"query">>(
162
180
  const queryKey = JSON.stringify(key);
163
181
  return queryKey;
164
182
  }
183
+
184
+ // NOTE!: We use the same ID so it's always cached, but it can mean a split is
185
+ // required off the bat if it's an old stale query result.
186
+ function nextPaginationId(): number {
187
+ return 0;
188
+ }
189
+
190
+ /**
191
+ * NOTE: The below is copied verbatim from the convex package, using the cached
192
+ * useQueries implementation.
193
+ */
194
+
195
+ // Incrementing integer for each page queried in the usePaginatedQuery hook.
196
+ type QueryPageKey = number;
197
+
198
+ type UsePaginatedQueryState = {
199
+ query: FunctionReference<"query">;
200
+ args: Record<string, Value>;
201
+ id: number;
202
+ nextPageKey: QueryPageKey;
203
+ pageKeys: QueryPageKey[];
204
+ queries: Record<
205
+ QueryPageKey,
206
+ {
207
+ query: FunctionReference<"query">;
208
+ // Use the validator type as a test that it matches the args
209
+ // we generate.
210
+ args: { paginationOpts: Infer<typeof paginationOptsValidator> };
211
+ }
212
+ >;
213
+ ongoingSplits: Record<QueryPageKey, [QueryPageKey, QueryPageKey]>;
214
+ skip: boolean;
215
+ };
216
+
217
+ const splitQuery =
218
+ (key: QueryPageKey, splitCursor: string, continueCursor: string) =>
219
+ (prevState: UsePaginatedQueryState) => {
220
+ const queries = { ...prevState.queries };
221
+ const splitKey1 = prevState.nextPageKey;
222
+ const splitKey2 = prevState.nextPageKey + 1;
223
+ const nextPageKey = prevState.nextPageKey + 2;
224
+ queries[splitKey1] = {
225
+ query: prevState.query,
226
+ args: {
227
+ ...prevState.args,
228
+ paginationOpts: {
229
+ ...prevState.queries[key]!.args.paginationOpts,
230
+ endCursor: splitCursor,
231
+ },
232
+ },
233
+ };
234
+ queries[splitKey2] = {
235
+ query: prevState.query,
236
+ args: {
237
+ ...prevState.args,
238
+ paginationOpts: {
239
+ ...prevState.queries[key]!.args.paginationOpts,
240
+ cursor: splitCursor,
241
+ endCursor: continueCursor,
242
+ },
243
+ },
244
+ };
245
+ const ongoingSplits = { ...prevState.ongoingSplits };
246
+ ongoingSplits[key] = [splitKey1, splitKey2];
247
+ return {
248
+ ...prevState,
249
+ nextPageKey,
250
+ queries,
251
+ ongoingSplits,
252
+ };
253
+ };
254
+
255
+ const completeSplitQuery =
256
+ (key: QueryPageKey) => (prevState: UsePaginatedQueryState) => {
257
+ const completedSplit = prevState.ongoingSplits[key];
258
+ if (completedSplit === undefined) {
259
+ return prevState;
260
+ }
261
+ const queries = { ...prevState.queries };
262
+ delete queries[key];
263
+ const ongoingSplits = { ...prevState.ongoingSplits };
264
+ delete ongoingSplits[key];
265
+ let pageKeys = prevState.pageKeys.slice();
266
+ const pageIndex = prevState.pageKeys.findIndex((v) => v === key);
267
+ if (pageIndex >= 0) {
268
+ pageKeys = [
269
+ ...prevState.pageKeys.slice(0, pageIndex),
270
+ ...completedSplit,
271
+ ...prevState.pageKeys.slice(pageIndex + 1),
272
+ ];
273
+ }
274
+ return {
275
+ ...prevState,
276
+ queries,
277
+ pageKeys,
278
+ ongoingSplits,
279
+ };
280
+ };
281
+
282
+ /**
283
+ * Load data reactively from a paginated query to a create a growing list.
284
+ *
285
+ * This can be used to power "infinite scroll" UIs.
286
+ *
287
+ * This hook must be used with public query references that match
288
+ * {@link PaginatedQueryReference}.
289
+ *
290
+ * `usePaginatedQuery` concatenates all the pages of results into a single list
291
+ * and manages the continuation cursors when requesting more items.
292
+ *
293
+ * Example usage:
294
+ * ```typescript
295
+ * const { results, status, isLoading, loadMore } = usePaginatedQuery(
296
+ * api.messages.list,
297
+ * { channel: "#general" },
298
+ * { initialNumItems: 5 }
299
+ * );
300
+ * ```
301
+ *
302
+ * If the query reference or arguments change, the pagination state will be reset
303
+ * to the first page. Similarly, if any of the pages result in an InvalidCursor
304
+ * error or an error associated with too much data, the pagination state will also
305
+ * reset to the first page.
306
+ *
307
+ * To learn more about pagination, see [Paginated Queries](https://docs.convex.dev/database/pagination).
308
+ *
309
+ * @param query - A FunctionReference to the public query function to run.
310
+ * @param args - The arguments object for the query function, excluding
311
+ * the `paginationOpts` property. That property is injected by this hook.
312
+ * @param options - An object specifying the `initialNumItems` to be loaded in
313
+ * the first page, and the `endCursorBehavior` to use.
314
+ * @param options.endCursorBehavior` controls how the `endCursor` is set on the
315
+ * last loaded page. The current behavior is to have the first request for a page
316
+ * "pin" the end of the page to the `endCursor` returned in the first request.
317
+ * This shows up as your first request growing as new items are added within
318
+ * the range of the initial page. This is tracked via a QueryJournal, which is
319
+ * not shared between clients and can have unexpected behavior, so we will be
320
+ * deprecating this behavior in favor of the new option `setOnLoadMore`.
321
+ * For `setOnLoadMore`, the `endCursor` is not inferred from the first request,
322
+ * instead the first call to `loadMore` will explicitly set the `endCursor` to
323
+ * the `continueCursor` of the last page. In the future this will not use the
324
+ * QueryJournal and will become the default behavior, resulting in the first
325
+ * page staying at the same size as `initialNumItems` until you call `loadMore`.
326
+ * Note: setting the `endCursor` on the request will re-request that page with
327
+ * the new argument, causing an effectively duplicate request per `loadMore`.
328
+ *
329
+ * @returns A {@link UsePaginatedQueryResult} that includes the currently loaded
330
+ * items, the status of the pagination, and a `loadMore` function.
331
+ *
332
+ * @public
333
+ */
334
+ export function usePaginatedQuery<Query extends PaginatedQueryReference>(
335
+ query: Query,
336
+ args: PaginatedQueryArgs<Query> | "skip",
337
+ options: {
338
+ initialNumItems: number;
339
+ endCursorBehavior?: "setOnLoadMore" | "legacyQueryJournal";
340
+ },
341
+ ): UsePaginatedQueryReturnType<Query> {
342
+ if (
343
+ typeof options?.initialNumItems !== "number" ||
344
+ options.initialNumItems < 0
345
+ ) {
346
+ throw new Error(
347
+ `\`options.initialNumItems\` must be a positive number. Received \`${options?.initialNumItems}\`.`,
348
+ );
349
+ }
350
+ const skip = args === "skip";
351
+ const argsObject = skip ? {} : args;
352
+ const queryName = getFunctionName(query);
353
+ const createInitialState = useMemo(() => {
354
+ return () => {
355
+ const id = nextPaginationId();
356
+ return {
357
+ query,
358
+ args: argsObject as Record<string, Value>,
359
+ id,
360
+ nextPageKey: 1,
361
+ pageKeys: skip ? [] : [0],
362
+ queries: skip
363
+ ? ({} as UsePaginatedQueryState["queries"])
364
+ : {
365
+ 0: {
366
+ query,
367
+ args: {
368
+ ...argsObject,
369
+ paginationOpts: {
370
+ numItems: options.initialNumItems,
371
+ cursor: null,
372
+ id,
373
+ },
374
+ },
375
+ },
376
+ },
377
+ ongoingSplits: {},
378
+ skip,
379
+ };
380
+ };
381
+ // ESLint doesn't like that we're stringifying the args. We do this because
382
+ // we want to avoid rerendering if the args are a different
383
+ // object that serializes to the same result.
384
+ // eslint-disable-next-line react-hooks/exhaustive-deps
385
+ }, [
386
+ // eslint-disable-next-line react-hooks/exhaustive-deps
387
+ JSON.stringify(convexToJson(argsObject as Value)),
388
+ queryName,
389
+ options.initialNumItems,
390
+ skip,
391
+ ]);
392
+
393
+ const [state, setState] =
394
+ useState<UsePaginatedQueryState>(createInitialState);
395
+
396
+ // `currState` is the state that we'll render based on.
397
+ let currState = state;
398
+ if (
399
+ getFunctionName(query) !== getFunctionName(state.query) ||
400
+ JSON.stringify(convexToJson(argsObject as Value)) !==
401
+ JSON.stringify(convexToJson(state.args)) ||
402
+ skip !== state.skip
403
+ ) {
404
+ currState = createInitialState();
405
+ setState(currState);
406
+ }
407
+ const convexClient = useConvex();
408
+ const logger = convexClient.logger;
409
+
410
+ const resultsObject = useQueries(currState.queries);
411
+
412
+ const [results, maybeLastResult]: [
413
+ Value[],
414
+ undefined | PaginationResult<Value>,
415
+ ] = useMemo(() => {
416
+ let currResult: PaginationResult<Value> | undefined = undefined;
417
+
418
+ const allItems: Value[] = [];
419
+ for (const pageKey of currState.pageKeys) {
420
+ currResult = resultsObject[pageKey];
421
+ if (currResult === undefined) {
422
+ break;
423
+ }
424
+
425
+ if (currResult instanceof Error) {
426
+ if (
427
+ currResult.message.includes("InvalidCursor") ||
428
+ (currResult instanceof ConvexError &&
429
+ typeof currResult.data === "object" &&
430
+ currResult.data?.isConvexSystemError === true &&
431
+ currResult.data?.paginationError === "InvalidCursor")
432
+ ) {
433
+ // - InvalidCursor: If the cursor is invalid, probably the paginated
434
+ // database query was data-dependent and changed underneath us. The
435
+ // cursor in the params or journal no longer matches the current
436
+ // database query.
437
+
438
+ // In all cases, we want to restart pagination to throw away all our
439
+ // existing cursors.
440
+ logger.warn(
441
+ "usePaginatedQuery hit error, resetting pagination state: " +
442
+ currResult.message,
443
+ );
444
+ setState(createInitialState);
445
+ return [[], undefined];
446
+ } else {
447
+ throw currResult;
448
+ }
449
+ }
450
+ const ongoingSplit = currState.ongoingSplits[pageKey];
451
+ if (ongoingSplit !== undefined) {
452
+ if (
453
+ resultsObject[ongoingSplit[0]] !== undefined &&
454
+ resultsObject[ongoingSplit[1]] !== undefined
455
+ ) {
456
+ // Both pages of the split have results now. Swap them in.
457
+ setState(completeSplitQuery(pageKey));
458
+ }
459
+ } else if (
460
+ currResult.splitCursor &&
461
+ (currResult.pageStatus === "SplitRecommended" ||
462
+ currResult.pageStatus === "SplitRequired" ||
463
+ currResult.page.length > options.initialNumItems * 2)
464
+ ) {
465
+ // If a single page has more than double the expected number of items,
466
+ // or if the server requests a split, split the page into two.
467
+ setState(
468
+ splitQuery(
469
+ pageKey,
470
+ currResult.splitCursor,
471
+ currResult.continueCursor,
472
+ ),
473
+ );
474
+ }
475
+ if (currResult.pageStatus === "SplitRequired") {
476
+ // If pageStatus is 'SplitRequired', it means the server was not able to
477
+ // fetch the full page. So we stop results before the incomplete
478
+ // page and return 'LoadingMore' while the page is splitting.
479
+ return [allItems, undefined];
480
+ }
481
+ allItems.push(...currResult.page);
482
+ }
483
+ return [allItems, currResult];
484
+ }, [
485
+ resultsObject,
486
+ currState.pageKeys,
487
+ currState.ongoingSplits,
488
+ options.initialNumItems,
489
+ createInitialState,
490
+ logger,
491
+ ]);
492
+
493
+ const statusObject = useMemo(() => {
494
+ if (maybeLastResult === undefined) {
495
+ if (currState.nextPageKey === 1) {
496
+ return {
497
+ status: "LoadingFirstPage",
498
+ isLoading: true,
499
+ loadMore: (_numItems: number) => {
500
+ // Intentional noop.
501
+ },
502
+ } as const;
503
+ } else {
504
+ return {
505
+ status: "LoadingMore",
506
+ isLoading: true,
507
+ loadMore: (_numItems: number) => {
508
+ // Intentional noop.
509
+ },
510
+ } as const;
511
+ }
512
+ }
513
+ if (maybeLastResult.isDone) {
514
+ return {
515
+ status: "Exhausted",
516
+ isLoading: false,
517
+ loadMore: (_numItems: number) => {
518
+ // Intentional noop.
519
+ },
520
+ } as const;
521
+ }
522
+ const continueCursor = maybeLastResult.continueCursor;
523
+ let alreadyLoadingMore = false;
524
+ return {
525
+ status: "CanLoadMore",
526
+ isLoading: false,
527
+ loadMore: (numItems: number) => {
528
+ if (!alreadyLoadingMore) {
529
+ alreadyLoadingMore = true;
530
+ setState((prevState) => {
531
+ let nextPageKey = prevState.nextPageKey;
532
+ const queries = { ...prevState.queries };
533
+ let ongoingSplits = prevState.ongoingSplits;
534
+ let pageKeys = prevState.pageKeys;
535
+ if (options.endCursorBehavior === "setOnLoadMore") {
536
+ const lastPageKey = prevState.pageKeys.at(-1)!;
537
+ const boundLastPageKey = nextPageKey;
538
+ queries[boundLastPageKey] = {
539
+ query: prevState.query,
540
+ args: {
541
+ ...prevState.args,
542
+ paginationOpts: {
543
+ ...(queries[lastPageKey]!.args
544
+ .paginationOpts as unknown as PaginationOptions),
545
+ endCursor: continueCursor,
546
+ },
547
+ },
548
+ };
549
+ nextPageKey++;
550
+ ongoingSplits = {
551
+ ...ongoingSplits,
552
+ [lastPageKey]: [boundLastPageKey, nextPageKey],
553
+ };
554
+ } else {
555
+ pageKeys = [...prevState.pageKeys, nextPageKey];
556
+ }
557
+ queries[nextPageKey] = {
558
+ query: prevState.query,
559
+ args: {
560
+ ...prevState.args,
561
+ paginationOpts: {
562
+ numItems,
563
+ cursor: continueCursor,
564
+ id: prevState.id,
565
+ },
566
+ },
567
+ };
568
+ nextPageKey++;
569
+ return {
570
+ ...prevState,
571
+ pageKeys,
572
+ nextPageKey,
573
+ queries,
574
+ ongoingSplits,
575
+ };
576
+ });
577
+ }
578
+ },
579
+ } as const;
580
+ }, [maybeLastResult, currState.nextPageKey, options.endCursorBehavior]);
581
+
582
+ return {
583
+ results,
584
+ ...statusObject,
585
+ };
586
+ }
package/react/cache.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { ConvexQueryCacheProvider } from "./cache/provider.js";
2
- export { useQuery, useQueries } from "./cache/hooks.js";
2
+ export { useQuery, useQueries, usePaginatedQuery } from "./cache/hooks.js";
3
3
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
package/react/cache.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { ConvexQueryCacheProvider } from "./cache/provider.js";
2
- export { useQuery, useQueries } from "./cache/hooks.js";
2
+ export { useQuery, useQueries, usePaginatedQuery } from "./cache/hooks.js";
package/react/cache.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { ConvexQueryCacheProvider } from "./cache/provider.js";
2
- export { useQuery, useQueries } from "./cache/hooks.js";
2
+ export { useQuery, useQueries, usePaginatedQuery } from "./cache/hooks.js";
@@ -116,8 +116,8 @@ export declare const migrationsTable: import("convex/server").TableDefinition<im
116
116
  latestEnd?: number | undefined;
117
117
  cursor: string | null;
118
118
  name: string;
119
- table: string;
120
119
  isDone: boolean;
120
+ table: string;
121
121
  processed: number;
122
122
  latestStart: number;
123
123
  }, {
@@ -129,7 +129,7 @@ export declare const migrationsTable: import("convex/server").TableDefinition<im
129
129
  processed: import("convex/values").VFloat64<number, "required">;
130
130
  latestStart: import("convex/values").VFloat64<number, "required">;
131
131
  latestEnd: import("convex/values").VFloat64<number | undefined, "optional">;
132
- }, "required", "cursor" | "name" | "table" | "isDone" | "workerId" | "processed" | "latestStart" | "latestEnd">, {
132
+ }, "required", "cursor" | "name" | "isDone" | "table" | "workerId" | "processed" | "latestStart" | "latestEnd">, {
133
133
  name: ["name", "_creationTime"];
134
134
  }, {}, {}>;
135
135
  declare const migrationArgs: {
@@ -284,8 +284,8 @@ export declare function cancelMigration<DataModel extends GenericDataModel>(ctx:
284
284
  latestEnd?: number | undefined;
285
285
  cursor: string | null;
286
286
  name: string;
287
- table: string;
288
287
  isDone: boolean;
288
+ table: string;
289
289
  processed: number;
290
290
  latestStart: number;
291
291
  } | {
@@ -294,8 +294,8 @@ export declare function cancelMigration<DataModel extends GenericDataModel>(ctx:
294
294
  latestEnd?: number | undefined;
295
295
  cursor: string | null;
296
296
  name: string;
297
- table: string;
298
297
  isDone: boolean;
298
+ table: string;
299
299
  processed: number;
300
300
  latestStart: number;
301
301
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["stream.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["stream.ts"],"names":[],"mappings":"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"}
package/server/stream.js CHANGED
@@ -22,7 +22,9 @@ function makeExclusive(boundType) {
22
22
  * 2. q.eq("f1", 1).gt("f2", 2).lt("f2", 3)
23
23
  * 3. q.eq("f1", 1).eq("f2", 3).lte("f3", 2)
24
24
  */
25
- function splitRange(indexFields, startBound, endBound, startBoundType, endBoundType) {
25
+ function splitRange(indexFields,
26
+ // For descending queries, the resulting queries are reversed.
27
+ order, startBound, endBound, startBoundType, endBoundType) {
26
28
  // Three parts to the split:
27
29
  // 1. reduce down from startBound to common prefix
28
30
  // 2. range with common prefix
@@ -79,7 +81,11 @@ function splitRange(indexFields, startBound, endBound, startBoundType, endBoundT
79
81
  middleRange.push([startBoundType, indexFields[0], startValue]);
80
82
  middleRange.push([endBoundType, indexFields[0], endValue]);
81
83
  }
82
- return [...startRanges, middleRange, ...endRanges];
84
+ const ranges = [...startRanges, middleRange, ...endRanges];
85
+ if (order === "desc") {
86
+ ranges.reverse();
87
+ }
88
+ return ranges;
83
89
  }
84
90
  function rangeToQuery(range) {
85
91
  return (q) => {
@@ -226,6 +232,18 @@ class QueryStream {
226
232
  throw new Error("Cannot call .filter on query stream. use .filterWith instead.");
227
233
  }
228
234
  async paginate(opts) {
235
+ if (opts.numItems === 0) {
236
+ if (opts.cursor === null) {
237
+ throw new Error(".paginate called with cursor of null and 0 for numItems. " +
238
+ "This is not supported, as null is not a valid continueCursor. " +
239
+ "Advice: avoid calling paginate entirely in these cases.");
240
+ }
241
+ return {
242
+ page: [],
243
+ isDone: false,
244
+ continueCursor: opts.cursor,
245
+ };
246
+ }
229
247
  const order = this.getOrder();
230
248
  let newStartKey = {
231
249
  key: [],
@@ -242,7 +260,7 @@ class QueryStream {
242
260
  inclusive: true,
243
261
  };
244
262
  const maxRowsToRead = opts.maximumRowsRead;
245
- const softMaxRowsToRead = maxRowsToRead ? (3 * maxRowsToRead) / 4 : 1000;
263
+ const softMaxRowsToRead = opts.numItems + 1;
246
264
  let maxRows = opts.numItems;
247
265
  if (opts.endCursor) {
248
266
  newEndKey = {
@@ -545,14 +563,11 @@ export class OrderedStreamQuery extends StreamableQuery {
545
563
  */
546
564
  export function streamIndexRange(db, schema, table, index, bounds, order) {
547
565
  const indexFields = getIndexFields(table, index, schema);
548
- const splitBounds = splitRange(indexFields, bounds.lowerBound, bounds.upperBound, bounds.lowerBoundInclusive ? "gte" : "gt", bounds.upperBoundInclusive ? "lte" : "lt");
549
- const subQueries = [];
550
- for (const splitBound of splitBounds) {
551
- subQueries.push(stream(db, schema)
552
- .query(table)
553
- .withIndex(index, rangeToQuery(splitBound))
554
- .order(order));
555
- }
566
+ const splitBounds = splitRange(indexFields, order, bounds.lowerBound, bounds.upperBound, bounds.lowerBoundInclusive ? "gte" : "gt", bounds.upperBoundInclusive ? "lte" : "lt");
567
+ const subQueries = splitBounds.map((splitBound) => stream(db, schema)
568
+ .query(table)
569
+ .withIndex(index, rangeToQuery(splitBound))
570
+ .order(order));
556
571
  return new ConcatStreams(...subQueries);
557
572
  }
558
573
  class ReflectIndexRange {
package/server/stream.ts CHANGED
@@ -52,6 +52,8 @@ type Bound = ["gt" | "lt" | "gte" | "lte" | "eq", string, Value];
52
52
  */
53
53
  function splitRange(
54
54
  indexFields: string[],
55
+ // For descending queries, the resulting queries are reversed.
56
+ order: "asc" | "desc",
55
57
  startBound: IndexKey,
56
58
  endBound: IndexKey,
57
59
  startBoundType: "gt" | "lt" | "gte" | "lte",
@@ -116,7 +118,11 @@ function splitRange(
116
118
  middleRange.push([startBoundType, indexFields[0]!, startValue]);
117
119
  middleRange.push([endBoundType, indexFields[0]!, endValue]);
118
120
  }
119
- return [...startRanges, middleRange, ...endRanges];
121
+ const ranges = [...startRanges, middleRange, ...endRanges];
122
+ if (order === "desc") {
123
+ ranges.reverse();
124
+ }
125
+ return ranges;
120
126
  }
121
127
 
122
128
  function rangeToQuery(range: Bound[]) {
@@ -321,6 +327,20 @@ abstract class QueryStream<T extends GenericStreamItem>
321
327
  maximumRowsRead?: number;
322
328
  },
323
329
  ): Promise<PaginationResult<T>> {
330
+ if (opts.numItems === 0) {
331
+ if (opts.cursor === null) {
332
+ throw new Error(
333
+ ".paginate called with cursor of null and 0 for numItems. " +
334
+ "This is not supported, as null is not a valid continueCursor. " +
335
+ "Advice: avoid calling paginate entirely in these cases.",
336
+ );
337
+ }
338
+ return {
339
+ page: [],
340
+ isDone: false,
341
+ continueCursor: opts.cursor,
342
+ };
343
+ }
324
344
  const order = this.getOrder();
325
345
  let newStartKey = {
326
346
  key: [] as IndexKey,
@@ -337,7 +357,7 @@ abstract class QueryStream<T extends GenericStreamItem>
337
357
  inclusive: true,
338
358
  };
339
359
  const maxRowsToRead = opts.maximumRowsRead;
340
- const softMaxRowsToRead = maxRowsToRead ? (3 * maxRowsToRead) / 4 : 1000;
360
+ const softMaxRowsToRead = opts.numItems + 1;
341
361
  let maxRows: number | undefined = opts.numItems;
342
362
  if (opts.endCursor) {
343
363
  newEndKey = {
@@ -821,20 +841,18 @@ export function streamIndexRange<
821
841
  const indexFields = getIndexFields(table, index, schema);
822
842
  const splitBounds = splitRange(
823
843
  indexFields,
844
+ order,
824
845
  bounds.lowerBound,
825
846
  bounds.upperBound,
826
847
  bounds.lowerBoundInclusive ? "gte" : "gt",
827
848
  bounds.upperBoundInclusive ? "lte" : "lt",
828
849
  );
829
- const subQueries: OrderedStreamQuery<Schema, T, IndexName>[] = [];
830
- for (const splitBound of splitBounds) {
831
- subQueries.push(
832
- stream(db, schema)
833
- .query(table)
834
- .withIndex(index, rangeToQuery(splitBound))
835
- .order(order),
836
- );
837
- }
850
+ const subQueries = splitBounds.map((splitBound) =>
851
+ stream(db, schema)
852
+ .query(table)
853
+ .withIndex(index, rangeToQuery(splitBound))
854
+ .order(order),
855
+ );
838
856
  return new ConcatStreams(...subQueries);
839
857
  }
840
858