Skip to content

Commit f5ac8bb

Browse files
ianmacartneyConvex, Inc.
authored andcommitted
add pagination result validation helper (#42729)
with returns validators being more important for components & static codegen, it's often necessary to generate the type for pagination results, and as we may change this type over time, it's important that we offer a safe version of it GitOrigin-RevId: b3d4bc3b8d7f60fc2a8ae4b887203f32198aff9c
1 parent 2132974 commit f5ac8bb

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed
Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { assert } from "../test/type_testing.js";
22
import { test } from "vitest";
3-
import { PaginationOptions, paginationOptsValidator } from "./pagination.js";
4-
import { Infer } from "../values/validator.js";
3+
import {
4+
PaginationOptions,
5+
PaginationResult,
6+
paginationOptsValidator,
7+
paginationResultValidator,
8+
} from "./pagination.js";
9+
import { Infer, v } from "../values/validator.js";
510

611
test("paginationOptsValidator matches the paginationOpts type", () => {
712
type validatorType = Infer<typeof paginationOptsValidator>;
@@ -11,3 +16,34 @@ test("paginationOptsValidator matches the paginationOpts type", () => {
1116
Required<validatorType> extends Required<PaginationOptions> ? true : false
1217
>();
1318
});
19+
20+
test("paginationResultValidator with string items", () => {
21+
const _validator = paginationResultValidator(v.string());
22+
type validatorType = Infer<typeof _validator>;
23+
type expectedType = PaginationResult<string>;
24+
25+
// Check that the inferred type matches PaginationResult<string>
26+
assert<validatorType extends expectedType ? true : false>();
27+
assert<expectedType extends validatorType ? true : false>();
28+
});
29+
30+
test("paginationResultValidator with object items", () => {
31+
const itemValidator = v.object({
32+
_id: v.id("users"),
33+
_creationTime: v.number(),
34+
name: v.string(),
35+
email: v.optional(v.string()),
36+
});
37+
const _validator = paginationResultValidator(itemValidator);
38+
type validatorType = Infer<typeof _validator>;
39+
type itemType = Infer<typeof itemValidator>;
40+
type expectedType = PaginationResult<itemType>;
41+
42+
// Check that the inferred type matches PaginationResult with the correct item type
43+
assert<validatorType extends expectedType ? true : false>();
44+
assert<expectedType extends validatorType ? true : false>();
45+
46+
// Verify the page array has the correct item type
47+
type pageType = validatorType["page"];
48+
assert<pageType extends itemType[] ? true : false>();
49+
});

npm-packages/convex/src/server/pagination.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { v } from "../values/validator.js";
2+
import type { Validator } from "../values/validators.js";
3+
import type { Value } from "../values/value.js";
24

35
/**
46
* An opaque identifier used for paginating a database query.
@@ -136,3 +138,41 @@ export const paginationOptsValidator = v.object({
136138
maximumRowsRead: v.optional(v.number()),
137139
maximumBytesRead: v.optional(v.number()),
138140
});
141+
142+
/**
143+
* A {@link values.Validator} factory for {@link PaginationResult}.
144+
*
145+
* Create a validator for the result of calling {@link OrderedQuery.paginate}
146+
* with a given item validator.
147+
*
148+
* For example:
149+
* ```ts
150+
* const paginationResultValidator = paginationResultValidator(v.object({
151+
* _id: v.id("users"),
152+
* _creationTime: v.number(),
153+
* name: v.string(),
154+
* }));
155+
* ```
156+
*
157+
* @param itemValidator - A validator for the items in the page
158+
* @returns A validator for the pagination result
159+
*
160+
* @public
161+
*/
162+
export function paginationResultValidator<
163+
T extends Validator<Value, "required", string>,
164+
>(itemValidator: T) {
165+
return v.object({
166+
page: v.array(itemValidator),
167+
continueCursor: v.string(),
168+
isDone: v.boolean(),
169+
splitCursor: v.optional(v.union(v.string(), v.null())),
170+
pageStatus: v.optional(
171+
v.union(
172+
v.literal("SplitRecommended"),
173+
v.literal("SplitRequired"),
174+
v.null(),
175+
),
176+
),
177+
});
178+
}

0 commit comments

Comments
 (0)