Schemas libraries (Zod, Valibot, ...)
Regle supports the Standard Schema Spec.
This means any Standard Schema compliant RPC library can be used with Regle.
Official list of supported libraries:
- Zod docs
3.24+
. - Valibot docs
1+
. - ArkType docs
2+
- Any library following the Standard Schema Spec
pnpm add @regle/schemas
npm install @regle/schemas
yarn add @regle/schemas
bun add @regle/schemas
Usage
Instead of using the core useRegle
, use useRegleSchema
export from @regle/schemas
.
import { useRegleSchema } from '@regle/schemas';
import { z } from 'zod';
const { r$ } = useRegleSchema({ name: '' }, z.object({
name: z.string().min(1)
}))
import { useRegleSchema } from '@regle/schemas';
import * as v from 'valibot';
const { r$ } = useRegleSchema({ name: '' }, v.object({
name: v.pipe(v.string(), v.minLength(3))
}))
import { useRegleSchema } from '@regle/schemas';
import { type } from 'arktype';
const { r$ } = useRegleSchema({ name: '' }, type({
name: "string > 1"
}))
WARNING
Limitations from the core behaviour
Using schema libraries uses a different mechanism than the core "rules" one. Regle will parse the entire tree instead of doing it per-field. Than means that properties or methods are not available in nested values:
$validate
(only at root)$pending
(only at root)
One other limitation is you won't have access to any children $rules
, so checking if a field is required with $fields.xx.$rules.required.active
is not possible with schemas.
Computed schema
You can also have a computed schema that can be based on other state values.
WARNING
When doing refinements or transform, Vue can't track what the schema depends on because you're in a function callback.
Same way as withParams
from @regle/rules
, you can use the withDeps
helper to force dependencies on any schema
import { useRegleSchema, inferSchema, withDeps } from '@regle/schemas';
import { z } from 'zod';
import { ref, computed } from 'vue';
type Form = {
firstName?: string;
lastName?: string
}
const form = ref<Form>({ firstName: '', lastName: '' })
const schema = computed(() =>
inferSchema(form, z.object({
firstName: z.string(),
/**
* Important to keep track of the depency change
* Without it, the validator wouldn't run if `firstName` changed
*/
lastName: withDeps(
z.string().refine((v) => v !== form.value.firstName, {
message: "Last name can't be equal to first name",
}),
[() => form.value.firstName]
),
}))
);
const { r$ } = useRegleSchema(form, schema);
import { useRegleSchema, inferSchema, withDeps} from '@regle/schemas';
import * as v from 'valibot';
import { ref, computed } from 'vue';
type Form = {
firstName?: string;
lastName?: string
}
const form = ref<Form>({ firstName: '', lastName: '' })
const schema = computed(() =>
inferSchema(form, v.object({
firstName: v.string(),
/**
* Important to keep track of the depency change
* Without it, the validator wouldn't run if `firstName` changed
*/
lastName: withDeps(
v.pipe(
v.string(),
v.check((v) => v !== form.value.firstName, "Last name can't be equal to first name")
),
[() => form.value.firstName]
),
}))
)
const { r$ } = useRegleSchema(form, schema);
syncState
By default, Regle doesn't allow any transforms on the state.
Modifiers like default
, catch
or transform
will not impact the validation.
If you want to allow the schema to update your form state you can use the syncState
option.
The state will only be pacthed is the parse is successful.
type RegleSchemaBehaviourOptions = {
syncState?: {
/**
* Applies every transform on every update to the state
*/
onUpdate?: boolean;
/**
* Applies every transform only when calling `$validate`
*/
onValidate?: boolean;
};
};
Usage:
<script setup lang="ts">
import { useRegleSchema } from '@regle/schemas';
import { z } from 'zod';
const state = ref({ firstName: '', lastName: '' });
const { r$ } = useRegleSchema(
state,
z.object({
firstName: z.string().min(1).catch('Victor'),
lastName: z.string().transform((value) => `Transformed ${value}`),
}),
{ syncState: { onValidate: true } }
);
</script>
Type safe output
Similar to the main useRegle
composable, r$.$validate
also returns a type-safe output using Zod type schema parser.
import { useRegleSchema, inferSchema } from '@regle/schemas';
import { z } from 'zod';
import { ref, computed } from 'vue';
type Form = {
firstName?: string;
lastName?: string
}
const form = ref<Form>({ firstName: '', lastName: '' })
const schema = computed(() => inferSchema(form, z.object({
firstName: z.string().optional(),
lastName: z.string().min(1).refine(v => v !== form.value.firstName, {
message: "Last name can't be equal to first name"
}),
})))
const { r$ } = useRegleSchema(form, schema);
async function submit() {
const { valid, data } = await r$.$validate();
if (valid) {
console.log(data);
}
}
import { useRegleSchema, inferSchema } from '@regle/schemas';
import * as v from 'valibot';
import { ref, computed } from 'vue';
type Form = {
firstName?: string;
lastName?: string
}
const form = ref<Form>({ firstName: '', lastName: '' })
const schema = computed(() => {
return inferSchema(form, v.object({
firstName: v.optional(v.string()),
lastName: v.pipe(
v.string(),
v.minLength(3),
v.check((v) => v !== form.value.firstName, "Last name can't be equal to first name")
)
}))
})
const { r$ } = useRegleSchema(form, schema);
async function submit() {
const { valid, data } = await r$.$validate();
if (valid) {
console.log(data);
}
}
import { useRegleSchema, inferSchema } from '@regle/schemas';
import { type } from 'arktype';
import { ref, computed } from 'vue';
type Form = {
firstName?: string;
lastName?: string
}
const form = ref<Form>({ firstName: '', lastName: '' })
const schema = computed(() => {
return inferSchema(form, type({
'firstName?': 'string',
lastName: 'string > 3',
}).narrow((data, ctx) => {
if (data.firstName !== data.lastName) {
return true;
}
return ctx.reject({
expected: 'different than firstName',
path: ['lastName'],
});
}))
})
const { r$ } = useRegleSchema(form, schema);
async function submit() {
const { valid, data } = await r$.$validate();
if (valid) {
console.log(data);
}
}