exodus/schemasafe
Form validator implementation based on @exodus/schemasafe a code-generating JSON Schema validator.
Installation
Section titled “Installation”npm i @sjsf/schemasafe-validator ajv@8yarn add @sjsf/schemasafe-validator ajv@8pnpm add @sjsf/schemasafe-validator ajv@8bun add @sjsf/schemasafe-validator ajv@8Example
Section titled “Example”<script lang="ts"> import { ON_INPUT, BasicForm, createForm, ON_CHANGE, ON_ARRAY_CHANGE, getValueSnapshot, } from "@sjsf/form"; import { createFormValidator } from "@sjsf/schemasafe-validator";
import * as defaults from "@/lib/form/defaults";
import { initialValue, schema, uiSchema } from "../shared";
const form = createForm({ ...defaults, schema, uiSchema, validator: createFormValidator, fieldsValidationMode: ON_INPUT | ON_CHANGE | ON_ARRAY_CHANGE, initialValue, });</script>
<BasicForm {form} novalidate />
<pre>{JSON.stringify(getValueSnapshot(form), null, 2)}</pre>import type { Schema, UiSchema } from "@sjsf/form";
export const schema: Schema = { type: "object", properties: { id: { type: "string", minLength: 8, pattern: "^\\d+$", }, active: { type: "boolean", }, skills: { type: "array", minItems: 4, items: { type: "string", minLength: 5, }, }, multipleChoicesList: { type: "array", maxItems: 2, items: { type: "string", enum: ["foo", "bar", "fuzz"], }, }, },};
export const uiSchema: UiSchema = { id: { "ui:options": { title: "Identifier", }, }, active: { "ui:options": { title: "Active", }, }, multipleChoicesList: { "ui:options": { title: "Pick max two items", }, },};
export const initialValue = { id: "Invalid", skills: ["karate", "budo", "aikido"], multipleChoicesList: ["foo", "bar", "fuzz"],} as const;Async validation
Section titled “Async validation”This validator does not support async validation.
Precompiled validation
Section titled “Precompiled validation”It is possible to use precompiled validator.
Schema precompilation
Section titled “Schema precompilation”The first step in the process is to compile a schema into a set of validate functions.
import fs from "node:fs";import path from "node:path";
import { validator } from '@exodus/schemasafe'
import { ON_ARRAY_CHANGE, ON_CHANGE, ON_INPUT } from '@sjsf/form';import { insertSubSchemaIds, fragmentSchema,} from "@sjsf/form/validators/precompile";import { DEFAULT_VALIDATOR_OPTIONS, FORM_FORMATS } from '@sjsf/schemasafe-validator'
import inputSchema from './input-schema.json' with { type: "json" }
const fieldsValidationMode = ON_INPUT | ON_CHANGE | ON_ARRAY_CHANGE
// NOTE: After calling this function, be sure to save the `schema` and// use it to generate the formconst patch = insertSubSchemaIds(inputSchema as any, { fieldsValidationMode });
// It is easier to save as a TS file// https://github.com/microsoft/TypeScript/issues/32063fs.writeFileSync( path.join(import.meta.dirname, "patched-schema.ts"), `import type { Schema } from "@sjsf/form";export const fieldsValidationMode = ${fieldsValidationMode}export const schema = ${JSON.stringify(patch.schema, null, 2)} as const satisfies Schema;`);
const schemas = fragmentSchema(patch)
// @ts-expect-error Typings for `multi` version are missingconst validate = validator(schemas, { ...DEFAULT_VALIDATOR_OPTIONS, formats: { ...FORM_FORMATS, "phone-us": /\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}$/, "area-code": /\d{3}/, }, schemas: new Map(schemas.map(schema => [schema.$id, schema])), multi: true,})
const validateFunctions = `export const [${schemas.map(s => s.$id).join(', ')}] = ${validate.toModule()}`
fs.writeFileSync(path.join(import.meta.dirname, "validate-functions.js"), validateFunctions){ "title": "User", "type": "object", "required": [ "id", "email", "age", "roles" ], "properties": { "id": { "type": "integer", "minimum": 1 }, "email": { "type": "string", "format": "email" }, "age": { "type": "integer", "minimum": 21, "maximum": 100 }, "roles": { "type": "array", "items": { "type": "string", "enum": [ "admin", "editor", "viewer" ] }, "uniqueItems": true, "minItems": 1 } }, "additionalProperties": false}node --experimental-strip-types compile-schema-script.ts<script lang="ts"> import { BasicForm, createForm, getValueSnapshot } from "@sjsf/form"; import { createFormValidatorFactory } from "@sjsf/schemasafe-validator/precompile"; import { resolver } from "@sjsf/form/resolvers/compat";
import * as defaults from "@/lib/form/defaults";
import { schema, fieldsValidationMode } from "./patched-schema"; import * as validateFunctions from "./validate-functions";
const form = createForm({ ...defaults, schema, validator: createFormValidatorFactory({ validateFunctions }), fieldsValidationMode, resolver, });</script>
<BasicForm {form} novalidate />
<pre>{JSON.stringify(getValueSnapshot(form), null, 2)}</pre>import type { Schema } from "@sjsf/form";export const fieldsValidationMode = 11export const schema = { "title": "User", "type": "object", "required": [ "id", "email", "age", "roles" ], "properties": { "id": { "type": "integer", "minimum": 1, "$id": "v1" }, "email": { "type": "string", "format": "email", "$id": "v2" }, "age": { "type": "integer", "minimum": 21, "maximum": 100, "$id": "v3" }, "roles": { "type": "array", "items": { "type": "string", "enum": [ "admin", "editor", "viewer" ], "$id": "v5" }, "uniqueItems": true, "minItems": 1, "$id": "v4" } }, "additionalProperties": false, "$id": "v0"} as const satisfies Schema;