New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more β†’
Get Started

schema-dsl

Package Overview
Dependencies
Maintainers
1
Versions
47
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

schema-dsl

A concise JSON Schema validation library with DSL syntax, side-effect-controlled chainable APIs, and convenient validation helpers

Source
npmnpm
Version
2.1.0
Version published
Weekly downloads
91
Maintainers
1
Weekly downloads
Β 
Created
Source

🎯 schema-dsl

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

πŸ“š Documentation: https://vextjs.github.io/schema-dsl

npm version npm downloads Build Status TypeScript License: Apache-2.0

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

npm install schema-dsl

Release note: this main branch README describes the current source tree. Entries such as schema-dsl/pure, schema-dsl/runtime, schema-dsl/transform, and schema-dsl/esbuild are guaranteed for packages built from this commit; if npm latest is older than this commit, install from the matching tag/commit or wait for the next npm release.

⚑ TL;DR (30-second intro)

What is schema-dsl?

Write field rules like this:

import { s, validate } from 'schema-dsl/pure';

const userSchema = s({
  username: s('string:3-32!').label('Username'),
  email:    s('email!').label('Email'),
  role:     'admin|user|guest',
  contact:  'types:email|phone'
});

const contactEmail = s('email!').label('Email').pattern(/custom/);
const accountEmail = s.email().label('Email').pattern(/custom/).require();

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 Β· Chinese Documentation Β· Feature Index

✨ Why schema-dsl?

🎯 Minimal DSL β€” 65% less code

❌ Manual JSON Schema β€” verbose

const schema = {
  type: 'object',
  properties: {
    username: { type: 'string', minLength: 3, maxLength: 32 },
    email: { type: 'string', format: 'email' },
    age: { type: 'number', minimum: 18, maximum: 120 }
  },
  required: ['username', 'email']
};

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

// just 3 lines
const schema = s({
  username: 'string:3-32!',
  email:    'email!',
  age:      'number:18-120'
});
Featureschema-dslNotes
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
Extension systemβœ…One business type definition for pure DSL, s('...'), and s.xxx() entries; plus formats and validators
Schema reuseβœ…pick / omit / partial / extend
Side-effect-controlled entriesβœ…root compatibility, schema-dsl/pure for no String.prototype installation, and schema-dsl/runtime for isolated runtime state
Compile-time transformβœ…schema-dsl/transform core and optional schema-dsl/esbuild adapter
Progressive s authoringβœ…Use plain DSL strings, s('email!'), or s.email(); all converge to the same builder implementation

🎨 One schema, many uses (unique capability)

import { s, exporters, SchemaUtils } from 'schema-dsl/pure';

const userSchema = s({
  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

This README follows the current main branch source. When consuming from npm, verify that the published version you install includes the entry points shown below.

πŸ“¦ Package Entry Points

schema-dsl keeps the root import compatible with v1-style direct string chaining, and also exposes explicit entries for projects that want tighter control over global side effects.

EntryPurpose
schema-dsl/pureRecommended default entry for application code; exports the s / dsl namespace and validation helpers without installing String.prototype extensions.
schema-dsl/runtimeRuntime adapter factory for per-tenant/per-app isolated Locale messages, messageProvider, TypeRegistry scope, PATTERNS, validator instances and I18nError creation.
schema-dslRoot compatibility entry; imports install the non-enumerable String chain API by default. Prefer schema-dsl/pure for new public examples.
schema-dsl/compatExplicit compatibility entry that installs String extensions on import.
schema-dsl/register-stringSide-effect entry for explicitly registering String extensions during application startup.
schema-dsl/string-typesOpt-in TypeScript declarations for String-chain authoring; no runtime prototype installation.
schema-dsl/transformBabel AST transform core that rewrites static string-chain calls into helper calls imported from schema-dsl/pure. Babel AST packages are optional peer dependencies for projects that use this entry.
schema-dsl/esbuildOptional esbuild plugin adapter around the transform core. esbuild is an optional peer dependency.
import { s, validate } from 'schema-dsl/pure';
import { transformSchemaDsl } from 'schema-dsl/transform';
import { schemaDslEsbuildPlugin } from 'schema-dsl/esbuild';
import { createRuntime } from 'schema-dsl/runtime';

const schema = s({
  email: 'email!',
  username: s('string:3-32!').label('Username'),
  backupEmail: s.email().label('Backup email').require()
});

const transformed = transformSchemaDsl(
  'export const field = "admin|user|guest".label("Role")',
  { filename: 'schema.ts' }
);

const plugins = [schemaDslEsbuildPlugin()];

const tenantRuntime = createRuntime({
  locale: 'tenant-a',
  messages: {
    'tenant.user.missing': { code: 'TENANT_USER_MISSING', message: 'Tenant user {{#id}} is missing' }
  },
  types: {
    tenantId: { type: 'string', pattern: '^tenant_[a-z0-9]+$' }
  },
  messageProvider: ({ key, locale, fallback }) =>
    key === 'number.min' ? `[${locale}] {{#label}} must be >= {{#limit}}` : fallback
});

const tenantSchema = tenantRuntime.s({
  id: 'tenantId!',
  age: 'number:18-120'
});

const tenantEmail = tenantRuntime.s.email().label('Tenant email').require().toSchema();

const tenantResult = tenantRuntime.validate(tenantSchema, { id: 'tenant_demo', age: 16 });

The transform handles static DSL string literals, including naked pipe enums such as "admin|user|guest", and injects imports from schema-dsl/pure. By default it rewrites the complete built-in String-chain API (.label(), .pattern(), .require(), .required(), .toJsonSchema(), and the other methods installed by schema-dsl). Use additionalMethods for user-defined chain methods, and additionalTypes / additionalTypePatterns for registered custom DSL type literals such as "tenant-id!".label("Tenant"); methods remains a legacy replacement set when you intentionally want to override the built-in default list. Dynamic expressions, computed member calls, and already transformed helper calls are left unchanged.

Use schema-dsl/pure for ordinary application code. Use schema-dsl/runtime when a framework needs independent runtime state per app, tenant, worker, or plugin host. createRuntime() keeps message lookup, per-call messageProvider, runtime custom types, namespace factories, pattern overrides, validator caches, custom keyword messages, conditional branches, async custom validators, and createI18nError() inside that runtime instance. Use one runtime for the app/plugin lifecycle, pass request-level locale, messages, messageProvider or { coerce: false } via per-call options, and call configure(..., { mode: 'replace' | 'reset' }), clearCache(), getStats() or dispose() for hot reload and shutdown.

createSchemaDslRuntime() and createSchemaDslAdapter() are equivalent aliases of createRuntime() for adapter-oriented integrations.

πŸš€ Quick Start

1. Basic validation

import { s, validate } from 'schema-dsl/pure';

const userSchema = s({
  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 { s, validateAsync, ValidationError } from 'schema-dsl/pure';

const createUserSchema = s({
  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 { s, SchemaUtils } from 'schema-dsl/pure';

const userSchema = s({
  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 { s, exporters } from 'schema-dsl/pure';

const productSchema = s({
  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`     DOUBLE NOT NULL,
  `stock`     BIGINT NOT NULL,
  `category`  VARCHAR(255) NOT NULL,
  `createdAt` DATETIME NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
*/

// Markdown field documentation
const markdown = exporters.MarkdownExporter.export(productSchema, { title: 'Product Field Reference' });

πŸ—’οΈ Feature Overview

Common use cases

Use caseAPIDocs
API parameter validationvalidateAsync + ValidationErrorAsync Validation
Form / script validationvalidate()validate()
Batch data validationSchemaUtils.validateBatch()SchemaUtils
create / update derivationpick / omit / partialSchemaUtils
Database table creationMongoDBExporter / MySQLExporterExport Guide
Field documentationMarkdownExporterExport Guide
Multilingual API errorsI18nErrorError Handling
Conditional / dynamic ruless.if() / s.match()Conditional API
Custom DSL typesregisterExtensions([...]) / s.registerExtension()Extensions Overview
No global String extensionschema-dsl/pureAPI Reference
Compile-time string-chain transformtransformSchemaDsl() / schemaDslEsbuildPlugin()API Reference

πŸ“– DSL Syntax Reference

Basic types

s({
  // 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

s({
  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 (s progressive entries)

import { s } from 'schema-dsl/pure';

const schema = s({
  username: s('string:3-32!')
    .username()
    .label('username')
    .messages({ required: 'Username is required' }),

  email: s('email!').label('email address'),

  phone: s('string:11!')
    .pattern(/^1[3-9]\d{9}$/)
    .label('phone number'),

  recoveryEmail: s.email()
    .label('recovery email')
    .pattern(/@company\.com$/)
    .require(),
});

Conditional validation

// s.match β€” route to different rules based on a field value
const contactSchema = s({
  type:    'email|phone|wechat',
  contact: s.match('type', {
    email:  'email!',
    phone:  'string:11!',
    wechat: 'string:6-20!',
  })
});

// s.if β€” simple conditional branch
const orderSchema = s({
  isVip:    'boolean!',
  discount: s.if('isVip', 'number:10-50!', 'number:0-10')
});

// s.if chain assertion
s.if(d => !d.account)
  .message('Account not found')
  .and(d => d.account.balance < amount)
  .message('Insufficient balance')
  .assert(data);

🌍 Internationalization

import { s, validate, Locale, I18nError } from 'schema-dsl/pure';

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

πŸ”Œ Extensions and Plugin Packaging

Use custom DSL types when one business type should work through pure DSL strings, s('...'), and s.xxx() factories. Use PluginManager when you want to package validator formats, keywords, lifecycle hooks, or several extension hooks together.

import { PluginManager, Validator, s } from 'schema-dsl/pure';

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 = s({ color: 'hex-color!', mac: 'mac-address' });
const result = validator.validate(schema, { color: '#FF5733', mac: '00:1A:2B:3C:4D:5E' });

πŸ”§ Core API Reference

APIPurposeReturnsDocs
s(schema)Create a schemaSchema objectDSL Syntax
validate(schema, data)Synchronous validation{ valid, errors, data }validate()
validateAsync(schema, data)Asynchronous validationPromise (throws on failure)Async Validation
SchemaUtils.pick()Select fieldsNew schemaSchemaUtils
SchemaUtils.omit()Exclude fieldsNew schemaSchemaUtils
SchemaUtils.partial()Make all fields optional, or only selected fields optional when fields is providedNew schemaSchemaUtils
s.if(condition)Conditional validationConditionalBuilderConditional API
s.match(field, map)Branch validationConditionalBuilderConditional API
I18nError.throw()Throw an i18n errorneverError Handling
I18nError.assert()Assert then throwvoidError Handling
schema-dsl/pureImport the API without installing String extensionsAPI namespaceAPI Reference
schema-dsl/string-typesOpt into TypeScript hints for String-chain authoringType declarationsTypeScript Usage
transformSchemaDsl()Rewrite static string-chain DSL calls at compile time{ code, changed, warnings }API Reference
schemaDslEsbuildPlugin()Use the transform in esbuild build/context flowsesbuild pluginAPI Reference

πŸ“ TypeScript Usage

import { s, validateAsync, ValidationError } from 'schema-dsl/pure';

// βœ… wrap strings with s() in TypeScript for builder method hints
const userSchema = s({
  username: s('string:3-32!').label('username'),
  email:    s('email!').label('email'),
  age:      s('number:18-100').label('age')
});

try {
  const validData = await validateAsync(userSchema, payload);
  // validData's static type is controlled by the generic passed to validateAsync<T>.
  // Use InferSchema / InferDslDefinition for schema-literal value type extraction.
} catch (error) {
  if (error instanceof ValidationError) {
    error.errors.forEach(e => console.log(`${e.path}: ${e.message}`));
  }
}

Note: In TypeScript projects, use s('...') or s.xxx() to get builder chain hints without adding global String declarations. DSL string literals also support lightweight value-type extraction through InferSchema / InferDslString, but constraints such as length ranges, regexes, defaults, and custom validators remain runtime schema rules. 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.

πŸ“– Core documentation

🎯 Feature documentation

πŸ—„οΈ Export & integration

πŸ’» Examples

πŸ“ Changelog & contributing

πŸ“„ License

Apache-2.0

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

Made with ❀️ by the schema-dsl team

Keywords

schema

FAQs

Package last updated on 26 Jun 2026

Related posts