Skip to content

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:

sh
pnpm add @regle/schemas
sh
npm install @regle/schemas
sh
yarn add @regle/schemas
sh
bun add @regle/schemas

Usage

Instead of using the core useRegle, use useRegleSchema export from @regle/schemas.

ts
import { useRegleSchema } from '@regle/schemas';
import { z } from 'zod';

const { r$ } = useRegleSchema({ name: '' }, z.object({
  name: z.string().min(1)
}))
ts
import { useRegleSchema } from '@regle/schemas';
import * as v from 'valibot';

const { r$ } = useRegleSchema({ name: '' }, v.object({
  name: v.pipe(v.string(), v.minLength(3))
}))
ts
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 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

ts
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);
ts
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.

ts
type RegleSchemaBehaviourOptions = {
  syncState?: {
    /**
     * Applies every transform on every update to the state
     */
    onUpdate?: boolean;
    /**
     * Applies every transform only when calling `$validate`
     */
    onValidate?: boolean;
  };
};

Usage:

vue
<script setup lang="ts">
import { ref } from 'vue';
// ---cut---
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.

ts
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);
  }
}
ts
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);
  }
}
ts
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);
  }
}

Released under the MIT License. Logo by Johannes Lacourly