Skip to content

vextjs/schema-dsl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

114 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

🎯 schema-dsl

Declare field rules with the simplest DSL β€” let one schema drive validation, derivation, export, and documentation.

npm version npm downloads Build Status TypeScript License: MIT

Quick Start Β· Documentation Β· Feature Overview Β· Examples

npm install schema-dsl

⚑ TL;DR (30-second intro)

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:

  • βœ… Sync / async validation β€” validate() / validateAsync()
  • βœ… Schema derivation β€” pick / omit / partial to tailor schemas per endpoint
  • βœ… Database schemas β€” export directly to MongoDB / MySQL / PostgreSQL
  • βœ… Field documentation β€” auto-generate Markdown
  • βœ… Unified error model β€” ValidationError + I18nError
  • βœ… Internationalization β€” 5 built-in locales (zh-CN / en-US / ja-JP / es-ES / fr-FR), switchable at runtime

5-minute tutorial: Quick Start | Full docs: Online Documentation


πŸ—ΊοΈ Documentation

Getting started:

Core features:

Export & integration:

Full docs: Online Documentation Β· Feature Index


✨ Why schema-dsl?

🎯 Minimal DSL β€” 65% less code

❌ Traditional approach β€” verbose

// Joi β€” requires 8 lines
const schema = Joi.object({
  username: Joi.string()
    .min(3).max(32).required(),
  email: Joi.string()
    .email().required(),
  age: Joi.number()
    .min(18).max(120)
});

βœ… schema-dsl β€” concise and clean

// just 3 lines
const schema = dsl({
  username: 'string:3-32!',
  email:    'email!',
  age:      'number:18-120'
});

πŸ’ͺ Full-featured

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

🎨 One schema, many uses (unique capability)

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 / oneOf when every branch resolves to the same SQL column type (for example ipv4 | ipv6). Ambiguous unions such as string | number now throw an explicit error instead of silently choosing the first branch.


πŸ“¦ Installation

npm install schema-dsl

Runtime requirement: Node.js >= 18.0.0


πŸš€ Quick Start

1. Basic validation

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' }
// ]

2. Async validation + Express integration

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);
});

3. Schema reuse (create / update / public)

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']);

4. Database schema export

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' });

πŸ—’οΈ Feature Overview

Common use cases

Use case API Docs
API parameter validation validateAsync + ValidationError Async Validation
Form / script validation validate() Validation Guide
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 Syntax Reference

Basic types

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
})

Built-in formats

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
})

Fluent chain API (recommended for TypeScript)

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'),
});

Conditional validation

// 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);

🌍 Internationalization

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;
  }
}

πŸ”Œ Plugin System

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' });

πŸ”§ Core API Reference

API Purpose Returns Docs
dsl(schema) Create a schema Schema object DSL Syntax
validate(schema, data) Synchronous validation { valid, errors, data } Validation Guide
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

πŸ“ TypeScript Usage

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.


πŸ› οΈ Development

npm run build      # compile TypeScript
npm run test       # run tests
npm run typecheck  # type check

Local documentation preview:

cd website
npm run dev

🀝 Contributing

git clone https://github.com/vextjs/schema-dsl.git
cd schema-dsl
npm install
npm test

See CONTRIBUTING.md for details.


πŸ”— Links

πŸ“– Core documentation

🎯 Feature documentation

πŸ—„οΈ Export & integration

πŸ’» Examples

πŸ“ Changelog & contributing


πŸ“„ License

MIT


If this project is useful to you, please consider giving it a Star ⭐

Made with ❀️ by the schema-dsl team

About

JS DSL data validation with runtime checks, i18n, and DB schema export; reduces code by 65%, ideal for multi-tenant apps.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors