@just-io/schema
v5.0.1
Published
Declare schemas for validation and generation JSON schemas
Readme
@just-io/schema
A TypeScript library for declaring schemas for validation and JSON Schema generation.
Installation
npm install @just-io/schemaQuick Start
import { make, ErrorKeeper, defaultErrorFormatter } from '@just-io/schema';
const schemas = make<'default'>();
// Define a schema
const userSchema = schemas.structure({
name: schemas.string(),
age: schemas.number().minimum(0).integer(),
email: schemas.string().regexp(/^[\w.-]+@[\w.-]+\.\w+$/),
active: schemas.boolean(),
});
// Validate data
const errorKeeper = new ErrorKeeper('default', defaultErrorFormatter);
const result = userSchema.validate({ name: 'John', age: 25, email: '[email protected]', active: true }, errorKeeper, false);
if (result.ok) {
console.log('Valid:', result.value);
} else {
console.log('Errors:', errorKeeper.makeStringErrors());
}Schema Types
Primitive Schemas
string()
Validates string values with optional constraints.
schemas.string() // Any string
schemas.string(['a', 'b', 'c']) // String enum (type narrowing)
schemas.string().enum(['a', 'b', 'c']) // String enum
schemas.string().regexp(/pattern/) // Regexp pattern matching
schemas.string().minLength(5) // Minimum length
schemas.string().maxLength(100) // Maximum lengthnumber()
Validates number values with optional constraints.
schemas.number() // Any number
schemas.number([1, 2, 3, 4, 5]) // Number enum (type narrowing)
schemas.number().enum([1, 2, 3]) // Number enum
schemas.number().minimum(0) // Minimum value
schemas.number().maximum(100) // Maximum value
schemas.number().integer() // Integer onlyboolean()
Validates boolean values.
schemas.boolean()null()
Validates null values.
schemas.null()undefined()
Validates undefined values.
schemas.undefined()value()
Validates a specific literal value.
schemas.value('exact-string') // Exact string
schemas.value(42) // Exact number
schemas.value(true) // Exact boolean
schemas.value(null) // Exact nullComposite Schemas
structure()
Validates object structures with defined fields.
type User = {
name: string;
age: number;
};
schemas.structure<User>({
name: schemas.string(),
age: schemas.number(),
});
// With additional properties
schemas.structure({
name: schemas.string(),
}).additionalProps(schemas.number());array()
Validates arrays with item schema.
schemas.array(schemas.string()) // Array of strings
schemas.array(schemas.number()).minItems(1) // At least 1 item
schemas.array(schemas.number()).maxItems(10) // At most 10 items
schemas.array(schemas.number()).unique() // All items must be uniquetuple()
Validates fixed-length arrays with specific types at each position.
schemas.tuple(schemas.number(), schemas.string()) // [number, string]record()
Validates objects with dynamic keys but uniform value types.
schemas.record(schemas.string()) // Record<string, string>
schemas.record(schemas.number()) // Record<string, number>union()
Validates values that match any of the provided schemas.
schemas.union(schemas.number(), schemas.null()) // number | null
schemas.union(schemas.string(), schemas.number()) // string | numbergroup()
Validates discriminated unions based on a key field.
type Action =
| { type: 'get'; url: string }
| { type: 'add'; data: object };
schemas.group<Action, 'type'>('type', {
get: schemas.structure({
type: schemas.value('get'),
url: schemas.string(),
}),
add: schemas.structure({
type: schemas.value('add'),
data: schemas.record(schemas.unknown()),
}),
});Modifier Schemas
optional()
Makes a schema accept undefined in addition to its base type.
schemas.optional(schemas.number()) // number | undefinednullable()
Makes a schema accept null in addition to its base type.
schemas.nullable(schemas.number()) // number | nullAdvanced Schemas
lazy()
Creates schemas for recursive types.
type TreeNode = {
nodes: (TreeNode | string)[];
};
const treeSchema: StructureSchema<TreeNode, 'default'> = schemas.structure<TreeNode>({
nodes: schemas.array(
schemas.union<TreeNode | string>(
schemas.string(),
schemas.lazy(() => treeSchema),
),
),
});extended()
Adds custom validators to existing schemas.
function notEmpty(value: Record<string, string>, lang: 'default', errorKeeper: ErrorKeeper<'default'>): boolean {
if (Object.keys(value).length === 0) {
errorKeeper.push('Should not be empty.');
return false;
}
return true;
}
schemas.extended(schemas.record(schemas.string()), notEmpty);custom()
Creates fully custom schemas for special types.
schemas.custom<File, 'default'>({
validate(value, errorKeeper, useDefault) {
if (value instanceof File) {
return { ok: true, value };
}
errorKeeper.push('Should be File.');
return { ok: false, error: true };
},
cast(value, errorKeeper, useDefault) {
if (value instanceof File) {
return { ok: true, value };
}
errorKeeper.push('Should be File.');
return { ok: false, error: true };
},
makeJSONSchema(pointer, defs, lang) {
return { type: 'string', format: 'binary' };
},
});any() and unknown()
Accepts any value without validation.
schemas.any()
schemas.unknown()Validation Methods
is(value, errorKeeper?)
Returns true if valid, false otherwise.
if (schemas.number().is(42)) {
// Valid
}assert(value, errorKeeper?)
Returns true if valid, throws an error otherwise.
schemas.number().assert(42); // Returns true
schemas.number().assert('42'); // Throws Errorcheck(value, errorKeeper?)
Returns a result object with ok boolean.
const result = schemas.number().check(42);
if (result.ok) {
console.log(result.value);
}validate(value, errorKeeper, useDefault)
Internal validation method returning a detailed result.
const errorKeeper = new ErrorKeeper('default', defaultErrorFormatter);
const result = schema.validate(data, errorKeeper, false);
if (!result.ok) {
console.log(errorKeeper.makeStringErrors());
// [{ pointer: ['field'], detail: 'Should be "string" type.' }]
}cast(value, errorKeeper, useDefault)
Validates and casts string values to their appropriate types (useful for form data).
const errorKeeper = new ErrorKeeper('default', defaultErrorFormatter);
// String "123" is cast to number 123
schemas.number().cast('123', errorKeeper, false);
// String "true" is cast to boolean true, empty string "" to false
schemas.boolean().cast('true', errorKeeper, false);Composing from Form Data
The compose method can construct objects from flat key-value data like FormData, URLSearchParams, or plain objects.
type Book = {
name: string;
chapters: { title: string; pages: number }[];
tags: string[];
};
const schema = schemas.structure<Book>({
name: schemas.string(),
chapters: schemas.array(schemas.structure({
title: schemas.string(),
pages: schemas.number(),
})),
tags: schemas.array(schemas.string()),
});
// From URLSearchParams
const params = new URLSearchParams([
['name', 'My Book'],
['chapters/0/title', 'Chapter 1'],
['chapters/0/pages', '10'],
['tags', 'fiction'],
['tags', 'adventure'],
]);
const errorKeeper = new ErrorKeeper('default', defaultErrorFormatter);
const result = schema.compose(params, errorKeeper, false, '/', 0);
// Also works with FormData and plain objectsJSON Schema Generation
Generate JSON Schema from your schemas:
const schema = schemas.structure({
name: schemas.string().title({ default: 'Name' }),
age: schemas.number().minimum(0).integer().description({ default: 'User age' }),
active: schemas.boolean().default(false),
});
const jsonSchema = schema.generateJSONSchema('default');
// {
// type: 'object',
// properties: {
// name: { type: 'string', title: 'Name' },
// age: { type: 'integer', minimum: 0, description: 'User age' },
// active: { type: 'boolean', defaut: false }
// },
// required: ['name', 'age', 'active'],
// additionalProperties: false
// }Schema Metadata
Add metadata for JSON Schema generation:
schemas.string()
.title({ default: 'Field Title' })
.description({ default: 'Field description' })
.default('default value')Error Handling
Validation errors provide detailed information about what went wrong:
const errorKeeper = new ErrorKeeper('default', defaultErrorFormatter);
schema.validate(invalidData, errorKeeper, false);
const errors = errorKeeper.makeStringErrors();
// [
// { pointer: ['name'], detail: 'Should be existed.' },
// { pointer: ['age'], detail: 'Should be "number" type.' },
// { pointer: ['unknownField'], detail: 'Should not be existed.' }
// ]Multi-language Support
The library supports multiple language configurations for error messages:
const schemas = make<'en' | 'de'>();
schemas.string()
.title({ en: 'Name', de: 'Name' })
.description({ en: 'User name', de: 'Benutzername' });License
MIT
