@mpen/valibot-extras
v0.1.1
Published
Extras/extensions for Valibot.
Readme
@mpen/valibot-extras
Extras and extensions for Valibot.
Motivation
Valibot's built-in string validators have some surprising behaviors:
v.string() accepts ill-formed strings
v.string() happily accepts strings containing lone/unpaired UTF-16 surrogates:
v.safeParse(v.string(), '\ud800').success // ✅ true — but this is not valid Unicode!These ill-formed strings can cause issues downstream (e.g. when serializing to JSON, sending over the network, or storing in a database).
v.minLength / v.maxLength count code units, not characters
Valibot's length validators use JavaScript's .length property, which counts UTF-16 code units — not visible characters. Characters outside the Basic Multilingual Plane (emoji, CJK ideographs, etc.) are represented as surrogate pairs and count as 2:
// '𠮷' is a single character, but 2 UTF-16 code units
v.safeParse(v.pipe(v.string(), v.minLength(2)), '𠮷').success // ✅ true — '𠮷'.length === 2
// '𠮷𠮷' is 2 characters, but 4 code units
v.safeParse(v.pipe(v.string(), v.maxLength(3)), '𠮷𠮷').success // ❌ false — despite being only 2 charsThis is particularly problematic when enforcing limits that should match database column widths (e.g. MySQL's VARCHAR(n), which counts characters, not bytes or code units).
Installation
bun add @mpen/valibot-extrasAPI
wellFormed(message?)
A Valibot action that rejects strings containing lone/unpaired UTF-16 surrogates (uses String.prototype.isWellFormed()):
import * as v from 'valibot'
import * as vx from '@mpen/valibot-extras'
const schema = v.pipe(v.string(), vx.wellFormed())
v.safeParse(schema, 'hello 𠮷').success // ✅ true
v.safeParse(schema, '\ud800').success // ❌ falsestring(message?)
Shorthand for v.pipe(v.string(), wellFormed()) — a string schema that only accepts well-formed Unicode:
import * as vx from '@mpen/valibot-extras'
const schema = vx.string()
v.safeParse(schema, 'abc').success // ✅ true
v.safeParse(schema, '\ud800').success // ❌ falseminCharLength(requirement, message?)
Like v.minLength, but counts Unicode code points (characters) instead of UTF-16 code units:
import * as v from 'valibot'
import * as vx from '@mpen/valibot-extras'
const schema = v.pipe(v.string(), vx.minCharLength(3))
v.safeParse(schema, '𠮷𠮷𠮷').success // ✅ true — 3 characters
v.safeParse(schema, '𠮷𠮷').success // ❌ false — only 2 charactersmaxCharLength(requirement, message?)
Like v.maxLength, but counts Unicode code points instead of UTF-16 code units:
import * as v from 'valibot'
import * as vx from '@mpen/valibot-extras'
const schema = v.pipe(v.string(), vx.maxCharLength(3))
v.safeParse(schema, '𠮷𠮷𠮷').success // ✅ true — 3 characters
v.safeParse(schema, '𠮷𠮷𠮷𠮷').success // ❌ false — 4 characterscharLength(requirement, message?)
Like v.length, but counts Unicode code points instead of UTF-16 code units:
import * as v from 'valibot'
import * as vx from '@mpen/valibot-extras'
const schema = v.pipe(v.string(), vx.charLength(3))
v.safeParse(schema, '𠮷𠮷𠮷').success // ✅ true — exactly 3 characters
v.safeParse(schema, '𠮷𠮷').success // ❌ false — only 2
v.safeParse(schema, '𠮷𠮷𠮷𠮷').success // ❌ false — 4toJsonSchema(schema, config?)
Converts a Valibot schema to JSON Schema, with support for custom JSON Schema annotations attached via attachJsonSchema.
This wraps @valibot/to-json-schema and adds overrideSchema/overrideAction hooks that look for schemas attached via the jsonSchemaSymbol. Attached schemas are merged into the converted output, preserving properties from the base conversion.
import * as v from 'valibot'
import * as vx from '@mpen/valibot-extras'
const schema = v.pipe(v.string(), vx.minCharLength(5), vx.maxCharLength(10))
const jsonSchema = vx.toJsonSchema(schema)
// { type: 'string', minLength: 5, maxLength: 10, $schema: '...' }attachJsonSchema(target, jsonSchema)
Attaches a custom JSON Schema fragment to any Valibot schema or action via a non-enumerable Symbol property. This is used internally by wellFormed, minCharLength, etc. to make them compatible with toJsonSchema, but you can use it to annotate your own custom actions:
import * as v from 'valibot'
import * as vx from '@mpen/valibot-extras'
const https = v.check((input: string) => input.startsWith('https://'), 'Must be HTTPS')
vx.attachJsonSchema(https, { format: 'uri' })
const schema = v.pipe(v.string(), https)
const jsonSchema = vx.toJsonSchema(schema)
// { type: 'string', format: 'uri', $schema: '...' }jsonSchemaSymbol
The Symbol used by attachJsonSchema to store the JSON Schema on a Valibot schema or action. Exposed for advanced use cases (e.g. reading back attached schemas).
JSON Schema Compatibility
All string validators in this package are compatible with @valibot/to-json-schema (and the included toJsonSchema wrapper). The minCharLength / maxCharLength / charLength actions produce the standard minLength / maxLength JSON Schema keywords.
