Skip to content

Add Standard Schema adapter #575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"arktype",
"joi",
"schemasafe",
"standard",
"typebox",
"valibot",
"vinejs",
Expand Down Expand Up @@ -163,6 +164,7 @@
"@exodus/schemasafe": "^1.3.0",
"@gcornut/valibot-json-schema": "^0.31.0",
"@sinclair/typebox": "^0.34.14",
"@standard-schema/spec": "^1.0.0",
"@typeschema/class-validator": "^0.3.0",
"@vinejs/vine": "^3.0.0",
"arktype": "^2.0.0",
Expand Down Expand Up @@ -219,5 +221,6 @@
},
"svelte": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module"
"type": "module",
"packageManager": "[email protected]+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228"
}
621 changes: 315 additions & 306 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions src/lib/adapters/adapters.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { JSONSchema } from '$lib/jsonSchema/index.js';
import { SuperFormError } from '$lib/errors.js';
import type { InputConstraints } from '$lib/jsonSchema/constraints.js';
import { constraints as schemaConstraints } from '$lib/jsonSchema/constraints.js';
import type { JSONSchema } from '$lib/jsonSchema/index.js';
import { defaultValues } from '$lib/jsonSchema/schemaDefaults.js';
import { schemaShape, type SchemaShape } from '$lib/jsonSchema/schemaShape.js';
import { schemaHash } from '$lib/jsonSchema/schemaHash.js';
import type { InputConstraints } from '$lib/jsonSchema/constraints.js';
import { SuperFormError } from '$lib/errors.js';
import { schemaShape, type SchemaShape } from '$lib/jsonSchema/schemaShape.js';
import { simpleSchema } from './simple-schema/index.js';
import type {
Schema,
ValidationResult,
Infer as InferSchema,
InferIn as InferInSchema,
Registry
Infer as InferSchema,
Registry,
Schema,
ValidationResult
} from './typeSchema.js';
import { simpleSchema } from './simple-schema/index.js';

export type { Schema, ValidationIssue, ValidationResult } from './typeSchema.js';

Expand All @@ -36,7 +36,8 @@ export type ValidationLibrary =
| 'vine'
| 'schemasafe'
| 'superstruct'
| 'effect';
| 'effect'
| 'standard';

export type AdapterOptions<T> = {
jsonSchema?: JSONSchema;
Expand Down
13 changes: 7 additions & 6 deletions src/lib/adapters/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
export type { ValidationAdapter, ClientValidationAdapter, Infer, InferIn } from './adapters.js';
export type { ClientValidationAdapter, Infer, InferIn, ValidationAdapter } from './adapters.js';

export { arktype, arktypeClient } from './arktype.js';
export { classvalidator, classvalidatorClient } from './classvalidator.js';
export { effect, effectClient } from './effect.js';
export { joi, joiClient } from './joi.js';
export { schemasafe, schemasafeClient } from './schemasafe.js';
export { standard, standardClient } from './standard.js';
export { superformClient } from './superform.js';
export { superstruct, superstructClient } from './superstruct.js';
export { typebox, typeboxClient } from './typebox.js';
export { valibot, valibotClient } from './valibot.js';
export { vine, vineClient } from './vine.js';
export { yup, yupClient } from './yup.js';
export {
zod,
zodClient,
type ZodValidation,
type ZodObjectType,
type ZodObjectTypes,
type ZodObjectType
type ZodValidation
} from './zod.js';
export { vine, vineClient } from './vine.js';
export { schemasafe, schemasafeClient } from './schemasafe.js';
export { superstruct, superstructClient } from './superstruct.js';

/*
// Cannot use ajv due to not being ESM compatible.
Expand Down
55 changes: 55 additions & 0 deletions src/lib/adapters/standard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { memoize } from "$lib/memoize.js";
import type { StandardSchemaV1 } from '@standard-schema/spec';
import {
createAdapter,
createJsonSchema,
type ClientValidationAdapter,
type Infer,
type InferIn,
type RequiredDefaultsOptions,
type ValidationAdapter,
type ValidationResult
} from './adapters.js';

async function _validate<T extends StandardSchemaV1>(
schema: T,
data: unknown
): Promise<ValidationResult<Infer<T, 'standard'>>> {
const result = await schema['~standard'].validate(data);
if ('value' in result) return { success: true, data: result.value as Infer<T, 'standard'> };
else {
return {
success: false,
issues: [
...result.issues.map((i) => ({
message: i.message,
path: i.path?.map((p) => (typeof p === 'object' && 'key' in p ? p.key : p))
}))
]
} as const;
}
}

function _standard<T extends StandardSchemaV1>(
schema: T,
options: RequiredDefaultsOptions<Infer<T, 'standard'>>
): ValidationAdapter<Infer<T, 'standard'>, InferIn<T, 'standard'>> {
return createAdapter({
superFormValidationLibrary: 'standard',
validate: (data) => _validate(schema, data),
jsonSchema: createJsonSchema(options),
defaults: options.defaults
});
}

function _standardClient<T extends StandardSchemaV1>(
schema: T
): ClientValidationAdapter<Infer<T, 'standard'>, InferIn<T, 'standard'>> {
return {
superFormValidationLibrary: 'standard',
validate: async (data) => _validate(schema, data)
};
}

export const standard = /* @__PURE__ */ memoize(_standard);
export const standardClient = /* @__PURE__ */ memoize(_standardClient);
28 changes: 19 additions & 9 deletions src/lib/adapters/typeSchema.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import type { TSchema, Static as Static$1 } from '@sinclair/typebox';
import type { type } from 'arktype';
import type { AnySchema } from 'joi';
import type { Static as Static$1, TSchema } from '@sinclair/typebox';
import type { StandardSchemaV1 } from '@standard-schema/spec';
import type {
Infer as ClassValidatorInfer,
InferIn as ClassValidatorInferIn,
Schema as ClassValidatorSchema
} from '@typeschema/class-validator';

import type { SchemaTypes, Infer as VineInfer } from '@vinejs/vine/types';
import type { type } from 'arktype';
import type { Schema as Schema$1 } from 'effect';
import type { AnySchema } from 'joi';
import type { FromSchema, JSONSchema } from 'json-schema-to-ts';
import type { Infer as Infer$2, Struct } from 'superstruct';
import type {
GenericSchema,
GenericSchemaAsync,
InferInput as Input,
InferOutput as Output
} from 'valibot';
import type { Schema as Schema$2, InferType } from 'yup';
import type { InferType, Schema as Schema$2 } from 'yup';
import type { ZodTypeAny, input, output } from 'zod';
import type { SchemaTypes, Infer as VineInfer } from '@vinejs/vine/types';
import type { FromSchema, JSONSchema } from 'json-schema-to-ts';
import type { Struct, Infer as Infer$2 } from 'superstruct';
import type { Schema as Schema$1 } from 'effect';

/*
import type { SchemaObject } from 'ajv';
Expand Down Expand Up @@ -141,6 +141,15 @@ interface VineResolver extends Resolver {
output: this['schema'] extends SchemaTypes ? VineInfer<this['schema']> : never;
}

interface StandardResolver extends Resolver {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
base: StandardSchemaV1<any, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input: this['schema'] extends StandardSchemaV1<any, any> ? StandardSchemaV1.InferInput<this['schema']> : never;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
output: this['schema'] extends StandardSchemaV1<any, any> ? StandardSchemaV1.InferOutput<this['schema']> : never;
}

interface SchemasafeResolver<Schema extends JSONSchema, Data = FromSchema<Schema>>
extends Resolver {
base: JSONSchema;
Expand Down Expand Up @@ -211,6 +220,7 @@ export type Registry = {
schemasafe: SchemasafeResolver<JSONSchema>;
superstruct: SuperstructResolver;
effect: EffectResolver;
standard: StandardResolver;
/*
ajv: AjvResolver;
deepkit: DeepkitResolver;
Expand Down
36 changes: 36 additions & 0 deletions src/routes/(v2)/v2/standard-schema/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { standard } from '$lib/adapters/standard.js';
import { message, superValidate } from '$lib/index.js';
import { fail } from '@sveltejs/kit';
import { valibotSchema, zodSchema } from './schema.js';

const options = {
defaults: {
name: 'John Doe',
email: '[email protected]'
}
};

export const load = async () => {
const valibotForm = await superValidate(standard(valibotSchema, options));
const zodForm = await superValidate(standard(zodSchema, options));
return { valibotForm, zodForm };
};

export const actions = {
valibot: async ({ request }) => {
const form = await superValidate(request, standard(valibotSchema, options));
console.log(form);

if (!form.valid) return fail(400, { form });

return message(form, 'Form posted successfully!');
},
zod: async ({ request }) => {
const form = await superValidate(request, standard(zodSchema, options));
console.log(form);

if (!form.valid) return fail(400, { form });

return message(form, 'Form posted successfully!');
}
};
122 changes: 122 additions & 0 deletions src/routes/(v2)/v2/standard-schema/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<script lang="ts">
import { page } from '$app/state';
import { standardClient } from "$lib/adapters/standard.js";
import SuperDebug, { superForm } from '$lib/index.js';
import { valibotSchema, zodSchema } from "./schema.js";

const { data } = $props();

const { form: valibotForm, errors: valibotErrors, message: valibotMessage, enhance: valibotEnhance } = superForm(data.valibotForm, {
validators: standardClient(valibotSchema)
});

const { form: zodForm, errors: zodErrors, message: zodMessage, enhance: zodEnhance } = superForm(data.zodForm, {
validators: standardClient(zodSchema)
});
</script>

<SuperDebug data={$valibotForm} />

<h3>Nullable Valibot schema</h3>

{#if $valibotMessage}
<!-- eslint-disable-next-line svelte/valid-compile -->
<div class="status" class:error={page.status >= 400} class:success={page.status == 200}>
{$valibotMessage}
</div>
{/if}

<form method="POST" action="?/valibot" use:valibotEnhance>
<label>
Name<br />
<input name="name" aria-invalid={$valibotErrors.name ? 'true' : undefined} bind:value={$valibotForm.name} />
{#if $valibotErrors.name}<span class="invalid">{$valibotErrors.name}</span>{/if}
</label>

<label>
Email<br />
<input
name="email"
type="email"
aria-invalid={$valibotErrors.email ? 'true' : undefined}
bind:value={$valibotForm.email}
/>
{#if $valibotErrors.email}<span class="invalid">{$valibotErrors.email}</span>{/if}
</label>

<button>Submit</button>
</form>

<SuperDebug data={$zodForm} />

<h3>Nullable Zod schema</h3>

{#if $zodMessage}
<!-- eslint-disable-next-line svelte/valid-compile -->
<div class="status" class:error={page.status >= 400} class:success={page.status == 200}>
{$zodMessage}
</div>
{/if}

<form method="POST" action="?/zod" use:zodEnhance>
<label>
Name<br />
<input name="name" aria-invalid={$zodErrors.name ? 'true' : undefined} bind:value={$zodForm.name} />
{#if $zodErrors.name}<span class="invalid">{$zodErrors.name}</span>{/if}
</label>

<label>
Email<br />
<input
name="email"
type="email"
aria-invalid={$zodErrors.email ? 'true' : undefined}
bind:value={$zodForm.email}
/>
{#if $zodErrors.email}<span class="invalid">{$zodErrors.email}</span>{/if}
</label>

<button>Submit</button>
</form>

<hr />
<p><a target="_blank" href="https://superforms.rocks/api">API Reference</a></p>

<style>
.invalid {
color: red;
}

.status {
color: white;
padding: 4px;
padding-left: 8px;
border-radius: 2px;
font-weight: 500;
}

.status.success {
background-color: seagreen;
}

.status.error {
background-color: #ff2a02;
}

input {
background-color: #ddd;
}

a {
text-decoration: underline;
}

hr {
margin-top: 4rem;
}

form {
padding-top: 1rem;
padding-bottom: 1rem;
}
</style>
12 changes: 12 additions & 0 deletions src/routes/(v2)/v2/standard-schema/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { email, minLength, object, pipe, string } from 'valibot';
import z from "zod";

export const valibotSchema = object({
name: pipe(string(), minLength(2)),
email: pipe(string(), email())
});

export const zodSchema = z.object({
name: z.string().min(2),
email: z.string().email()
});