Use SchemaUtils when multiple schemas share fields or when you need to derive one schema from another. This page focuses on practical reuse patterns first, then introduces helper APIs for larger projects.
Schema reuse
Direct reuse (easiest)✅
import { s } from 'schema-dsl/pure';
//Define reusable fields (just ordinary objects)
const commonFields = {
email: s('email!').label('email address'),
phone: s('string:11!').phone('cn').label('mobile number'),
username: s('string:3-32!').username().label('username')
};
// use directly
const registerSchema = s({
...commonFields, // ✅ Expand directly
password: s('string:8-64!').password('strong')
});
const profileSchema = s({
...commonFields, // ✅Reuse
bio: 'string:500',
avatar: 'url'
});
Advantages: The simplest, directly use JavaScript object expansion
Function reuse (when parameters are required)
//Define reusable field functions
const createEmailField = (label = 'Email address') =>
s('email!').label(label);
const createRangeField = (min, max) =>
s(`number:${min}-${max}`).label('numeric range');
// use
const schema = s({
email: createEmailField('Contact email'),
workEmail: createEmailField('work email'),
age: createRangeField(18, 120),
score: createRangeField(0, 100)
});
Advantages: Supports parameterization and has strong flexibility
Field library reuse (large projects)
// fields/common.js - defines the field library
import { s } from 'schema-dsl/pure';
export default {
email: () => s('email!').label('email address'),
phone: (country = 'cn') => s(`string:11!`).phone(country).label('mobile number'),
username: (range = '3-32') => s(`string:${range}!`).username(range).label('username'),
password: (strength = 'medium') => s('string:8-64!').password(strength).label('password'),
//Combined fields
userAuth: () => ({
username: s('string:3-32!').username().label('username'),
password: s('string:8-64!').password('strong').label('password')
}),
userProfile: () => ({
nickname: s('string:2-20!').label('nickname'),
bio: 'string:500',
avatar: 'url'
})
};
// use
import fields from './fields/common.js';
const loginSchema = s({
email: fields.email(),
password: fields.password('strong')
});
const registerSchema = s({
...fields.userAuth(), // ✅ Expand combined fields
email: fields.email(),
phone: fields.phone('cn')
});
Advantages: Unified management, easy to maintain
Schema merge
createLibrary() - Create a fragment library
import { SchemaUtils, s } from 'schema-dsl/pure';
const fields = SchemaUtils.createLibrary({
email: () => s('email!').label('email address'),
phone: () => s('string!').phone('cn').label('mobile number'),
profile: () => ({
bio: 'string:500',
avatar: 'url'
})
});
const registerSchema = s({
email: fields.email(),
phone: fields.phone(),
password: s('string!').password('strong')
});
const profileSchema = s({
...fields.profile(),
email: fields.email()
});
Description: createLibrary() just returns a fragment factory collection, suitable for centralized management of fields and combined fragments in large projects.
extend() - extend Schema (inheritance)
const baseUser = s({
name: 'string!',
email: 'email!'
});
//Extend basic Schema
const admin = SchemaUtils.extend(baseUser, {
role: 'admin|superadmin',
permissions: 'array<string>'
});
// admin contains all baseUser fields + role + permissions
Description: Similar to inheritance, all fields of the base Schema are retained.
Schema filter
pick() - select a field
const fullUser = s({
name: 'string!',
email: 'email!',
password: 'string:8-64!',
phone: 'string:11!',
age: 'number:18-120'
});
// Select only specific fields
const publicUser = SchemaUtils.pick(fullUser, ['name', 'email']);
// publicUser only contains name and email
Purpose: Extract some fields (such as public information) from the complete Schema
omit() - exclude fields
const fullUser = s({
name: 'string!',
email: 'email!',
password: 'string:8-64!',
phone: 'string:11!'
});
//Exclude sensitive fields
const safeUser = SchemaUtils.omit(fullUser, ['password']);
// safeUser contains all fields except password
Purpose: Remove sensitive fields (such as passwords)
partial() - Make fields optional
const updateSchema = SchemaUtils.partial(s({
name: 'string!',
email: 'email!',
age: 'number:18-120'
}));
// required will be removed from the results, suitable for PATCH / partial update scenarios
You can also make only some fields optional while preserving the rest of the schema:
const schema = s({
name: 'string!',
email: 'email!',
age: 'number:18-120'
});
const partialContact = SchemaUtils.partial(schema, ['name', 'email']);
partialContact still contains age; only name and email are removed from the top-level required list. Use SchemaUtils.pick(schema, fields).partial() when you want a schema that contains only those fields.
Schema export
toMarkdown() - Export to Markdown document
const schema = s({
username: s('string:3-32!').label('username'),
email: s('email!').label('email address'),
age: 'number:18-120'
});
const markdown = SchemaUtils.toMarkdown(schema, {
title: 'User Registration Schema'
});
console.log(markdown);
Output:
# User registration Schema
| Field | Type | Required | Constraints | Description |
|------|------|------|------|------|
| username | string | ✅ | length: 3-32 | username |
| email | email | ✅ | - | email address |
| age | number | ❌ | range: 18-120 | - |
Purpose: Generate API documentation
toHTML() - export to HTML table
const html = SchemaUtils.toHTML(schema, {
title: 'User Registration Schema'
});
// Generate HTML table, which can be embedded in the document
Use: Integrate into web documents
validateBatch() - batch validation statistics
import { SchemaUtils, Validator, s } from 'schema-dsl/pure';
const schema = s({
email: 'email!',
age: 'number:18-120'
});
const validator = new Validator();
const items = [
{ email: 'user1@example.com', age: 25 },
{ email: 'invalid', age: 15 },
{ email: 'user2@example.com', age: 30 }
];
const batch = SchemaUtils.validateBatch(schema, items, validator.getAjv());
console.log(batch);
// {
// results: [
// { index: 0, valid: true, errors: null, data: {...} },
// { index: 1, valid: false, errors: [...], data: null },
// { index: 2, valid: true, errors: null, data: {...} }
// ],
// summary: {
// total: 3,
// valid: 2,
// invalid: 1,
// duration: 5,
// averageTime: 1.67
// }
// }
Description:
- If you only need the result of "whether each item passed", you can directly use
validator.validateBatch(schema, items)
- If you also need summary statistics, use
SchemaUtils.validateBatch(schema, items, validator.getAjv())
const validator = SchemaUtils.withPerformance(new Validator());
const result = validator.validate(schema, data);
console.log(result.performance);
// {
// duration: 2,
// timestamp: '2026-05-06T12:34:56.789Z'
// }
Purpose: Add time-consuming information to the validation results without changing the business calling method.
clone() - Deep clone Schema
const original = s({
user: {
name: 'string!',
profile: {
bio: 'string:500'
}
}
});
const cloned = SchemaUtils.clone(original);
// cloned is a completely independent copy
cloned.properties.user.properties.name.maxLength = 100;
// original will not be modified
validateNestingDepth() - Check nesting depth
import { s, DslBuilder } from 'schema-dsl/pure';
const schema = s({
level1: {
level2: {
level3: {
level4: 'string'
}
}
}
});
const result = DslBuilder.validateNestingDepth(schema, 10);
// Return: { valid: true, depth: 4, path: 'level1.level2.level3', message: '...' }
if (result.depth > 5) {
console.warn('The nesting level is too deep, it is recommended to flatten');
}
Note: This capability belongs to the DslBuilder static method and is not a member of SchemaUtils; it is listed here because it is often used with the Schema tool chain.
Complete example
Enterprise level field library
// libs/fields/index.js
import { s } from 'schema-dsl/pure';
export default {
//Basic fields
id: () => s('string!').pattern(/^[a-zA-Z0-9_-]+$/).label('ID'),
email: () => s('email!').label('email address'),
phone: (country = 'cn') => s('string:11!').phone(country).label('mobile number'),
// Authentication field
auth: {
username: () => s('string:3-32!').username().label('username'),
password: (strength = 'strong') =>
s('string:8-64!').password(strength).label('password')
},
// personal information
profile: {
nickname: () => s('string:2-20!').label('nickname'),
realName: () => s('string:2-50').label('real name'),
bio: () => 'string:500',
avatar: () => s('url').label('avatar'),
birthday: () => 'date'
},
//Address information
address: () => ({
country: 'string:2-50!',
province: 'string:2-50!',
city: 'string:2-50!',
detail: 'string:10-200!'
}),
// timestamp
timestamps: () => ({
created_at: 'datetime!',
updated_at: 'datetime!'
})
};
// use
import fields from './libs/fields/index.js';
//User registration
const registerSchema = s({
...fields.auth,
email: fields.email(),
phone: fields.phone('cn'),
agree: 'boolean!'
});
//User information
const profileSchema = s({
...fields.profile,
...fields.timestamps()
});
// full user
const userSchema = SchemaUtils.extend(
SchemaUtils.extend(registerSchema, profileSchema),
fields.address()
);
best practices
1. Small projects: direct reuse
const commonFields = {
email: s('email!').label('mailbox'),
phone: s('string:11!').phone('cn')
};
const schema1 = s({ ...commonFields, ... });
const schema2 = s({ ...commonFields, ... });
2. Medium-sized projects: function reuse
const createUserFields = (options = {}) => ({
email: s('email!').label(options.emailLabel || 'Email'),
phone: s('string:11!').phone(options.country || 'cn')
});
const schema = s({
...createUserFields({ emailLabel: 'Contact email' }),
...otherFields
});
3. Large-scale projects: field library
// Unified management in fields/ directory
import fields from './fields/index.js';
const schema = s({
...fields.auth,
...fields.profile
});
Corresponding sample file
Example entry: schema-utils.ts
Description: Minimal workflow covering reusable(), createLibrary(), extend(), validateBatch(), withPerformance() and clone().