bun/zod-prefer-lazy-schema
Disallow building a validation schema while the module loads
A Zod schema at the top level of a module is built as soon as the module is imported, even if you never validate anything with it. With hundreds of schemas, that makes every start slower.
The rule reports a chain of calls that starts with z, where z is imported or global, if it runs while the module loads. That includes static class fields and functions that are called where they're written. A schema inside another schema isn't reported again.
Configure#
Off by default. To turn it on:
[lint.rules]
"bun/zod-prefer-lazy-schema" = "error" # or "warn"Every option, set to its default. The ones that are commented out are unset by default.
[lint.rules]
"bun/zod-prefer-lazy-schema" = ["error", {
roots = ["z"],
methods = [], # list of strings
# factorySuffix = <string>,
# lazyWrapper = <string>,
}]Examples#
import { z } from "zod";
export const User = z.object({This schema is built as soon as the module is imported. Build it when it is first used.
name: z.string().min(1),
email: z.string().email(),
});import { z } from "zod";
let user: z.ZodType | undefined;
export function userSchema() {
return (user ??= z.object({
name: z.string().min(1),
email: z.string().email(),
}));
}z.lazy() doesn't build anything until it's used, so it's allowed:
import { z } from "zod";
export const Comment = z.lazy(() => z.object({ text: z.string() }));Options#
| Option | Default | What it does |
|---|---|---|
roots | ["z"] | The names that a schema starts with, such as the z in z.object(). |
methods | [] | Methods that build a schema, no matter what they're called on, such as extend. |
factorySuffix | none | Also report calls to functions whose name ends in this string. |
lazyWrapper | none | The name of your helper that builds a schema on first use. The message then shows how to call it. |
{ "roots": ["v"] }1 problemimport * as v from "valibot";
export const Email = v.pipe(v.string(), v.email())This schema is built as soon as the module is imported. Build it when it is first used.;{ "methods": ["extend"] }1 problemimport { User } from "./user";
export const Admin = User.extend({ level: 1 })This schema is built as soon as the module is imported. Build it when it is first used.;{ "factorySuffix": "Schema" }1 problemimport { createUserSchema } from "./user";
export const User = createUserSchema()This schema is built as soon as the module is imported. Build it when it is first used.;lazyWrapper doesn't change what's reported. A schema in a function that you pass to a helper is allowed with or without it:
{ "lazyWrapper": "once" }No problemsimport { z } from "zod";
import { once } from "./once";
export const user = once(() => z.object({ name: z.string() }));When not to use it#
In a small program, or a server that starts once and runs for days, the time to build the schemas doesn't matter.