
Security News
arXiv Is Rate Limiting Authors Following a Flood of AI Slop Submissions
arXiv now limits authors to two submissions a month as AI slop overwhelms moderators, delays good papers, and sparks debate over applying the limit to everyone.
schema-dsl
Advanced tools
A concise and powerful JSON Schema validation library - DSL syntax + String extensions + convenient validate API
Declare field rules with the simplest DSL — let one schema drive validation, derivation, export, and documentation.
📚 Documentation: https://vextjs.github.io/schema-dsl
Quick Start · Documentation · Feature Overview · Examples
npm install schema-dsl
What is schema-dsl?
Write field rules like this:
import { dsl, validate } from 'schema-dsl';
const userSchema = dsl({
username: 'string:3-32!',
email: 'email!',
role: 'admin|user|guest',
contact: 'types:email|phone'
});
const result = validate(userSchema, req.body);
Then that same set of rules continues to power:
validate() / validateAsync()pick / omit / partial to tailor schemas per endpointValidationError + I18nError5-minute tutorial: Quick Start | Full docs: Online Documentation
Getting started:
Core features:
Export & integration:
Full docs: Online Documentation · Chinese Documentation · Feature Index
|
❌ Traditional approach — verbose
|
✅ schema-dsl — concise and clean
|
| Feature | schema-dsl | Notes |
|---|---|---|
| Basic validation | ✅ | string, number, boolean, date, email, url, phone… |
| Advanced validation | ✅ | regex, custom functions, conditional branches, nested objects, arrays… |
| Cross-type union | ✅ | types:email|phone — one field accepts multiple types |
| Error messages | ✅ | auto-translated + custom messages + field labels |
| i18n business errors | ✅ | I18nError with numeric error codes |
| Database export | ✅ | MongoDB / MySQL / PostgreSQL schema generation |
| Documentation generation | ✅ | Markdown field docs auto-generated |
| TypeScript | ✅ | Written in native TypeScript with full type inference |
| Plugin system | ✅ | Custom types / formats / validators |
| Schema reuse | ✅ | pick / omit / partial / extend |
import { dsl, exporters, SchemaUtils } from 'schema-dsl';
const userSchema = dsl({
id: 'uuid!',
username: 'string:3-32!',
email: 'email!',
password: 'string:8-64!',
age: 'number:18-120',
createdAt: 'string!'
});
// 📋 derive scenario-specific schemas
const createSchema = SchemaUtils.omit(userSchema, ['id', 'createdAt']);
const updateSchema = SchemaUtils.partial(SchemaUtils.pick(userSchema, ['username', 'email']));
const publicSchema = SchemaUtils.omit(userSchema, ['password']);
// 🗄️ export the same schema to any database
const mongoSchema = new exporters.MongoDBExporter().export(userSchema);
const mysqlDDL = new exporters.MySQLExporter().export('users', userSchema);
const pgDDL = new exporters.PostgreSQLExporter().export('users', userSchema);
// 📝 generate field documentation from the same schema
const markdown = exporters.MarkdownExporter.export(userSchema, { title: 'User Field Reference' });
⚠️ SQL exporters only accept
anyOf/oneOfwhen every branch resolves to the same SQL column type (for exampleipv4 | ipv6). Ambiguous unions such asstring | numbernow throw an explicit error instead of silently choosing the first branch.
npm install schema-dsl
Runtime requirement: Node.js >= 18.0.0
import { dsl, validate } from 'schema-dsl';
const userSchema = dsl({
username: 'string:3-32!',
email: 'email!',
age: 'number:18-120',
role: 'admin|user|guest',
tags: 'array<string>'
});
// ✅ validation passed
const result = validate(userSchema, {
username: 'john_doe',
email: 'john@example.com',
age: 25,
role: 'user',
tags: ['verified']
});
console.log(result.valid); // true
console.log(result.data); // validated data
// ❌ validation failed
const bad = validate(userSchema, { username: 'ab', email: 'not-email' });
console.log(bad.errors);
// [
// { path: 'username', message: 'username must be at least 3 characters' },
// { path: 'email', message: 'email must be a valid email address' }
// ]
import { dsl, validateAsync, ValidationError } from 'schema-dsl';
const createUserSchema = dsl({
username: 'string:3-32!',
email: 'email!',
password: 'string:8-32!'
});
app.post('/api/users', async (req, res, next) => {
try {
// throws ValidationError automatically on failure
const validData = await validateAsync(createUserSchema, req.body);
const user = await db.users.create(validData);
res.json({ success: true, data: user });
} catch (error) {
next(error);
}
});
// global error handler
app.use((error, req, res, next) => {
if (error instanceof ValidationError) {
return res.status(400).json({ success: false, errors: error.errors });
}
next(error);
});
import { dsl, SchemaUtils } from 'schema-dsl';
const userSchema = dsl({
id: 'uuid!',
username: 'string:3-32!',
email: 'email!',
password: 'string:8-64!',
createdAt: 'string!'
});
// create endpoint: remove server-generated fields
const createSchema = SchemaUtils.omit(userSchema, ['id', 'createdAt']);
// update endpoint: pick editable fields, all optional
const updateSchema = SchemaUtils.partial(
SchemaUtils.pick(userSchema, ['username', 'email'])
);
// public response: hide sensitive fields
const publicSchema = SchemaUtils.omit(userSchema, ['password']);
import { dsl, exporters } from 'schema-dsl';
const productSchema = dsl({
name: 'string:1-100!',
price: 'number:>0!',
stock: 'integer:0-!',
category: 'string!',
createdAt: 'datetime!'
});
// MongoDB $jsonSchema (for db.createCollection() document validation; not a Mongoose model schema)
const mongoSchema = new exporters.MongoDBExporter().export(productSchema);
/*
{
$jsonSchema: {
bsonType: 'object',
properties: {
name: { bsonType: 'string', minLength: 1, maxLength: 100 },
price: { bsonType: 'double', minimum: 0 },
stock: { bsonType: 'int', minimum: 0 },
category: { bsonType: 'string' },
createdAt: { bsonType: 'string' }
},
required: ['name', 'price', 'stock', 'category', 'createdAt']
}
}
*/
// MySQL DDL
const mysqlDDL = new exporters.MySQLExporter().export('products', productSchema);
/*
CREATE TABLE `products` (
`name` VARCHAR(100) NOT NULL,
`price` DECIMAL(10, 2) NOT NULL,
`stock` INT NOT NULL,
`category` VARCHAR(255) NOT NULL,
`createdAt` DATETIME NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*/
// Markdown field documentation
const markdown = exporters.MarkdownExporter.export(productSchema, { title: 'Product Field Reference' });
| Use case | API | Docs |
|---|---|---|
| API parameter validation | validateAsync + ValidationError | Async Validation |
| Form / script validation | validate() | validate() |
| Batch data validation | SchemaUtils.validateBatch() | SchemaUtils |
| create / update derivation | pick / omit / partial | SchemaUtils |
| Database table creation | MongoDBExporter / MySQLExporter | Export Guide |
| Field documentation | MarkdownExporter | Export Guide |
| Multilingual API errors | I18nError | Error Handling |
| Conditional / dynamic rules | dsl.if() / dsl.match() | Conditional API |
| Custom type extensions | PluginManager | Plugin System |
dsl({
// string
name: 'string!', // required
code: 'string:6', // exact length 6
bio: 'string:-500', // max length 500
username: 'string:3-32', // length range 3–32
// number
age: 'number:18-120', // range 18–120
score: 'integer:0-100', // integer 0–100
price: 'number:>0', // strictly greater than 0
level: 'number:>=1', // greater than or equal to 1
// enum
status: 'active|inactive|pending', // string enum
tier: 'enum:number:1|2|3', // numeric enum
// array
tags: 'array<string>', // string array
items: 'array:1-10<number>', // 1–10 numeric elements
// boolean
active: 'boolean!',
// union type
contact: 'types:email|phone!', // email or phone, required
price2: 'types:number:0-|string', // number or string
})
dsl({
email: 'email!', // email address
website: 'url!', // URL
birthday: 'date!', // YYYY-MM-DD
createdAt: 'datetime!', // ISO 8601
userId: 'uuid!', // UUID
phone: 'phone:cn!', // Chinese mobile number
idCard: 'idCard:cn!', // Chinese national ID
slug: 'slug:3-100!', // URL-friendly string
})
import { dsl } from 'schema-dsl';
const schema = dsl({
username: dsl('string:3-32!')
.username()
.label('username')
.messages({ required: 'Username is required' }),
email: dsl('email!').label('email address'),
phone: dsl('string:11!')
.pattern(/^1[3-9]\d{9}$/)
.label('phone number'),
});
// dsl.match — route to different rules based on a field value
const contactSchema = dsl({
type: 'email|phone|wechat',
contact: dsl.match('type', {
email: 'email!',
phone: 'string:11!',
wechat: 'string:6-20!',
})
});
// dsl.if — simple conditional branch
const orderSchema = dsl({
isVip: 'boolean!',
discount: dsl.if('isVip', 'number:10-50!', 'number:0-10')
});
// dsl.if chain assertion
dsl.if(d => !d.account)
.message('Account not found')
.and(d => d.account.balance < amount)
.message('Insufficient balance')
.assert(data);
import { dsl, validate, Locale, I18nError } from 'schema-dsl';
// built-in locales: zh-CN / en-US / ja-JP / es-ES / fr-FR (auto-loaded, no configuration needed)
const result = validate(schema, data, { locale: 'en-US' });
// error messages automatically use the specified locale
// register a custom locale
Locale.addLocale('zh-CN', {
'user.notFound': 'User not found',
'user.forbidden': { code: 40003, message: 'Access forbidden' },
});
// throw i18n business errors
I18nError.assert(user, 'user.notFound'); // auto-throw when user is falsy
I18nError.throw('user.forbidden', {}, 403); // throw directly
I18nError.assert(ok, 'user.notFound', {}, 404, locale); // specify locale at runtime
// errors carry a numeric code; frontend can branch on it
try {
await api.getUser(id);
} catch (error) {
switch (error.code) {
case 40003: showForbiddenPage(); break;
}
}
import { PluginManager, Validator, dsl } from 'schema-dsl';
const pluginManager = new PluginManager();
// register a custom format plugin (must provide an install function)
pluginManager.register({
name: 'extra-formats',
install(core) {
const validator = core as Validator;
// register custom formats on the Validator instance via addFormat
validator.addFormat('hex-color', {
validate: (v: string) => /^#[0-9A-F]{6}$/i.test(v)
});
validator.addFormat('mac-address', {
validate: (v: string) => /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/i.test(v)
});
}
});
// create a Validator and install plugins
const validator = new Validator();
pluginManager.install(validator);
// use the custom formats in a schema
const schema = dsl({ color: 'hex-color!', mac: 'mac-address' });
const result = validator.validate(schema, { color: '#FF5733', mac: '00:1A:2B:3C:4D:5E' });
| API | Purpose | Returns | Docs |
|---|---|---|---|
dsl(schema) | Create a schema | Schema object | DSL Syntax |
validate(schema, data) | Synchronous validation | { valid, errors, data } | validate() |
validateAsync(schema, data) | Asynchronous validation | Promise (throws on failure) | Async Validation |
SchemaUtils.pick() | Select fields | New schema | SchemaUtils |
SchemaUtils.omit() | Exclude fields | New schema | SchemaUtils |
SchemaUtils.partial() | Make all fields optional | New schema | SchemaUtils |
dsl.if(condition) | Conditional validation | ConditionalBuilder | Conditional API |
dsl.match(field, map) | Branch validation | ConditionalBuilder | Conditional API |
I18nError.throw() | Throw an i18n error | never | Error Handling |
I18nError.assert() | Assert then throw | void | Error Handling |
import { dsl, validateAsync, ValidationError } from 'schema-dsl';
// ✅ wrap strings with dsl() in TypeScript for full type inference
const userSchema = dsl({
username: dsl('string:3-32!').label('username'),
email: dsl('email!').label('email'),
age: dsl('number:18-100').label('age')
});
try {
const validData = await validateAsync(userSchema, payload);
// validData has full type inference
} catch (error) {
if (error instanceof ValidationError) {
error.errors.forEach(e => console.log(`${e.path}: ${e.message}`));
}
}
Note: In TypeScript projects, wrap strings with
dsl('...')to get type inference. In JavaScript projects you can pass strings directly. See the TypeScript Guide for details.
npm run build # compile TypeScript
npm run test # run tests
npm run typecheck # type check
Local documentation preview:
cd website
npm run dev
git clone https://github.com/vextjs/schema-dsl.git
cd schema-dsl
npm install
npm test
See CONTRIBUTING.md for details.
ValidationError handlingIf this project is useful to you, please consider giving it a Star ⭐
Made with ❤️ by the schema-dsl team
FAQs
Progressive TypeScript schema DSL for concise, serializable field rules, validation, schema reuse, database/Markdown export, i18n, and runtime isolation
The npm package schema-dsl receives a total of 83 weekly downloads. As such, schema-dsl popularity was classified as not popular.
We found that schema-dsl demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
arXiv now limits authors to two submissions a month as AI slop overwhelms moderators, delays good papers, and sparks debate over applying the limit to everyone.

Research
/Security News
A new GhostAction wave hits hundreds of GitHub repos, expanding CI/CD secret theft to cloud and AI credentials in source code and git history.

Research
/Security News
Tensorlake npm SDK version 0.5.144 was compromised in a ChainDrop / Shai-Hulud attack, delivering credential-stealing malware.