Skip to content

$schema

import {
$schema,
s
} from "dreamkit";

With the $schema constructor you can create schemas very easily and most importantly, these schemas can be reused throughout the application, especially in the params option of $route, $settings, $session and $api.

Because most of the data tends to come from a limited schema (SQL/NoSQL), $schema does not aspire to be able to build very complex schemas, since these complex schemas, in addition to not existing in a database, are not easy to represent with secure typing in a form.

To simplify the creation of schemas it is recommended to use s, a simple alias for $schema.

An important point to note about $schema is the ability to deeply manipulate object properties with the methods: pick, omit, assign, require, deepPartial, deepRequired and deepNullish.


Definition

class Type {
title(value: string): this;
nullable(): this;
optional(): this;
nullish(): this;
required(): this;
validate(input: any): any[];
test(input: any): boolean;
assert(input: any): asserts input is any;
refine(input: (input: any) => boolean | any[]): this;
toJsonSchema(): any;
}
const $schema: {
title(value: string): typeof $schema;
object(props: Record<string, Type>): Type & {
props: Record<string, Type>;
findProp(name: string): Type | undefined;
findPropOrFail(name: string): Type;
pick(mask: Record<string, boolean>): Type;
omit(mask: Record<string, boolean>): Type;
assign(props: Record<string, Type>): Type;
require(mask?: Record<string, boolean>): Type;
deepPartial(self?: boolean): Type;
deepRequired(self?: boolean): Type;
deepNullish(self?: boolean): Type;
};
array(type: Type): Type & {
min(value: number): Type;
max(value: number): Type;
};
string(): Type & {
min(value: number): Type;
max(value: number): Type;
length(value: number): Type;
};
number(): Type & {
min(value: number): Type;
max(value: number): Type;
integer(): Type;
};
bool(): Type;
file(): Type & {
ext(value: string[]): Type;
min(value: number | `${number}mb`): Type;
max(value: number | `${number}mb`): Type;
};
custom(assert: (value: any) => boolean): Type;
};
const s: typeof $schema;

FAQ

Why another schema validator?

You may be tired of the number of libraries of this kind (e.g. zod, yup, valibot), but below I will explain some of the reasons for this decision.

In the initial stages of development, valibot was considered, but its user experience is diminished by its aggressive tree shaking system, and if there is one thing that DreamKit aims to stand out for, it is its UX.

Zod was another alternative, but its large size and performance deficiencies in TypeScript were important reasons for its discard.


Examples

String validation

import { $route, Input, s } from "dreamkit";
import { createSignal } from "solid-js";
const type = s.string().min(3).max(5);
export default $route.path("/").create(() => {
const [name, setName] = createSignal("");
return (
<>
<Input value={name} onChange={setName} />
<p>
{"errors: "}
{JSON.stringify(type.validate(name()), null, 2)}
</p>
</>
);
});

Object manipulation

import { $route, ObjectType, s } from "dreamkit";
import { createSignal, For } from "solid-js";
const object = s
.object({
id: s.number().integer().min(1),
})
.assign({
profile: { name: s.string() },
});
const schemas: Record<string, ObjectType<{}>> = {
// { id: number }
pickOnlyId: object.pick({ id: true }),
// { id?: number, profile?: { name?: string } }
deepPartial: object.deepPartial(),
// { id?: number | null, profile?: { name?: string | null } | null }
deepNullish: object.deepNullish(),
// { profile: { name: string, age?: number } }
assign: object
.assign({ profile: { age: s.number().optional() } })
.pick({ profile: true }),
// { id: number, profile: {}}
omitName: object.omit({ profile: { name: true } }),
};
export default $route.path("/").create(() => {
const [schema, setSchema] = createSignal("pickOnlyId");
const [json, setJson] = createSignal(JSON.stringify({ id: 1 }, null, 2));
const errors = () => {
let subject;
try {
subject = JSON.parse(json());
} catch {
subject = {};
}
return schemas[schema()].validate(subject);
};
return (
<>
Schema:{" "}
<select onInput={(event) => setSchema(event.target.value)}>
<For each={Object.keys(schemas)}>
{(value) => <option>{value}</option>}
</For>
</select>
<p>
JSON Schema: <br />
<textarea
style={{ height: "300px", width: "100%" }}
disabled
onInput={(event) => setJson(event.target.value)}
>
{JSON.stringify(schemas[schema()].toJsonSchema(), null, 2)}
</textarea>
</p>
<p>
Input:
<br />
<textarea
style={{ height: "300px", width: "100%" }}
onInput={(event) => setJson(event.target.value)}
>
{json()}
</textarea>
</p>
<p>Errors: {JSON.stringify(errors(), null, 2)}</p>
</>
);
});