Skip to content

Commit f9998ed

Browse files
authored
Update the Zod example in README.md (#851)
1 parent 0d5a877 commit f9998ed

File tree

2 files changed

+80
-4
lines changed

2 files changed

+80
-4
lines changed

packages/convex-helpers/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,8 @@ See the [Stack post on Zod validation](https://stack.convex.dev/typescript-zod-f
397397
Example:
398398

399399
```js
400-
import { z } from "zod";
401-
import { zCustomQuery, zid } from "convex-helpers/server/zod";
400+
import * as z from "zod";
401+
import { zCustomQuery, zid } from "convex-helpers/server/zod4";
402402
import { NoOp } from "convex-helpers/server/customFunctions";
403403

404404
// Define this once - and customize like you would customQuery
@@ -407,7 +407,7 @@ const zodQuery = zCustomQuery(query, NoOp);
407407
export const myComplexQuery = zodQuery({
408408
args: {
409409
userId: zid("users"),
410-
email: z.string().email(),
410+
email: z.email(),
411411
num: z.number().min(0),
412412
nullableBigint: z.nullable(z.bigint()),
413413
boolWithDefault: z.boolean().default(true),

packages/convex-helpers/server/zod4.functions.test.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import {
1717
import { convexTest } from "convex-test";
1818
import { assertType, describe, expect, expectTypeOf, test } from "vitest";
1919
import { modules } from "./setup.test.js";
20-
import { zCustomQuery, zCustomMutation, zCustomAction } from "./zod4.js";
20+
import { zCustomQuery, zCustomMutation, zCustomAction, zid } from "./zod4.js";
2121
import { z } from "zod/v4";
2222
import { v } from "convex/values";
23+
import { NoOp } from "./customFunctions.js";
2324

2425
const schema = defineSchema({
2526
users: defineTable({
@@ -186,13 +187,52 @@ export const codec = zQuery({
186187
}),
187188
});
188189

190+
// The example from README.md
191+
const zodQuery = zCustomQuery(query, NoOp);
192+
export const myComplexQuery = zodQuery({
193+
args: {
194+
userId: zid("users"),
195+
email: z.email(),
196+
num: z.number().min(0),
197+
nullableBigint: z.nullable(z.bigint()),
198+
boolWithDefault: z.boolean().default(true),
199+
null: z.null(),
200+
array: z.array(z.string()),
201+
optionalObject: z.object({ a: z.string(), b: z.number() }).optional(),
202+
union: z.union([z.string(), z.number()]),
203+
discriminatedUnion: z.discriminatedUnion("kind", [
204+
z.object({ kind: z.literal("a"), a: z.string() }),
205+
z.object({ kind: z.literal("b"), b: z.number() }),
206+
]),
207+
literal: z.literal("hi"),
208+
enum: z.enum(["a", "b"]),
209+
readonly: z.object({ a: z.string(), b: z.number() }).readonly(),
210+
pipeline: z.number().pipe(z.coerce.string()),
211+
},
212+
handler: async (_ctx, args) => {
213+
//... args at this point has been validated and has the types of what
214+
// zod parses the values into.
215+
// e.g. boolWithDefault is `bool` but has an input type `bool | undefined`.
216+
217+
return args;
218+
},
219+
});
220+
export const generateUserId = mutation({
221+
args: {},
222+
handler: async ({ db }) => {
223+
return db.insert("users", { name: "Nicolas" });
224+
},
225+
});
226+
189227
const testApi: ApiFromModules<{
190228
fns: {
191229
testQuery: typeof testQuery;
192230
testMutation: typeof testMutation;
193231
testAction: typeof testAction;
194232
transform: typeof transform;
195233
codec: typeof codec;
234+
myComplexQuery: typeof myComplexQuery;
235+
generateUserId: typeof generateUserId;
196236
};
197237
}>["fns"] = anyApi["zod4.functions.test"] as any;
198238

@@ -259,6 +299,42 @@ describe("zCustomQuery, zCustomMutation, zCustomAction", () => {
259299
>
260300
>();
261301
});
302+
303+
test("README example", async () => {
304+
const t = convexTest(schema, modules);
305+
const userId = await t.mutation(testApi.generateUserId);
306+
const response = await t.query(testApi.myComplexQuery, {
307+
userId,
308+
309+
num: 42,
310+
nullableBigint: 123n,
311+
null: null,
312+
array: ["foo", "bar"],
313+
optionalObject: { a: "test", b: 1 },
314+
union: "hello",
315+
discriminatedUnion: { kind: "a", a: "value" },
316+
literal: "hi",
317+
enum: "a",
318+
readonly: { a: "readonly", b: 2 },
319+
pipeline: 100,
320+
});
321+
expect(response).toMatchObject({
322+
userId,
323+
324+
num: 42,
325+
nullableBigint: 123n,
326+
boolWithDefault: true,
327+
null: null,
328+
array: ["foo", "bar"],
329+
optionalObject: { a: "test", b: 1 },
330+
union: "hello",
331+
discriminatedUnion: { kind: "a", a: "value" },
332+
literal: "hi",
333+
enum: "a",
334+
readonly: { a: "readonly", b: 2 },
335+
pipeline: "100",
336+
});
337+
});
262338
});
263339

264340
describe("transform", () => {

0 commit comments

Comments
 (0)