validate
js/validate.ts
fino:validate - JSON Schema validation with fluent builders.
JSON Schema specification: https://json-schema.org/specification
This module treats JSON Schema as the canonical schema representation. The
builder API is only a convenient way to construct JSON-Schema-shaped objects;
builders serialize directly with toJSON(), so they can be passed to
JSON.stringify() or stored on disk without a conversion step.
Raw JSON Schema objects are accepted anywhere a builder is accepted. This is intentional: applications can load schemas from disk, receive schemas from tools, or build schemas fluently in code while using the same validator pipeline.
The supported JSON Schema subset is intentionally small and runtime-focused:
type, const, enum, properties, required, additionalProperties,
items, prefixItems, anyOf, string length/pattern/format constraints,
numeric minimum/maximum, and array length constraints. Unknown keywords are
preserved on schemas for tooling compatibility but ignored by validation.
Supported string formats are email, url, and uri.
This is not a full JSON Schema implementation. $ref, $defs,
definitions, oneOf, allOf, not, pattern-property/dependency
keywords, unevaluated keywords, and unknown string formats are not resolved
or enforced in this release. Validate schemas with a full JSON Schema engine
first when unsupported keywords should be treated as application errors.
Validators are compiled into closure graphs. The implementation avoids
generated source and eval, but still avoids re-walking the schema metadata
for every input value.
import { parse, v } from 'fino:validate';
const schema = v.object({
name: v.string().min(1),
port: v.integer().min(1).max(65535).default(3000),
});
const config = parse(schema, { name: 'api' });
JSON.stringify(schema); // valid JSON Schema
Types
type JsonSchema = Record<string, unknown>
JSON-Schema-shaped object accepted by validators and builders.
This type is intentionally broad because callers can pass raw JSON Schema objects or builder output. Unsupported JSON Schema keywords are preserved in the object but ignored by this validator.
import type { JsonSchema } from 'fino:validate';
const schema: JsonSchema = { type: 'string', minLength: 1 };
Interfaces
interface ValidationIssue {
One validation failure at a concrete input path.
Issues are collected during traversal and attached to ValidationError.
safeParse() also exposes the array directly for callers that do not want to
inspect the error object.
import type { ValidationIssue } from 'fino:validate';
const issue: ValidationIssue = { path: 'name', keyword: 'minLength', message: 'too short' };
Properties
path: string
Dotted path to the invalid value; empty string means the root value.
Array indexes use bracket notation, such as items[0].
import type { ValidationIssue } from 'fino:validate';
const issue: ValidationIssue = { path: 'items[0]', keyword: 'type', message: 'expected string' };
message: string
Human-readable diagnostic.
Messages are intended for developer diagnostics and simple application errors; localize or rewrite them before exposing them in user-facing UI.
import type { ValidationIssue } from 'fino:validate';
const issue: ValidationIssue = { path: 'age', keyword: 'minimum', message: 'must be >= 18' };
keyword: string
JSON Schema keyword or fino refinement that failed.
Examples include type, required, minimum, anyOf, and refine.
import type { ValidationIssue } from 'fino:validate';
const issue: ValidationIssue = { path: '', keyword: 'required', message: 'is required' };
value?: unknown
The received value, when useful for diagnostics.
Some issues omit this field, such as missing required properties where the
value is undefined.
import type { ValidationIssue } from 'fino:validate';
const issue: ValidationIssue = { path: 'enabled', keyword: 'type', message: 'expected boolean', value: 'yes' };
interface SafeParseSuccess<T = unknown> {
Successful safeParse() result.
The discriminant is success: true; value contains the parsed value with
defaults applied.
import type { SafeParseSuccess } from 'fino:validate';
const result: SafeParseSuccess<string> = { success: true, value: 'ok' };
Properties
success: true
Success discriminant.
Use it to narrow the union returned by safeParse().
import { safeParse, v } from 'fino:validate';
const result = safeParse(v.string(), 'ok');
if (result.success) result.value.toUpperCase();
value: T
Parsed value, including applied defaults.
The value may be cloned for defaulted objects and arrays so callers can mutate it without changing the schema default.
import type { SafeParseSuccess } from 'fino:validate';
const result: SafeParseSuccess<number> = { success: true, value: 42 };
interface SafeParseFailure {
Failed safeParse() result.
The discriminant is success: false; error and issues describe all
validation failures found during traversal.
import { safeParse, v } from 'fino:validate';
const result = safeParse(v.integer(), 'nope');
if (!result.success) result.issues;
Properties
success: false
Failure discriminant.
Use it to narrow the union returned by safeParse().
import { safeParse, v } from 'fino:validate';
const result = safeParse(v.boolean(), 'yes');
if (!result.success) result.error.message;
error: ValidationError
Error object containing the same issues.
This is the same error shape thrown by parse().
import { safeParse, v } from 'fino:validate';
const result = safeParse(v.number(), 'x');
if (!result.success) result.error.issues;
issues: ValidationIssue[]
Individual validation issues.
The array is exposed directly so callers can inspect failures without touching the error object.
import { safeParse, v } from 'fino:validate';
const result = safeParse(v.string().min(2), 'a');
if (!result.success) result.issues[0]?.keyword;
Classes
class ValidationError extends Error {
Error thrown by parse() when validation fails.
The message summarizes all issues as path: message pairs. Use the issues
property for structured handling. safeParse() returns this error instead of
throwing it.
import { ValidationError, parse, v } from 'fino:validate';
try {
parse(v.string(), 123);
} catch (error) {
if (error instanceof ValidationError) error.issues;
}
Properties
issues: ValidationIssue[]
All validation issues found during traversal.
The array is stored as provided to the constructor and is not deep-cloned.
import { ValidationError } from 'fino:validate';
const error = new ValidationError([{ path: '', keyword: 'type', message: 'expected string' }]);
error.issues;
Constructors
constructor(issues: ValidationIssue[])
Create a validation error from collected issues.
The error name is set to ValidationError, and the message is built from
the issue paths and messages. An empty issue array creates a valid error
object but is not produced by the parser.
import { ValidationError } from 'fino:validate';
const error = new ValidationError([{ path: 'name', keyword: 'required', message: 'is required' }]);
class CompiledValidator<T = unknown> {
Reusable compiled validator for a builder or raw JSON Schema object.
The constructor compiles the schema once into closure-based validators. Reuse instances for repeated parsing of the same schema to avoid recompilation.
import { CompiledValidator, v } from 'fino:validate';
const validator = new CompiledValidator<string>(v.string().min(1));
const value = validator.parse('ok');
Constructors
constructor(schema: unknown)
Compile schema immediately.
Accepts a SchemaBuilder or raw JSON Schema object. Non-object schemas
throw Error. The compiled validator keeps a reference to the schema
object, so avoid mutating builders after compiling them.
import { CompiledValidator, v } from 'fino:validate';
const validator = new CompiledValidator<number>(v.integer().min(1));
Getters
get schema(): JsonSchema
Original canonical JSON Schema object used by this validator.
The returned object is the same object captured during construction, not a clone. Mutating it after compilation does not rebuild the closure graph.
import { compile, v } from 'fino:validate';
const validator = compile(v.string());
const schema = validator.schema;
Methods
parse(value: unknown): T
Parse value and return the validated value.
Defaults are applied to missing values. Throws ValidationError when any
issue is found.
import { compile, v } from 'fino:validate';
const validator = compile<string>(v.string().min(1));
const value = validator.parse('name');
safeParse(value: unknown): SafeParseSuccess<T> | SafeParseFailure
Parse value without throwing.
The success branch contains the parsed value. The failure branch contains a
ValidationError plus the issue array for direct inspection.
import { compile, v } from 'fino:validate';
const validator = compile<number>(v.number());
const result = validator.safeParse('nope');
class SchemaBuilder<T = unknown> {
Base fluent builder.
The schema property is the actual JSON Schema object. Builder methods mutate
and return the same builder so users can fluently compose constraints while
still preserving direct JSON serialization.
const documentedClass = 'SchemaBuilder';
console.log(documentedClass);
Properties
schema: JsonSchema
Mutable JSON Schema object represented by this builder.
Builder methods update this object and return the same builder. It can be passed anywhere a raw JSON Schema object is accepted.
import { v } from 'fino:validate';
const schema = v.string().schema;
Constructors
constructor(schema: JsonSchema, optional = false)
Create a builder around an existing JSON Schema object.
optional marks the schema as optional when it is used in v.object().
The marker is stored as a non-enumerable symbol and is not emitted by
JSON.stringify().
import { SchemaBuilder } from 'fino:validate';
const builder = new SchemaBuilder<string>({ type: 'string' });
Methods
toJSON(): JsonSchema
Return the canonical JSON Schema object for JSON.stringify().
Custom refinements and optional markers are non-enumerable symbol metadata, so they are intentionally omitted from serialized JSON Schema.
import { v } from 'fino:validate';
const json = JSON.stringify(v.string().min(1).toJSON());
parse(value: unknown): T
Validate value with this schema and throw on failure.
This compiles the builder for the call, applies defaults, and returns the
parsed value. Validation failures throw ValidationError.
import { v } from 'fino:validate';
const value = v.integer().min(1).parse(3);
safeParse(value: unknown): SafeParseSuccess<T> | SafeParseFailure
Validate value with this schema and return a tagged result.
This compiles the builder for the call. Success returns { success: true,
value }; failure returns { success: false, error, issues }.
import { v } from 'fino:validate';
const result = v.string().safeParse(123);
optional(): this
Mark this schema as optional when used as an object property.
Optional properties are omitted from the generated required array in
v.object(). The method mutates and returns the same builder.
import { v } from 'fino:validate';
const schema = v.object({ nickname: v.string().optional() });
nullable(): SchemaBuilder<T | null>
Accept this schema or null.
Returns a new builder using anyOf with the current schema and
{ type: 'null' }. The original builder is not marked optional.
import { v } from 'fino:validate';
const schema = v.string().nullable();
default(value: unknown): this
Apply this default when the input value is missing.
Defaults are used when the input is undefined; object and array defaults
are recursively cloned before returning parsed output.
import { v } from 'fino:validate';
const schema = v.integer().default(3000);
describe(text: string): this
Attach a human-readable description to the schema.
The description surfaces in generated JSON Schema, OpenAPI 3.1 output, and LLM tool parameter specs, so it doubles as documentation for both machines and humans.
import { v } from 'fino:validate';
const schema = v.string().describe('The user's display name');
refine(fn: (value: T) => boolean, message = 'failed custom validation'): this
Attach a custom in-process refinement.
Refinements cannot be represented in JSON Schema. They are preserved on the
builder object for runtime validation, but they are intentionally omitted
when serialized with toJSON().
import { v } from 'fino:validate';
const schema = v.string().refine((value) => value.startsWith('x-'), 'must start with x-');
Constants
const v
Fluent builder namespace.
Every builder method returns a schema builder whose toJSON() result is a
JSON-Schema-shaped object. Raw JSON Schema objects can be mixed with builders
anywhere a child schema is accepted.
const documentedMember = 'v';
console.log(documentedMember);
Methods
any(): SchemaBuilder<unknown>
Create a schema that accepts any value.
The generated JSON Schema is {}. Use this for intentionally unvalidated
extension points or values that are validated elsewhere.
import { v } from 'fino:validate';
const schema = v.any();
string(): StringBuilder
Create a string schema builder.
Chain min(), max(), length(), pattern(), or format() to add
string constraints.
import { v } from 'fino:validate';
const schema = v.string().min(1).max(64);
number(): NumberBuilder
Create a number schema builder.
The schema accepts finite JavaScript numbers. Chain min() and max() to
add numeric bounds.
import { v } from 'fino:validate';
const schema = v.number().min(0);
integer(): NumberBuilder
Create an integer schema builder.
The schema accepts JavaScript numbers that satisfy Number.isInteger().
Chain min() and max() to add integer bounds.
import { v } from 'fino:validate';
const schema = v.integer().min(1).max(65535);
boolean(): SchemaBuilder<boolean>
Create a boolean schema builder.
import { v } from 'fino:validate';
const schema = v.boolean().default(false);
null(): SchemaBuilder<null>
Create a schema that accepts only null.
Use someSchema.nullable() when a non-null schema should also accept
null.
import { v } from 'fino:validate';
const schema = v.null();
literal(value: unknown): SchemaBuilder<unknown>
Create a constant-value schema.
The value is stored as JSON Schema const and must compare equal during
validation.
import { v } from 'fino:validate';
const schema = v.literal('production');
enum(values: unknown[]): SchemaBuilder<unknown>
Create an enum schema from allowed values.
The values array is copied into JSON Schema enum, so later mutations to
the caller's array do not change the schema.
import { v } from 'fino:validate';
const schema = v.enum(['dev', 'prod']);
array(item: unknown): ArrayBuilder
Create an array schema with one item schema.
item may be another builder or a raw JSON Schema object. Chain min()
and max() to constrain item count.
import { v } from 'fino:validate';
const schema = v.array(v.string()).min(1);
tuple(items: unknown[]): ArrayBuilder
Create a fixed-length tuple schema.
items may contain builders or raw JSON Schema objects. The generated
schema uses prefixItems and sets minItems and maxItems to the tuple
length.
import { v } from 'fino:validate';
const schema = v.tuple([v.string(), v.integer()]);
union(items: unknown[]): SchemaBuilder<unknown>
Create a union schema.
items may contain builders or raw JSON Schema objects. The generated
schema uses JSON Schema anyOf; validation succeeds when any branch
accepts the value.
import { v } from 'fino:validate';
const schema = v.union([v.string(), v.integer()]);
Properties
object
Create an object schema from a property shape.
Shape values may be builders or raw JSON Schema objects. Properties are
required by default; call .optional() on a builder to omit that property
from the generated required list.
import { v } from 'fino:validate';
const schema = v.object({
name: v.string(),
nickname: v.string().optional(),
});
Functions
function compile<T = unknown>(schema: unknown): CompiledValidator<T>
Compile a builder or raw JSON Schema object into a reusable validator.
Use this when validating many values with the same schema. Non-object schemas
throw Error; validation failures occur later when calling parse() or
safeParse() on the returned validator.
import { compile, v } from 'fino:validate';
const validator = compile<{ name: string }>(v.object({ name: v.string() }));
function parse<T = unknown>(schema: unknown, value: unknown): T
Parse a value once with a builder or raw JSON Schema object.
This compiles the schema for the call, applies defaults, and returns the
parsed value. Validation failures throw ValidationError.
import { parse, v } from 'fino:validate';
const value = parse<string>(v.string().min(1), 'ok');
function safeParse<T = unknown>(
schema: unknown,
value: unknown
): SafeParseSuccess<T> | SafeParseFailure
Parse a value once and return a tagged result instead of throwing.
This compiles the schema for the call. Success returns { success: true,
value }; failure returns { success: false, error, issues }.
import { safeParse, v } from 'fino:validate';
const result = safeParse<number>(v.integer(), 'not an integer');