A complete guide to DSL syntax
This page is the main reference for writing schema rules. Read it after Quick Start, then use Type Reference and Validation Guide for deeper behavior.
quick start
import { s } from 'schema-dsl/pure';
//Basic type
const schema = s({
name: 'string!', // required string
age: 'number', // optional number
email: 'email!', // required email
active: 'boolean', // Boolean value
tags: 'array<string>' // String array
});
// Factory entry for stronger method discovery:
const emailField = s.email().label('Email').require();
Use the same schema to validate real data:
import { s, validate } from 'schema-dsl/pure';
const userSchema = s({
name: 'string:1-50!',
email: 'email!',
age: 'number:18-120',
role: 'admin|user|guest'
});
validate(userSchema, {
name: 'John',
email: 'john@example.com',
age: 28,
role: 'user'
}).valid; // true
validate(userSchema, {
name: '',
email: 'not-email',
age: 15,
role: 'owner'
}).valid; // false
Complete list of types
basic type
special type
basic grammar
1. Type definition
//Basic type
'string' // string
'number' // number
'integer' // integer
'boolean' // boolean
// format type
'email' // Email
'url' // URL
'date' // date
'uuid' // UUID
2. Required and optional marks
Use ! to mark required fields and ? to explicitly mark optional fields:
const schema = s({
username: 'string!', // required
nickname: 'string', // optional (default)
bio: 'string?', // Explicitly optional (equivalent to string)
email: 'email?' // Optional email
});
Description:
! - required tag, field must exist
? - optional tag, field can be omitted (explicit expression)
- Unmarked - optional by default (field can be omitted)
recommend:
- Clearly mark required fields using
!
- Use
? to enhance code readability when you need to explicitly express "optional"
3. Object required
Two methods are supported:
// Method 1: Required inside the field
const schema1 = s({
user: {
name: 'string!', // name is required (user is optional)
email: 'email!' // email is required
}
});
// Method 2: The object itself is required ✅ Recommended
const schema2 = s({
'user!': { // user itself is required
name: 'string', // name optional
email: 'email' // email optional
}
});
constraint syntax
1. String length
'string:10' // Exact length 10 (exactLength: minLength=10, maxLength=10)
'string:-10' // Maximum length 10
'string:3-32' // length range 3-32
'string:10-' // Minimum length 10 (no maximum limit)
Example:
const schema = s({
username: 'string:3-32!', // 3-32 characters, required
bio: 'string:500', // Maximum 500 characters
password: 'string:8-' // minimum 8 characters
});
2. Numeric range
'number:100' // Maximum value 100
'number:0-100' // Range 0-100
'number:18-' // minimum value 18
Example:
const schema = s({
age: 'number:18-120', // 18-120
score: 'number:100', // 0-100
price: 'number:0-' // ≥0
});
3. Enumeration values
Use | to separate enumeration values:
const schema = s({
status: 'active|inactive|pending',
gender: 'male|female|other!',
role: 'admin|user|guest'
});
4. types: Union type
When a field needs to accept multiple different types, you can use the types: prefix to generate a union type:
const schema = s({
contact: 'types:email|phone',
price: 'types:number:0-|string:1-20',
payload: 'types:object|array<object>'
});
This syntax will be compiled into the oneOf structure, which is suitable for scenarios where "any one of the types is satisfied".
Applicable scenarios:
- Contact information allows email or mobile phone number
- Price fields allow numeric values or description strings
- Compatible with multiple input formats for the same field in the history interface
5. Special constraints
Support format-specific constraints:
'phone:cn' // China mobile phone number
'idCard:cn' // Chinese ID card
'creditCard:visa' // Visa credit card
'licensePlate:cn' // Chinese license plate
'postalCode:cn' // China postal code
'passport:cn' // Chinese passport
Example:
const schema = s({
mobile: 'phone:cn!',
id: 'idCard:cn',
card: 'creditCard:mastercard'
});
Array syntax
1. Basic array
'array' // Array of any type
'array<string>' // string array
'array<number>' // array of numbers
'array<integer>' // integer array
2. Array length constraints
'array:1-10' // 1-10 elements
'array!1-10' // 1-10 elements, required
'array:1-' // at least 1 element
'array:-10' // up to 10 elements
'array:1-10<string>' // 1-10 string elements
Example:
const schema = s({
tags: 'array!1-10<string>', // required, 1-10 strings
scores: 'array:1-5<number>', // optional, 1-5 numbers
items: 'array:1-<string>' // at least 1 string
});
3. Array element constraints
const schema = s({
tags: 'array<string:1-20>', // 1-20 characters per string
scores: 'array<number:0-100>', // each number 0-100
ids: 'array<integer:1->' // Each integer ≥ 1
});
4. Nested arrays
// two-dimensional array
const schema = s({
matrix: 'array<array<number>>'
});
// array of objects
const schema = s({
users: 'array<object>',
// detailed object item definition
items: s.array({
name: 'string!',
age: 'number'
})
});
object syntax
1. Basic objects
const schema = s({
user: {
name: 'string!',
email: 'email!',
age: 'number'
}
});
2. Nested objects
const schema = s({
user: {
profile: {
bio: 'string:500',
social: {
twitter: 'url',
github: 'url'
}
}
}
});
3. Mixed nesting
const schema = s({
'user!': { // user required
name: 'string!', // name is required
contacts: 'array!1-5<object>', // 1-5 contact information
tags: 'array<string:1-20>' // String array
}
});
Conditional validation (Match)
Supports more elegant conditional validation syntax s.match and s.if.
1. s.match (recommended)
Similar to switch-case, the validation rules for the current field are determined based on the value of a certain field.
grammar:
s.match(field, {
value1: 'schema1',
value2: 'schema2',
_default: 'defaultSchema' // optional
})
Example:
const schema = s({
contactType: 'email|phone',
//Determine the rules for contact based on the value of contactType
contact: s.match('contactType', {
email: 'email!', // when contactType=email
phone: 'string:11!', // when contactType=phone
_default: 'string' // other situations
})
});
Handling non-English values:
If the condition value contains Chinese, numbers or special characters, just add quotes to the key name:
discount: s.match('level', {
'Normal user': 'number:0-5',
'VIP-1': 'number:0-20',
'100': 'number:0-50'
})
2. s.if (simple condition)
Suitable for simple two-choice scenarios.
grammar:
s.if(conditionField, thenSchema, elseSchema)
Example:
const schema = s({
isVip: 'boolean',
// If it is VIP, discount 0-50, otherwise 0-10
discount: s.if('isVip', 'number:0-50', 'number:0-10')
});
Advanced usage
1. Chain call
⚠️ .custom() supports synchronous custom logic; the asynchronous custom validator that returns Promise should be executed through validateAsync().
const schema = s({
username: s('string:3-32!').pattern(/^[a-zA-Z0-9_]+$/)
.label('username')
.messages({
'pattern': 'can only contain letters, numbers and underscores'
}),
email: s('email!').label('email address')
.description('Used to log in and receive notifications')
.custom((value) => {
if (value.endsWith('@blocked.example')) return 'This email domain name is not allowed to be registered';
})
});
2. Default validator
const schema = s({
username: s('string!').username('5-20'), // Automatic regularization + length
phone: s('string!').phone('cn'), // China mobile phone number
password: s('string!').password('strong') // Strong password
});
Things to note
1. Condition validation
⚠️ Note: DSL strings do not support direct writing of conditional logic
'string | number' // ❌ not supported
Solution: Use s.match (recommended)
// ✅ Recommendation: use s.match
const schema = s({
vipLevel: 'string',
discount: s.match('vipLevel', {
gold: 'number:0-50',
silver: 'number:0-20',
normal: 'number:0-5'
})
});
2. Array constraints
✅ Recommended: Use concise DSL syntax
'array!1-10<string:1-20>' // 1-10 elements, 1-20 characters each
✅ Object items can also be written as a DSL object:
s.array({
name: 'string!',
age: 'number:18-'
}).min(1).max(10)
⚠️ Also: Use the complete JSON Schema format when you need direct JSON Schema interoperability
{
type: 'array',
items: { type: 'string' },
minItems: 1,
maxItems: 10
}
3. Regularity validation
⚠️ Note: DSL strings do not support writing regular rules directly
'string:/^[a-z]+$/' // ❌ Not supported
Solution: Use .pattern() method
s('string!').pattern(/^[a-z]+$/) // ✅ Recommended
4. Custom validation
⚠️ Note: DSL strings do not support custom logic
'string!@custom' // ❌ Not supported
Solution: Use .custom() method to carry custom logic; synchronization logic can be used validate() / validateAsync(), Promise-returning logic must use validateAsync()
s('string!').custom((value) => {
// Custom synchronization logic
if (value === 'reserved') {
return 'The value is not available';
}
})
Asynchronous validation (such as database duplication checking) can be placed in Promise-returning .custom() and executed through validateAsync(); it can also be executed separately in the business layer after the structure validation passes.
5. Detailed definition of object array
⚠️ Note: DSL abbreviation does not support detailed definition of object arrays
'array<object{name:string,age:number}>' // ❌ Not supported
Solution: Use s.array({ ... }) or .items({ ... })
const schema = s({
users: s.array({
name: 'string!',
age: 'number:18-'
}).min(1)
});
const usersField = s.array().items({
name: 'string!',
age: 'number:18-'
});
Do not write a field name such as items:array to declare an array type. In schema-dsl, the field name syntax describes the field name itself and required markers such as 'profile!'; the field type belongs in the value:
const schema = s({
items: s.array({
name: 'string!',
price: 'number:0-10000!'
})
});
Complete example
import { s } from 'schema-dsl/pure';
const schema = s({
//Basic information
username: s('string:3-32!').username().label('username'),
password: s('string!').password('strong').label('password'),
email: s('email!').label('mailbox'),
phone: s('string!').phone('cn').label('mobile number'),
// personal data
'profile!': {
realName: 'string:2-50',
gender: 'male|female|other',
birthday: 'date',
bio: 'string:500'
},
//Address information
addresses: s.array({
city: 'string!',
street: 'string!'
}).min(1).max(5), // 1-5 addresses
// Label
tags: 'array:1-10<string:1-20>', // 1-10 tags, each 1-20 characters
// Agree to the terms
agree: 'boolean!'
});
E-commerce product Schema
const schema = s({
// Basic product information
title: 'string:1-100!',
price: 'number:0-!',
stock: 'integer:0-',
status: 'on_sale|off_sale|sold_out!',
// product details
'details!': {
description: 'string:10000',
images: 'array!1-10<url>',
specs: s.array({
name: 'string!',
value: 'string!'
}),
tags: 'array:1-20<string:1-30>'
},
// SKU information
skus: s.array({
sku_code: 'string!',
price: 'number!',
stock: 'integer!'
}).min(1)
});
API request validation
const schema = s({
// Query parameters
page: 'integer:1-',
pageSize: 'integer:10-100',
keyword: 'string:1-50',
// filter conditions
filters: {
category: 'array<string>',
priceRange: {
min: 'number:0-',
max: 'number:0-'
},
status: 'active|inactive'
},
// sort
sort: {
field: 'price|created_at|sales',
order: 'asc|desc'
}
});
FAQ
Q1: Why was the abbreviation function removed?
A: To reduce learning costs and reduce ambiguity. Using the full type name is more clear, especially for newbies.
Q2: How to write array length constraints?
A: Support writing directly in DSL:
'array!1-10<string>' // Recommended
Q3: How to define an object array?
A: Use s.array({ ... }); each field inside the object item keeps using normal DSL:
const schema = s({
users: s.array({
name: 'string!',
email: 'email!'
})
});
Q4: Doesn’t it support conditional validation?
A: Supported. It is recommended to use s.match:
s.match('vipLevel', { gold: 'number:0-50', silver: 'number:0-20' })
Q5: Can I use regular validation?
A: Yes, use .pattern() method:
s('string!').pattern(/^[a-z]+$/)
Corresponding sample file
Example entry: dsl-syntax.ts
Description: Covers the basic types, constraints, enumerations, arrays and nested object writing methods of DSL syntax in Batch 1, and can be run directly for reference.