@hesam_mhm/zod-rules
v1.0.0
Published
Enterprise-grade declarative conditional validation framework on top of Zod. Eliminates most direct usage of superRefine().
Maintainers
Readme
@hesam_mhm/zod-rules
Enterprise-grade declarative conditional validation framework on top of Zod v4. Eliminates most direct usage of
superRefine().
Why?
Zod's superRefine() is powerful but ergonomic poison: cross-field rules drift into
ad-hoc callbacks, error messages are inlined, and conditional logic disappears into
lambdas. zod-rules replaces that pattern with declarative, composable, fully-typed
rules that read like requirements and behave like code.
import { z } from "zod";
import {
withRules,
requiredIf,
equalTo,
atLeastOne,
dateAfter,
} from "@hesam_mhm/zod-rules";
const UserSchema = withRules(
z.object({
hasCompany: z.boolean(),
companyName: z.string().optional(),
password: z.string(),
confirmPassword: z.string(),
email: z.string().optional(),
phone: z.string().optional(),
startDate: z.coerce.date(),
endDate: z.coerce.date(),
}),
[
requiredIf("hasCompany", true, "companyName"),
equalTo("password", "confirmPassword"),
atLeastOne(["email", "phone"]),
dateAfter("endDate", "startDate"),
],
);Field names are checked at compile time. Rules compose with and, or, not,
when. Async rules (uniqueAsync, existsAsync) work transparently with
safeParseAsync. React Hook Form and i18n plugins ship out of the box.
Features
- 70+ declarative rules across 11 categories: presence, comparison, conditional, dependency, collection, aggregate, string, number, date, object, array-item, async, enterprise.
- Full TypeScript inference — typos in field names fail at compile time. Nested
paths like
"user.address.city"and"items.0.price"are typed. - Functional architecture — no classes, no
this, no mutation. Rules are plain objects with anapplymethod. - Composable —
and,or,not,whencombine rules into higher-level ones. - Async-ready —
parseAsync/safeParseAsyncwork natively;uniqueAsync,existsAsync,validateAsyncfor server-side checks. - React Hook Form compatible — drop-in
zodRulesResolverreplaces@hookform/resolvers/zod. - i18n plugin — install a translator and every message can be a
{messageKey, params}descriptor resolved through your dictionary. - Tree-shakable ESM — only the rules you import are bundled.
- Zero runtime dependencies except
zod(and optionalreact-hook-form).
Installation
npm install @hesam_mhm/zod-rules zod
# peer deps (optional)
npm install react-hook-formQuick start
import { z } from "zod";
import { withRules, required, equalTo, when } from "@hesam_mhm/zod-rules";
const SignupSchema = withRules(
z.object({
country: z.string(),
nationalCode: z.string().optional(),
password: z.string(),
confirmPassword: z.string(),
}),
[
// Conditional requirement
when((d) => d.country === "IR", required("nationalCode")),
// Cross-field equality
equalTo("password", "confirmPassword"),
],
);
// Sync parse if all rules are sync:
const r1 = SignupSchema.safeParse({
country: "US",
password: "x",
confirmPassword: "x",
});
// => { success: true, data: ... }
// Async parse for async rules (or just to be safe):
const r2 = await SignupSchema.safeParseAsync({
country: "IR",
nationalCode: "123",
password: "x",
confirmPassword: "y",
});
// => { success: false, error: ZodError }Documentation
- Architecture — folder structure, design principles, data flow.
- API reference — every public function and type.
- Examples — patterns and recipes.
- Migration guide — coming from raw
superRefine. - Best practices — rules of thumb for real apps.
- Tradeoffs — what we chose and what we gave up.
- Performance — benchmarks and tuning.
- Extension guide — write your own rules and plugins.
React Hook Form
import { useForm } from "react-hook-form";
import { z } from "zod";
import { withRules, required, equalTo } from "@hesam_mhm/zod-rules";
import { zodRulesResolver } from "@hesam_mhm/zod-rules/react-hook-form";
const schema = withRules(
z.object({
email: z.string(),
password: z.string(),
confirmPassword: z.string(),
}),
[required("email"), equalTo("password", "confirmPassword")],
);
function SignupForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodRulesResolver(schema),
});
// ...
}See examples/ for FieldArray and multi-step form examples.
i18n
import i18next from "i18next";
import { installI18n, i18n } from "@hesam_mhm/zod-rules/i18n";
installI18n((key, params) => i18next.t(key, params));
const schema = withRules(z.object({ email: z.string() }), [
required("email", { message: i18n("errors.required", { field: "email" }) }),
]);License
MIT © Hesam
