Custom Schema
Writing your own schemas and adapting other validation libraries to kvant.
Any object with parse and encode functions is a valid kvant schema:
interface KvantSchema<Output, Input = Output, RawInput = unknown> {
parse: (value: RawInput) => Output // storage to state
encode: (value: Output) => Input // state to storage
}Rolling your own
The simplest schema is a plain object:
import type { } from 'kvantjs'
import { } from 'kvantjs/vue'
const : <string[] | undefined, string | undefined> = {
: => ? ().(',').() : ,
: => ?. ? .(',') : ,
}
.('a,b,c') // ['a', 'b', 'c']
.(['a', 'b']) // 'a,b'
.([]) // undefined (removes the key)
const = ('tags', )You can also use kv.custom() from kvantjs/schema, which gives you the same
result with full type inference and access to the chainable combinators
(.default(), .pipe(), .refine(), ...):
import * as from 'kvantjs/schema'
import { } from 'kvantjs/vue'
const = .<string[] | undefined, string | undefined>({
: => ? ().(',').() : ,
: => ?. ? .(',') : ,
})
const = ('tags', .([]))Contract, in short:
- Never throw. Return
undefinedfor invalid input instead. URLs, cookies, and storage are user-editable, and throwing turns a bad value into a broken page. - Lossless.
encode(parse(v))preserves valid values. - Pure. No side effects: same input, same output.
Adapting other schema libraries
Valibot
Valibot is validation-only, so wrap it with an encode side and a non-throwing parse:
import * as v from 'valibot'
import type { KvantSchema } from 'kvantjs'
function valibot<T>(
schema: v.BaseSchema<unknown, T, v.BaseIssue<unknown>>
): KvantSchema<T | undefined> {
return {
parse: (value) => {
const result = v.safeParse(schema, value)
return result.success ? result.output : undefined
},
encode: (value) => value,
}
}ArkType
ArkType schemas return errors instead of throwing, so the adapter is straightforward:
import { type Type, ArkErrors } from 'arktype'
import type { KvantSchema } from 'kvantjs'
function arktype<T>(
def: Type<T>
): KvantSchema<T | undefined> {
return {
parse: (value) => {
const out = def(value)
return out instanceof ArkErrors ? undefined : out as T
},
encode: (value) => value,
}
}Standard Schema
Any Standard Schema-compatible library works through the same adapter shape:
import type { StandardSchemaV1 } from '@standard-schema/spec'
import type { KvantSchema } from 'kvantjs'
function standard<T>(
schema: StandardSchemaV1<unknown, T>
): KvantSchema<T | undefined> {
return {
parse: (value) => {
const result = schema['~standard'].validate(value)
if (result instanceof Promise)
throw new TypeError('Async schemas are not supported by kvant')
return result.issues ? undefined : result.value
},
encode: (value) => value,
}
}The pattern is always the same: safe-parse on the way in, identity (or a
codec) on the way out, and undefined, never an exception, for invalid
input.