schema-dsl 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +130 -113
  2. package/LICENSE +21 -21
  3. package/README.md +628 -628
  4. package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
  5. package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
  6. package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
  7. package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
  8. package/dist/index.cjs +75 -29
  9. package/dist/index.d.cts +10 -4
  10. package/dist/index.d.ts +10 -4
  11. package/dist/index.js +75 -29
  12. package/dist/plugins/custom-format.cjs +33 -17
  13. package/dist/plugins/custom-format.d.cts +1 -1
  14. package/dist/plugins/custom-format.d.ts +1 -1
  15. package/dist/plugins/custom-format.js +33 -17
  16. package/dist/plugins/custom-type-example.cjs +33 -17
  17. package/dist/plugins/custom-type-example.d.cts +1 -1
  18. package/dist/plugins/custom-type-example.d.ts +1 -1
  19. package/dist/plugins/custom-type-example.js +33 -17
  20. package/dist/plugins/custom-validator.cjs +0 -2
  21. package/dist/plugins/custom-validator.d.cts +1 -1
  22. package/dist/plugins/custom-validator.d.ts +1 -1
  23. package/dist/plugins/custom-validator.js +0 -2
  24. package/docs/FEATURE-INDEX.md +553 -553
  25. package/docs/add-custom-locale.md +496 -496
  26. package/docs/add-keyword.md +24 -24
  27. package/docs/api-reference.md +1047 -1047
  28. package/docs/api.md +13 -13
  29. package/docs/best-practices-project-structure.md +417 -417
  30. package/docs/best-practices.md +712 -712
  31. package/docs/cache-manager.md +344 -344
  32. package/docs/compile.md +45 -45
  33. package/docs/conditional-api.md +1307 -1307
  34. package/docs/custom-extensions-guide.md +339 -339
  35. package/docs/design-philosophy.md +606 -606
  36. package/docs/doc-index.md +324 -324
  37. package/docs/dsl-syntax.md +714 -714
  38. package/docs/dynamic-locale.md +608 -608
  39. package/docs/enum.md +482 -482
  40. package/docs/error-handling.md +1975 -1975
  41. package/docs/export-guide.md +501 -501
  42. package/docs/export-limitations.md +567 -567
  43. package/docs/faq.md +596 -596
  44. package/docs/frontend-i18n-guide.md +307 -307
  45. package/docs/i18n-user-guide.md +487 -487
  46. package/docs/i18n.md +476 -476
  47. package/docs/index.md +48 -48
  48. package/docs/json-schema-basics.md +40 -40
  49. package/docs/label-vs-description.md +271 -271
  50. package/docs/markdown-exporter.md +406 -406
  51. package/docs/mongodb-exporter.md +302 -302
  52. package/docs/multi-language.md +26 -26
  53. package/docs/multi-type-support.md +322 -322
  54. package/docs/mysql-exporter.md +280 -280
  55. package/docs/number-operators.md +449 -449
  56. package/docs/optional-marker-guide.md +326 -326
  57. package/docs/performance-guide.md +49 -49
  58. package/docs/plugin-system.md +381 -381
  59. package/docs/plugin-type-registration.md +34 -34
  60. package/docs/postgresql-exporter.md +311 -311
  61. package/docs/public/favicon.svg +4 -4
  62. package/docs/quick-start.md +435 -435
  63. package/docs/runtime-locale-support.md +532 -532
  64. package/docs/schema-helper.md +345 -345
  65. package/docs/schema-utils-advanced-issues.md +23 -23
  66. package/docs/schema-utils-best-practices.md +20 -20
  67. package/docs/schema-utils-chaining.md +150 -150
  68. package/docs/schema-utils.md +524 -524
  69. package/docs/security-checklist.md +20 -20
  70. package/docs/string-extensions.md +488 -488
  71. package/docs/troubleshooting.md +486 -486
  72. package/docs/type-converter.md +310 -310
  73. package/docs/type-reference.md +242 -242
  74. package/docs/typescript-guide.md +584 -584
  75. package/docs/union-type-guide.md +157 -157
  76. package/docs/union-types.md +284 -284
  77. package/docs/validate-async.md +491 -491
  78. package/docs/validate-batch.md +49 -49
  79. package/docs/validate-dsl-object-support.md +578 -578
  80. package/docs/validate.md +506 -506
  81. package/docs/validation-guide.md +502 -502
  82. package/docs/validator.md +39 -39
  83. package/package.json +131 -131
  84. package/plugins/custom-format.cjs +8 -8
  85. package/plugins/custom-type-example.cjs +8 -8
  86. package/plugins/custom-validator.cjs +8 -8
  87. package/src/adapters/DslAdapter.ts +111 -111
  88. package/src/adapters/index.ts +1 -1
  89. package/src/config/constants.ts +83 -83
  90. package/src/config/index.ts +2 -2
  91. package/src/config/patterns.ts +77 -77
  92. package/src/core/CacheManager.ts +169 -159
  93. package/src/core/ConditionalBuilder.ts +382 -382
  94. package/src/core/ConditionalRuntime.ts +27 -27
  95. package/src/core/ConditionalValidator.ts +254 -254
  96. package/src/core/DslBuilder.ts +687 -677
  97. package/src/core/ErrorCodes.ts +38 -38
  98. package/src/core/ErrorFormatter.ts +271 -271
  99. package/src/core/JSONSchemaCore.ts +65 -65
  100. package/src/core/Locale.ts +187 -187
  101. package/src/core/MessageTemplate.ts +42 -42
  102. package/src/core/ObjectDslBuilder.ts +64 -64
  103. package/src/core/PluginManager.ts +326 -326
  104. package/src/core/StringExtensions.ts +140 -140
  105. package/src/core/TemplateEngine.ts +44 -44
  106. package/src/core/Validator.ts +448 -448
  107. package/src/errors/I18nError.ts +159 -159
  108. package/src/errors/ValidationError.ts +105 -105
  109. package/src/exporters/BaseExporter.ts +60 -60
  110. package/src/exporters/MarkdownExporter.ts +305 -305
  111. package/src/exporters/MongoDBExporter.ts +126 -126
  112. package/src/exporters/MySQLExporter.ts +156 -155
  113. package/src/exporters/PostgreSQLExporter.ts +222 -222
  114. package/src/exporters/index.ts +18 -18
  115. package/src/index.ts +651 -633
  116. package/src/locales/en-US.ts +160 -160
  117. package/src/locales/es-ES.ts +160 -160
  118. package/src/locales/fr-FR.ts +160 -160
  119. package/src/locales/index.ts +103 -103
  120. package/src/locales/ja-JP.ts +160 -160
  121. package/src/locales/types.ts +156 -156
  122. package/src/locales/zh-CN.ts +160 -160
  123. package/src/parser/ConstraintParser.ts +101 -101
  124. package/src/parser/DslParser.ts +470 -470
  125. package/src/parser/SchemaCompiler.ts +66 -66
  126. package/src/parser/TypeRegistry.ts +250 -250
  127. package/src/parser/index.ts +6 -6
  128. package/src/plugins/custom-format.ts +124 -126
  129. package/src/plugins/custom-type-example.ts +106 -108
  130. package/src/plugins/custom-validator.ts +138 -140
  131. package/src/types/conditional.ts +28 -28
  132. package/src/types/config.ts +59 -59
  133. package/src/types/dsl.ts +131 -131
  134. package/src/types/error.ts +60 -60
  135. package/src/types/index.ts +17 -17
  136. package/src/types/infer.ts +127 -127
  137. package/src/types/plugin.ts +58 -58
  138. package/src/types/safe-regex.d.ts +9 -9
  139. package/src/types/schema.ts +66 -66
  140. package/src/types/validate.ts +71 -71
  141. package/src/utils/SchemaHelper.ts +196 -196
  142. package/src/utils/SchemaUtils.ts +365 -346
  143. package/src/utils/TypeConverter.ts +215 -215
  144. package/src/utils/index.ts +10 -10
  145. package/src/validators/CustomKeywords.ts +477 -477
package/README.md CHANGED
@@ -1,628 +1,628 @@
1
- <div align="center">
2
-
3
- # 🎯 schema-dsl
4
-
5
- **Declare field rules with the simplest DSL — let one schema drive validation, derivation, export, and documentation.**
6
-
7
- [![npm version](https://img.shields.io/npm/v/schema-dsl.svg?style=flat-square)](https://www.npmjs.com/package/schema-dsl)
8
- [![npm downloads](https://img.shields.io/npm/dm/schema-dsl.svg?style=flat-square)](https://www.npmjs.com/package/schema-dsl)
9
- [![Build Status](https://github.com/vextjs/schema-dsl/workflows/CI/badge.svg)](https://github.com/vextjs/schema-dsl/actions)
10
- [![TypeScript](https://img.shields.io/badge/TypeScript-Native-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/)
11
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
12
-
13
- [Quick Start](#-quick-start) · [Documentation](https://vextjs.github.io/schema-dsl) · [Feature Overview](#-feature-overview) · [Examples](./examples)
14
-
15
- ```bash
16
- npm install schema-dsl
17
- ```
18
-
19
- </div>
20
-
21
- ---
22
-
23
- ## ⚡ TL;DR (30-second intro)
24
-
25
- **What is schema-dsl?**
26
-
27
- Write field rules like this:
28
-
29
- ```typescript
30
- import { dsl, validate } from 'schema-dsl';
31
-
32
- const userSchema = dsl({
33
- username: 'string:3-32!',
34
- email: 'email!',
35
- role: 'admin|user|guest',
36
- contact: 'types:email|phone'
37
- });
38
-
39
- const result = validate(userSchema, req.body);
40
- ```
41
-
42
- Then that **same set of rules** continues to power:
43
-
44
- - ✅ **Sync / async validation** — `validate()` / `validateAsync()`
45
- - ✅ **Schema derivation** — `pick / omit / partial` to tailor schemas per endpoint
46
- - ✅ **Database schemas** — export directly to MongoDB / MySQL / PostgreSQL
47
- - ✅ **Field documentation** — auto-generate Markdown
48
- - ✅ **Unified error model** — `ValidationError` + `I18nError`
49
- - ✅ **Internationalization** — 5 built-in locales (zh-CN / en-US / ja-JP / es-ES / fr-FR), switchable at runtime
50
-
51
- **5-minute tutorial**: [Quick Start](https://vextjs.github.io/schema-dsl/quick-start) | **Full docs**: [Online Documentation](https://vextjs.github.io/schema-dsl)
52
-
53
- ---
54
-
55
- ## 🗺️ Documentation
56
-
57
- **Getting started**:
58
- - [Quick Start](https://vextjs.github.io/schema-dsl/quick-start) — up and running in 5 minutes
59
- - [DSL Syntax Reference](#-dsl-syntax-reference) — syntax cheatsheet
60
- - [FAQ](https://vextjs.github.io/schema-dsl/faq) — common questions
61
-
62
- **Core features**:
63
- - [Validation Guide](https://vextjs.github.io/schema-dsl/validation-guide) — all validation scenarios
64
- - [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) — schema reuse
65
- - [Conditional Validation API](https://vextjs.github.io/schema-dsl/conditional-api) — dsl.if / dsl.match
66
- - [Async Validation & Framework Integration](https://vextjs.github.io/schema-dsl/validate-async) — Express / Koa / Fastify
67
- - [Error Handling & i18n](https://vextjs.github.io/schema-dsl/error-handling) — error model
68
-
69
- **Export & integration**:
70
- - [Export Guide](https://vextjs.github.io/schema-dsl/export-guide) — MongoDB / MySQL / PostgreSQL
71
- - [TypeScript Guide](https://vextjs.github.io/schema-dsl/typescript-guide) — type inference and usage
72
- - [Plugin System](https://vextjs.github.io/schema-dsl/plugin-system) — custom extensions
73
-
74
- **Full docs**: [Online Documentation](https://vextjs.github.io/schema-dsl) · [Feature Index](https://vextjs.github.io/schema-dsl/FEATURE-INDEX)
75
-
76
- ---
77
-
78
- ## ✨ Why schema-dsl?
79
-
80
- ### 🎯 Minimal DSL — 65% less code
81
-
82
- <table>
83
- <tr>
84
- <td width="50%" valign="top">
85
-
86
- **❌ Traditional approach** — verbose
87
-
88
- ```javascript
89
- // Joi — requires 8 lines
90
- const schema = Joi.object({
91
- username: Joi.string()
92
- .min(3).max(32).required(),
93
- email: Joi.string()
94
- .email().required(),
95
- age: Joi.number()
96
- .min(18).max(120)
97
- });
98
- ```
99
-
100
- </td>
101
- <td width="50%" valign="top">
102
-
103
- **✅ schema-dsl** — concise and clean
104
-
105
- ```typescript
106
- // just 3 lines
107
- const schema = dsl({
108
- username: 'string:3-32!',
109
- email: 'email!',
110
- age: 'number:18-120'
111
- });
112
- ```
113
-
114
- </td>
115
- </tr>
116
- </table>
117
-
118
- ### 💪 Full-featured
119
-
120
- | Feature | schema-dsl | Notes |
121
- |---------|:----------:|-------|
122
- | **Basic validation** | ✅ | string, number, boolean, date, email, url, phone… |
123
- | **Advanced validation** | ✅ | regex, custom functions, conditional branches, nested objects, arrays… |
124
- | **Cross-type union** | ✅ | `types:email\|phone` — one field accepts multiple types |
125
- | **Error messages** | ✅ | auto-translated + custom messages + field labels |
126
- | **i18n business errors** | ✅ | `I18nError` with numeric error codes |
127
- | **Database export** | ✅ | MongoDB / MySQL / PostgreSQL schema generation |
128
- | **Documentation generation** | ✅ | Markdown field docs auto-generated |
129
- | **TypeScript** | ✅ | Written in native TypeScript with full type inference |
130
- | **Plugin system** | ✅ | Custom types / formats / validators |
131
- | **Schema reuse** | ✅ | pick / omit / partial / extend |
132
-
133
- ### 🎨 One schema, many uses (unique capability)
134
-
135
- ```typescript
136
- import { dsl, exporters, SchemaUtils } from 'schema-dsl';
137
-
138
- const userSchema = dsl({
139
- id: 'uuid!',
140
- username: 'string:3-32!',
141
- email: 'email!',
142
- password: 'string:8-64!',
143
- age: 'number:18-120',
144
- createdAt: 'string!'
145
- });
146
-
147
- // 📋 derive scenario-specific schemas
148
- const createSchema = SchemaUtils.omit(userSchema, ['id', 'createdAt']);
149
- const updateSchema = SchemaUtils.partial(SchemaUtils.pick(userSchema, ['username', 'email']));
150
- const publicSchema = SchemaUtils.omit(userSchema, ['password']);
151
-
152
- // 🗄️ export the same schema to any database
153
- const mongoSchema = new exporters.MongoDBExporter().export(userSchema);
154
- const mysqlDDL = new exporters.MySQLExporter().export('users', userSchema);
155
- const pgDDL = new exporters.PostgreSQLExporter().export('users', userSchema);
156
-
157
- // 📝 generate field documentation from the same schema
158
- const markdown = exporters.MarkdownExporter.export(userSchema, { title: 'User Field Reference' });
159
- ```
160
-
161
- > ⚠️ 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.
162
-
163
- ---
164
-
165
- ## 📦 Installation
166
-
167
- ```bash
168
- npm install schema-dsl
169
- ```
170
-
171
- **Runtime requirement**: Node.js >= 18.0.0
172
-
173
- ---
174
-
175
- ## 🚀 Quick Start
176
-
177
- ### 1. Basic validation
178
-
179
- ```typescript
180
- import { dsl, validate } from 'schema-dsl';
181
-
182
- const userSchema = dsl({
183
- username: 'string:3-32!',
184
- email: 'email!',
185
- age: 'number:18-120',
186
- role: 'admin|user|guest',
187
- tags: 'array<string>'
188
- });
189
-
190
- // ✅ validation passed
191
- const result = validate(userSchema, {
192
- username: 'john_doe',
193
- email: 'john@example.com',
194
- age: 25,
195
- role: 'user',
196
- tags: ['verified']
197
- });
198
-
199
- console.log(result.valid); // true
200
- console.log(result.data); // validated data
201
-
202
- // ❌ validation failed
203
- const bad = validate(userSchema, { username: 'ab', email: 'not-email' });
204
- console.log(bad.errors);
205
- // [
206
- // { path: 'username', message: 'username must be at least 3 characters' },
207
- // { path: 'email', message: 'email must be a valid email address' }
208
- // ]
209
- ```
210
-
211
- ### 2. Async validation + Express integration
212
-
213
- ```typescript
214
- import { dsl, validateAsync, ValidationError } from 'schema-dsl';
215
-
216
- const createUserSchema = dsl({
217
- username: 'string:3-32!',
218
- email: 'email!',
219
- password: 'string:8-32!'
220
- });
221
-
222
- app.post('/api/users', async (req, res, next) => {
223
- try {
224
- // throws ValidationError automatically on failure
225
- const validData = await validateAsync(createUserSchema, req.body);
226
- const user = await db.users.create(validData);
227
- res.json({ success: true, data: user });
228
- } catch (error) {
229
- next(error);
230
- }
231
- });
232
-
233
- // global error handler
234
- app.use((error, req, res, next) => {
235
- if (error instanceof ValidationError) {
236
- return res.status(400).json({ success: false, errors: error.errors });
237
- }
238
- next(error);
239
- });
240
- ```
241
-
242
- ### 3. Schema reuse (create / update / public)
243
-
244
- ```typescript
245
- import { dsl, SchemaUtils } from 'schema-dsl';
246
-
247
- const userSchema = dsl({
248
- id: 'uuid!',
249
- username: 'string:3-32!',
250
- email: 'email!',
251
- password: 'string:8-64!',
252
- createdAt: 'string!'
253
- });
254
-
255
- // create endpoint: remove server-generated fields
256
- const createSchema = SchemaUtils.omit(userSchema, ['id', 'createdAt']);
257
-
258
- // update endpoint: pick editable fields, all optional
259
- const updateSchema = SchemaUtils.partial(
260
- SchemaUtils.pick(userSchema, ['username', 'email'])
261
- );
262
-
263
- // public response: hide sensitive fields
264
- const publicSchema = SchemaUtils.omit(userSchema, ['password']);
265
- ```
266
-
267
- ### 4. Database schema export
268
-
269
- ```typescript
270
- import { dsl, exporters } from 'schema-dsl';
271
-
272
- const productSchema = dsl({
273
- name: 'string:1-100!',
274
- price: 'number:>0!',
275
- stock: 'integer:0-!',
276
- category: 'string!',
277
- createdAt: 'datetime!'
278
- });
279
-
280
- // MongoDB $jsonSchema (for db.createCollection() document validation; not a Mongoose model schema)
281
- const mongoSchema = new exporters.MongoDBExporter().export(productSchema);
282
- /*
283
- {
284
- $jsonSchema: {
285
- bsonType: 'object',
286
- properties: {
287
- name: { bsonType: 'string', minLength: 1, maxLength: 100 },
288
- price: { bsonType: 'double', minimum: 0 },
289
- stock: { bsonType: 'int', minimum: 0 },
290
- category: { bsonType: 'string' },
291
- createdAt: { bsonType: 'string' }
292
- },
293
- required: ['name', 'price', 'stock', 'category', 'createdAt']
294
- }
295
- }
296
- */
297
-
298
- // MySQL DDL
299
- const mysqlDDL = new exporters.MySQLExporter().export('products', productSchema);
300
- /*
301
- CREATE TABLE `products` (
302
- `name` VARCHAR(100) NOT NULL,
303
- `price` DECIMAL(10, 2) NOT NULL,
304
- `stock` INT NOT NULL,
305
- `category` VARCHAR(255) NOT NULL,
306
- `createdAt` DATETIME NOT NULL
307
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
308
- */
309
-
310
- // Markdown field documentation
311
- const markdown = exporters.MarkdownExporter.export(productSchema, { title: 'Product Field Reference' });
312
- ```
313
-
314
- ---
315
-
316
- ## 🗒️ Feature Overview
317
-
318
- ### Common use cases
319
-
320
- | Use case | API | Docs |
321
- |----------|-----|------|
322
- | API parameter validation | `validateAsync` + `ValidationError` | [Async Validation](https://vextjs.github.io/schema-dsl/validate-async) |
323
- | Form / script validation | `validate()` | [Validation Guide](https://vextjs.github.io/schema-dsl/validation-guide) |
324
- | Batch data validation | `SchemaUtils.validateBatch()` | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
325
- | create / update derivation | `pick / omit / partial` | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
326
- | Database table creation | `MongoDBExporter / MySQLExporter` | [Export Guide](https://vextjs.github.io/schema-dsl/export-guide) |
327
- | Field documentation | `MarkdownExporter` | [Export Guide](https://vextjs.github.io/schema-dsl/export-guide) |
328
- | Multilingual API errors | `I18nError` | [Error Handling](https://vextjs.github.io/schema-dsl/error-handling) |
329
- | Conditional / dynamic rules | `dsl.if()` / `dsl.match()` | [Conditional API](https://vextjs.github.io/schema-dsl/conditional-api) |
330
- | Custom type extensions | `PluginManager` | [Plugin System](https://vextjs.github.io/schema-dsl/plugin-system) |
331
-
332
- ---
333
-
334
- ## 📖 DSL Syntax Reference
335
-
336
- ### Basic types
337
-
338
- ```typescript
339
- dsl({
340
- // string
341
- name: 'string!', // required
342
- code: 'string:6', // exact length 6
343
- bio: 'string:-500', // max length 500
344
- username: 'string:3-32', // length range 3–32
345
-
346
- // number
347
- age: 'number:18-120', // range 18–120
348
- score: 'integer:0-100', // integer 0–100
349
- price: 'number:>0', // strictly greater than 0
350
- level: 'number:>=1', // greater than or equal to 1
351
-
352
- // enum
353
- status: 'active|inactive|pending', // string enum
354
- tier: 'enum:number:1|2|3', // numeric enum
355
-
356
- // array
357
- tags: 'array<string>', // string array
358
- items: 'array:1-10<number>', // 1–10 numeric elements
359
-
360
- // boolean
361
- active: 'boolean!',
362
-
363
- // union type
364
- contact: 'types:email|phone!', // email or phone, required
365
- price2: 'types:number:0-|string', // number or string
366
- })
367
- ```
368
-
369
- ### Built-in formats
370
-
371
- ```typescript
372
- dsl({
373
- email: 'email!', // email address
374
- website: 'url!', // URL
375
- birthday: 'date!', // YYYY-MM-DD
376
- createdAt: 'datetime!', // ISO 8601
377
- userId: 'uuid!', // UUID
378
- phone: 'phone:cn!', // Chinese mobile number
379
- idCard: 'idCard:cn!', // Chinese national ID
380
- slug: 'slug:3-100!', // URL-friendly string
381
- })
382
- ```
383
-
384
- ### Fluent chain API (recommended for TypeScript)
385
-
386
- ```typescript
387
- import { dsl } from 'schema-dsl';
388
-
389
- const schema = dsl({
390
- username: dsl('string:3-32!')
391
- .username()
392
- .label('username')
393
- .messages({ required: 'Username is required' }),
394
-
395
- email: dsl('email!').label('email address'),
396
-
397
- phone: dsl('string:11!')
398
- .pattern(/^1[3-9]\d{9}$/)
399
- .label('phone number'),
400
- });
401
- ```
402
-
403
- ### Conditional validation
404
-
405
- ```typescript
406
- // dsl.match — route to different rules based on a field value
407
- const contactSchema = dsl({
408
- type: 'email|phone|wechat',
409
- contact: dsl.match('type', {
410
- email: 'email!',
411
- phone: 'string:11!',
412
- wechat: 'string:6-20!',
413
- })
414
- });
415
-
416
- // dsl.if — simple conditional branch
417
- const orderSchema = dsl({
418
- isVip: 'boolean!',
419
- discount: dsl.if('isVip', 'number:10-50!', 'number:0-10')
420
- });
421
-
422
- // dsl.if chain assertion
423
- dsl.if(d => !d.account)
424
- .message('Account not found')
425
- .and(d => d.account.balance < amount)
426
- .message('Insufficient balance')
427
- .assert(data);
428
- ```
429
-
430
- ---
431
-
432
- ## 🌍 Internationalization
433
-
434
- ```typescript
435
- import { dsl, validate, Locale, I18nError } from 'schema-dsl';
436
-
437
- // built-in locales: zh-CN / en-US / ja-JP / es-ES / fr-FR (auto-loaded, no configuration needed)
438
- const result = validate(schema, data, { locale: 'en-US' });
439
- // error messages automatically use the specified locale
440
-
441
- // register a custom locale
442
- Locale.addLocale('zh-CN', {
443
- 'user.notFound': 'User not found',
444
- 'user.forbidden': { code: 40003, message: 'Access forbidden' },
445
- });
446
-
447
- // throw i18n business errors
448
- I18nError.assert(user, 'user.notFound'); // auto-throw when user is falsy
449
- I18nError.throw('user.forbidden', {}, 403); // throw directly
450
- I18nError.assert(ok, 'user.notFound', {}, 404, locale); // specify locale at runtime
451
-
452
- // errors carry a numeric code; frontend can branch on it
453
- try {
454
- await api.getUser(id);
455
- } catch (error) {
456
- switch (error.code) {
457
- case 40003: showForbiddenPage(); break;
458
- }
459
- }
460
- ```
461
-
462
- ---
463
-
464
- ## 🔌 Plugin System
465
-
466
- ```typescript
467
- import { PluginManager, Validator, dsl } from 'schema-dsl';
468
-
469
- const pluginManager = new PluginManager();
470
-
471
- // register a custom format plugin (must provide an install function)
472
- pluginManager.register({
473
- name: 'extra-formats',
474
- install(core) {
475
- const validator = core as Validator;
476
- // register custom formats on the Validator instance via addFormat
477
- validator.addFormat('hex-color', {
478
- validate: (v: string) => /^#[0-9A-F]{6}$/i.test(v)
479
- });
480
- validator.addFormat('mac-address', {
481
- validate: (v: string) => /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/i.test(v)
482
- });
483
- }
484
- });
485
-
486
- // create a Validator and install plugins
487
- const validator = new Validator();
488
- pluginManager.install(validator);
489
-
490
- // use the custom formats in a schema
491
- const schema = dsl({ color: 'hex-color!', mac: 'mac-address' });
492
- const result = validator.validate(schema, { color: '#FF5733', mac: '00:1A:2B:3C:4D:5E' });
493
- ```
494
-
495
- ---
496
-
497
- ## 🔧 Core API Reference
498
-
499
- | API | Purpose | Returns | Docs |
500
- |-----|---------|---------|------|
501
- | `dsl(schema)` | Create a schema | Schema object | [DSL Syntax](https://vextjs.github.io/schema-dsl/dsl-syntax) |
502
- | `validate(schema, data)` | Synchronous validation | `{ valid, errors, data }` | [Validation Guide](https://vextjs.github.io/schema-dsl/validation-guide) |
503
- | `validateAsync(schema, data)` | Asynchronous validation | Promise (throws on failure) | [Async Validation](https://vextjs.github.io/schema-dsl/validate-async) |
504
- | `SchemaUtils.pick()` | Select fields | New schema | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
505
- | `SchemaUtils.omit()` | Exclude fields | New schema | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
506
- | `SchemaUtils.partial()` | Make all fields optional | New schema | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
507
- | `dsl.if(condition)` | Conditional validation | ConditionalBuilder | [Conditional API](https://vextjs.github.io/schema-dsl/conditional-api) |
508
- | `dsl.match(field, map)` | Branch validation | ConditionalBuilder | [Conditional API](https://vextjs.github.io/schema-dsl/conditional-api) |
509
- | `I18nError.throw()` | Throw an i18n error | never | [Error Handling](https://vextjs.github.io/schema-dsl/error-handling) |
510
- | `I18nError.assert()` | Assert then throw | void | [Error Handling](https://vextjs.github.io/schema-dsl/error-handling) |
511
-
512
- ---
513
-
514
- ## 📝 TypeScript Usage
515
-
516
- ```typescript
517
- import { dsl, validateAsync, ValidationError } from 'schema-dsl';
518
-
519
- // ✅ wrap strings with dsl() in TypeScript for full type inference
520
- const userSchema = dsl({
521
- username: dsl('string:3-32!').label('username'),
522
- email: dsl('email!').label('email'),
523
- age: dsl('number:18-100').label('age')
524
- });
525
-
526
- try {
527
- const validData = await validateAsync(userSchema, payload);
528
- // validData has full type inference
529
- } catch (error) {
530
- if (error instanceof ValidationError) {
531
- error.errors.forEach(e => console.log(`${e.path}: ${e.message}`));
532
- }
533
- }
534
- ```
535
-
536
- > **Note**: In TypeScript projects, wrap strings with `dsl('...')` to get type inference. In JavaScript projects you can pass strings directly.
537
- > See the [TypeScript Guide](https://vextjs.github.io/schema-dsl/typescript-guide) for details.
538
-
539
- ---
540
-
541
- ## 🛠️ Development
542
-
543
- ```bash
544
- npm run build # compile TypeScript
545
- npm run test # run tests
546
- npm run typecheck # type check
547
- ```
548
-
549
- Local documentation preview:
550
-
551
- ```bash
552
- cd website
553
- npm run dev
554
- ```
555
-
556
- ---
557
-
558
- ## 🤝 Contributing
559
-
560
- ```bash
561
- git clone https://github.com/vextjs/schema-dsl.git
562
- cd schema-dsl
563
- npm install
564
- npm test
565
- ```
566
-
567
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
568
-
569
- ---
570
-
571
- ## 🔗 Links
572
-
573
- ### 📖 Core documentation
574
- - [Quick Start](https://vextjs.github.io/schema-dsl/quick-start) — up and running in 5 minutes
575
- - [DSL Syntax Guide](https://vextjs.github.io/schema-dsl/dsl-syntax) — complete syntax reference
576
- - [Validation Guide](https://vextjs.github.io/schema-dsl/validation-guide) — advanced validation techniques
577
- - [API Reference](https://vextjs.github.io/schema-dsl/api-reference) — complete API docs
578
- - [TypeScript Guide](https://vextjs.github.io/schema-dsl/typescript-guide) — required reading for TS users
579
- - [Best Practices](https://vextjs.github.io/schema-dsl/best-practices) — avoid common pitfalls
580
- - [Troubleshooting](https://vextjs.github.io/schema-dsl/troubleshooting) — diagnosing issues
581
-
582
- ### 🎯 Feature documentation
583
- - [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils)
584
- - [Conditional Validation API](https://vextjs.github.io/schema-dsl/conditional-api)
585
- - [Async Validation](https://vextjs.github.io/schema-dsl/validate-async)
586
- - [Error Handling & i18n](https://vextjs.github.io/schema-dsl/error-handling)
587
- - [Union Types](https://vextjs.github.io/schema-dsl/union-types)
588
- - [Enum Types](https://vextjs.github.io/schema-dsl/enum)
589
-
590
- ### 🗄️ Export & integration
591
- - [Export Guide](https://vextjs.github.io/schema-dsl/export-guide)
592
- - [MongoDB Exporter](https://vextjs.github.io/schema-dsl/mongodb-exporter)
593
- - [MySQL Exporter](https://vextjs.github.io/schema-dsl/mysql-exporter)
594
- - [PostgreSQL Exporter](https://vextjs.github.io/schema-dsl/postgresql-exporter)
595
- - [Markdown Exporter](https://vextjs.github.io/schema-dsl/markdown-exporter)
596
- - [⚠️ Export Limitations](https://vextjs.github.io/schema-dsl/export-limitations)
597
-
598
- ### 💻 Examples
599
- - [quick-start.ts](./examples/docs/quick-start.ts) — basic usage and registration form
600
- - [validate-async.ts](./examples/docs/validate-async.ts) — async validation and `ValidationError` handling
601
- - [export-guide.ts](./examples/docs/export-guide.ts) — database export overview
602
- - [error-handling.ts](./examples/docs/error-handling.ts) — field errors and business error handling
603
- - [plugin-system.ts](./examples/docs/plugin-system.ts) — plugin system and hooks
604
-
605
- ### 📝 Changelog & contributing
606
- - [Changelog](./CHANGELOG.md)
607
- - [Contributing Guide](./CONTRIBUTING.md)
608
- - [Security Policy](./SECURITY.md)
609
-
610
- ---
611
-
612
- ## 📄 License
613
-
614
- [MIT](./LICENSE)
615
-
616
- ---
617
-
618
- <div align="center">
619
-
620
- If this project is useful to you, please consider giving it a Star ⭐
621
-
622
- Made with ❤️ by the schema-dsl team
623
-
624
- </div>
625
-
626
-
627
-
628
-
1
+ <div align="center">
2
+
3
+ # 🎯 schema-dsl
4
+
5
+ **Declare field rules with the simplest DSL — let one schema drive validation, derivation, export, and documentation.**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/schema-dsl.svg?style=flat-square)](https://www.npmjs.com/package/schema-dsl)
8
+ [![npm downloads](https://img.shields.io/npm/dm/schema-dsl.svg?style=flat-square)](https://www.npmjs.com/package/schema-dsl)
9
+ [![Build Status](https://github.com/vextjs/schema-dsl/workflows/CI/badge.svg)](https://github.com/vextjs/schema-dsl/actions)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Native-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/)
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
12
+
13
+ [Quick Start](#-quick-start) · [Documentation](https://vextjs.github.io/schema-dsl) · [Feature Overview](#-feature-overview) · [Examples](./examples)
14
+
15
+ ```bash
16
+ npm install schema-dsl
17
+ ```
18
+
19
+ </div>
20
+
21
+ ---
22
+
23
+ ## ⚡ TL;DR (30-second intro)
24
+
25
+ **What is schema-dsl?**
26
+
27
+ Write field rules like this:
28
+
29
+ ```typescript
30
+ import { dsl, validate } from 'schema-dsl';
31
+
32
+ const userSchema = dsl({
33
+ username: 'string:3-32!',
34
+ email: 'email!',
35
+ role: 'admin|user|guest',
36
+ contact: 'types:email|phone'
37
+ });
38
+
39
+ const result = validate(userSchema, req.body);
40
+ ```
41
+
42
+ Then that **same set of rules** continues to power:
43
+
44
+ - ✅ **Sync / async validation** — `validate()` / `validateAsync()`
45
+ - ✅ **Schema derivation** — `pick / omit / partial` to tailor schemas per endpoint
46
+ - ✅ **Database schemas** — export directly to MongoDB / MySQL / PostgreSQL
47
+ - ✅ **Field documentation** — auto-generate Markdown
48
+ - ✅ **Unified error model** — `ValidationError` + `I18nError`
49
+ - ✅ **Internationalization** — 5 built-in locales (zh-CN / en-US / ja-JP / es-ES / fr-FR), switchable at runtime
50
+
51
+ **5-minute tutorial**: [Quick Start](https://vextjs.github.io/schema-dsl/quick-start) | **Full docs**: [Online Documentation](https://vextjs.github.io/schema-dsl)
52
+
53
+ ---
54
+
55
+ ## 🗺️ Documentation
56
+
57
+ **Getting started**:
58
+ - [Quick Start](https://vextjs.github.io/schema-dsl/quick-start) — up and running in 5 minutes
59
+ - [DSL Syntax Reference](#-dsl-syntax-reference) — syntax cheatsheet
60
+ - [FAQ](https://vextjs.github.io/schema-dsl/faq) — common questions
61
+
62
+ **Core features**:
63
+ - [Validation Guide](https://vextjs.github.io/schema-dsl/validation-guide) — all validation scenarios
64
+ - [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) — schema reuse
65
+ - [Conditional Validation API](https://vextjs.github.io/schema-dsl/conditional-api) — dsl.if / dsl.match
66
+ - [Async Validation & Framework Integration](https://vextjs.github.io/schema-dsl/validate-async) — Express / Koa / Fastify
67
+ - [Error Handling & i18n](https://vextjs.github.io/schema-dsl/error-handling) — error model
68
+
69
+ **Export & integration**:
70
+ - [Export Guide](https://vextjs.github.io/schema-dsl/export-guide) — MongoDB / MySQL / PostgreSQL
71
+ - [TypeScript Guide](https://vextjs.github.io/schema-dsl/typescript-guide) — type inference and usage
72
+ - [Plugin System](https://vextjs.github.io/schema-dsl/plugin-system) — custom extensions
73
+
74
+ **Full docs**: [Online Documentation](https://vextjs.github.io/schema-dsl) · [Feature Index](https://vextjs.github.io/schema-dsl/FEATURE-INDEX)
75
+
76
+ ---
77
+
78
+ ## ✨ Why schema-dsl?
79
+
80
+ ### 🎯 Minimal DSL — 65% less code
81
+
82
+ <table>
83
+ <tr>
84
+ <td width="50%" valign="top">
85
+
86
+ **❌ Traditional approach** — verbose
87
+
88
+ ```javascript
89
+ // Joi — requires 8 lines
90
+ const schema = Joi.object({
91
+ username: Joi.string()
92
+ .min(3).max(32).required(),
93
+ email: Joi.string()
94
+ .email().required(),
95
+ age: Joi.number()
96
+ .min(18).max(120)
97
+ });
98
+ ```
99
+
100
+ </td>
101
+ <td width="50%" valign="top">
102
+
103
+ **✅ schema-dsl** — concise and clean
104
+
105
+ ```typescript
106
+ // just 3 lines
107
+ const schema = dsl({
108
+ username: 'string:3-32!',
109
+ email: 'email!',
110
+ age: 'number:18-120'
111
+ });
112
+ ```
113
+
114
+ </td>
115
+ </tr>
116
+ </table>
117
+
118
+ ### 💪 Full-featured
119
+
120
+ | Feature | schema-dsl | Notes |
121
+ |---------|:----------:|-------|
122
+ | **Basic validation** | ✅ | string, number, boolean, date, email, url, phone… |
123
+ | **Advanced validation** | ✅ | regex, custom functions, conditional branches, nested objects, arrays… |
124
+ | **Cross-type union** | ✅ | `types:email\|phone` — one field accepts multiple types |
125
+ | **Error messages** | ✅ | auto-translated + custom messages + field labels |
126
+ | **i18n business errors** | ✅ | `I18nError` with numeric error codes |
127
+ | **Database export** | ✅ | MongoDB / MySQL / PostgreSQL schema generation |
128
+ | **Documentation generation** | ✅ | Markdown field docs auto-generated |
129
+ | **TypeScript** | ✅ | Written in native TypeScript with full type inference |
130
+ | **Plugin system** | ✅ | Custom types / formats / validators |
131
+ | **Schema reuse** | ✅ | pick / omit / partial / extend |
132
+
133
+ ### 🎨 One schema, many uses (unique capability)
134
+
135
+ ```typescript
136
+ import { dsl, exporters, SchemaUtils } from 'schema-dsl';
137
+
138
+ const userSchema = dsl({
139
+ id: 'uuid!',
140
+ username: 'string:3-32!',
141
+ email: 'email!',
142
+ password: 'string:8-64!',
143
+ age: 'number:18-120',
144
+ createdAt: 'string!'
145
+ });
146
+
147
+ // 📋 derive scenario-specific schemas
148
+ const createSchema = SchemaUtils.omit(userSchema, ['id', 'createdAt']);
149
+ const updateSchema = SchemaUtils.partial(SchemaUtils.pick(userSchema, ['username', 'email']));
150
+ const publicSchema = SchemaUtils.omit(userSchema, ['password']);
151
+
152
+ // 🗄️ export the same schema to any database
153
+ const mongoSchema = new exporters.MongoDBExporter().export(userSchema);
154
+ const mysqlDDL = new exporters.MySQLExporter().export('users', userSchema);
155
+ const pgDDL = new exporters.PostgreSQLExporter().export('users', userSchema);
156
+
157
+ // 📝 generate field documentation from the same schema
158
+ const markdown = exporters.MarkdownExporter.export(userSchema, { title: 'User Field Reference' });
159
+ ```
160
+
161
+ > ⚠️ 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.
162
+
163
+ ---
164
+
165
+ ## 📦 Installation
166
+
167
+ ```bash
168
+ npm install schema-dsl
169
+ ```
170
+
171
+ **Runtime requirement**: Node.js >= 18.0.0
172
+
173
+ ---
174
+
175
+ ## 🚀 Quick Start
176
+
177
+ ### 1. Basic validation
178
+
179
+ ```typescript
180
+ import { dsl, validate } from 'schema-dsl';
181
+
182
+ const userSchema = dsl({
183
+ username: 'string:3-32!',
184
+ email: 'email!',
185
+ age: 'number:18-120',
186
+ role: 'admin|user|guest',
187
+ tags: 'array<string>'
188
+ });
189
+
190
+ // ✅ validation passed
191
+ const result = validate(userSchema, {
192
+ username: 'john_doe',
193
+ email: 'john@example.com',
194
+ age: 25,
195
+ role: 'user',
196
+ tags: ['verified']
197
+ });
198
+
199
+ console.log(result.valid); // true
200
+ console.log(result.data); // validated data
201
+
202
+ // ❌ validation failed
203
+ const bad = validate(userSchema, { username: 'ab', email: 'not-email' });
204
+ console.log(bad.errors);
205
+ // [
206
+ // { path: 'username', message: 'username must be at least 3 characters' },
207
+ // { path: 'email', message: 'email must be a valid email address' }
208
+ // ]
209
+ ```
210
+
211
+ ### 2. Async validation + Express integration
212
+
213
+ ```typescript
214
+ import { dsl, validateAsync, ValidationError } from 'schema-dsl';
215
+
216
+ const createUserSchema = dsl({
217
+ username: 'string:3-32!',
218
+ email: 'email!',
219
+ password: 'string:8-32!'
220
+ });
221
+
222
+ app.post('/api/users', async (req, res, next) => {
223
+ try {
224
+ // throws ValidationError automatically on failure
225
+ const validData = await validateAsync(createUserSchema, req.body);
226
+ const user = await db.users.create(validData);
227
+ res.json({ success: true, data: user });
228
+ } catch (error) {
229
+ next(error);
230
+ }
231
+ });
232
+
233
+ // global error handler
234
+ app.use((error, req, res, next) => {
235
+ if (error instanceof ValidationError) {
236
+ return res.status(400).json({ success: false, errors: error.errors });
237
+ }
238
+ next(error);
239
+ });
240
+ ```
241
+
242
+ ### 3. Schema reuse (create / update / public)
243
+
244
+ ```typescript
245
+ import { dsl, SchemaUtils } from 'schema-dsl';
246
+
247
+ const userSchema = dsl({
248
+ id: 'uuid!',
249
+ username: 'string:3-32!',
250
+ email: 'email!',
251
+ password: 'string:8-64!',
252
+ createdAt: 'string!'
253
+ });
254
+
255
+ // create endpoint: remove server-generated fields
256
+ const createSchema = SchemaUtils.omit(userSchema, ['id', 'createdAt']);
257
+
258
+ // update endpoint: pick editable fields, all optional
259
+ const updateSchema = SchemaUtils.partial(
260
+ SchemaUtils.pick(userSchema, ['username', 'email'])
261
+ );
262
+
263
+ // public response: hide sensitive fields
264
+ const publicSchema = SchemaUtils.omit(userSchema, ['password']);
265
+ ```
266
+
267
+ ### 4. Database schema export
268
+
269
+ ```typescript
270
+ import { dsl, exporters } from 'schema-dsl';
271
+
272
+ const productSchema = dsl({
273
+ name: 'string:1-100!',
274
+ price: 'number:>0!',
275
+ stock: 'integer:0-!',
276
+ category: 'string!',
277
+ createdAt: 'datetime!'
278
+ });
279
+
280
+ // MongoDB $jsonSchema (for db.createCollection() document validation; not a Mongoose model schema)
281
+ const mongoSchema = new exporters.MongoDBExporter().export(productSchema);
282
+ /*
283
+ {
284
+ $jsonSchema: {
285
+ bsonType: 'object',
286
+ properties: {
287
+ name: { bsonType: 'string', minLength: 1, maxLength: 100 },
288
+ price: { bsonType: 'double', minimum: 0 },
289
+ stock: { bsonType: 'int', minimum: 0 },
290
+ category: { bsonType: 'string' },
291
+ createdAt: { bsonType: 'string' }
292
+ },
293
+ required: ['name', 'price', 'stock', 'category', 'createdAt']
294
+ }
295
+ }
296
+ */
297
+
298
+ // MySQL DDL
299
+ const mysqlDDL = new exporters.MySQLExporter().export('products', productSchema);
300
+ /*
301
+ CREATE TABLE `products` (
302
+ `name` VARCHAR(100) NOT NULL,
303
+ `price` DECIMAL(10, 2) NOT NULL,
304
+ `stock` INT NOT NULL,
305
+ `category` VARCHAR(255) NOT NULL,
306
+ `createdAt` DATETIME NOT NULL
307
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
308
+ */
309
+
310
+ // Markdown field documentation
311
+ const markdown = exporters.MarkdownExporter.export(productSchema, { title: 'Product Field Reference' });
312
+ ```
313
+
314
+ ---
315
+
316
+ ## 🗒️ Feature Overview
317
+
318
+ ### Common use cases
319
+
320
+ | Use case | API | Docs |
321
+ |----------|-----|------|
322
+ | API parameter validation | `validateAsync` + `ValidationError` | [Async Validation](https://vextjs.github.io/schema-dsl/validate-async) |
323
+ | Form / script validation | `validate()` | [Validation Guide](https://vextjs.github.io/schema-dsl/validation-guide) |
324
+ | Batch data validation | `SchemaUtils.validateBatch()` | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
325
+ | create / update derivation | `pick / omit / partial` | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
326
+ | Database table creation | `MongoDBExporter / MySQLExporter` | [Export Guide](https://vextjs.github.io/schema-dsl/export-guide) |
327
+ | Field documentation | `MarkdownExporter` | [Export Guide](https://vextjs.github.io/schema-dsl/export-guide) |
328
+ | Multilingual API errors | `I18nError` | [Error Handling](https://vextjs.github.io/schema-dsl/error-handling) |
329
+ | Conditional / dynamic rules | `dsl.if()` / `dsl.match()` | [Conditional API](https://vextjs.github.io/schema-dsl/conditional-api) |
330
+ | Custom type extensions | `PluginManager` | [Plugin System](https://vextjs.github.io/schema-dsl/plugin-system) |
331
+
332
+ ---
333
+
334
+ ## 📖 DSL Syntax Reference
335
+
336
+ ### Basic types
337
+
338
+ ```typescript
339
+ dsl({
340
+ // string
341
+ name: 'string!', // required
342
+ code: 'string:6', // exact length 6
343
+ bio: 'string:-500', // max length 500
344
+ username: 'string:3-32', // length range 3–32
345
+
346
+ // number
347
+ age: 'number:18-120', // range 18–120
348
+ score: 'integer:0-100', // integer 0–100
349
+ price: 'number:>0', // strictly greater than 0
350
+ level: 'number:>=1', // greater than or equal to 1
351
+
352
+ // enum
353
+ status: 'active|inactive|pending', // string enum
354
+ tier: 'enum:number:1|2|3', // numeric enum
355
+
356
+ // array
357
+ tags: 'array<string>', // string array
358
+ items: 'array:1-10<number>', // 1–10 numeric elements
359
+
360
+ // boolean
361
+ active: 'boolean!',
362
+
363
+ // union type
364
+ contact: 'types:email|phone!', // email or phone, required
365
+ price2: 'types:number:0-|string', // number or string
366
+ })
367
+ ```
368
+
369
+ ### Built-in formats
370
+
371
+ ```typescript
372
+ dsl({
373
+ email: 'email!', // email address
374
+ website: 'url!', // URL
375
+ birthday: 'date!', // YYYY-MM-DD
376
+ createdAt: 'datetime!', // ISO 8601
377
+ userId: 'uuid!', // UUID
378
+ phone: 'phone:cn!', // Chinese mobile number
379
+ idCard: 'idCard:cn!', // Chinese national ID
380
+ slug: 'slug:3-100!', // URL-friendly string
381
+ })
382
+ ```
383
+
384
+ ### Fluent chain API (recommended for TypeScript)
385
+
386
+ ```typescript
387
+ import { dsl } from 'schema-dsl';
388
+
389
+ const schema = dsl({
390
+ username: dsl('string:3-32!')
391
+ .username()
392
+ .label('username')
393
+ .messages({ required: 'Username is required' }),
394
+
395
+ email: dsl('email!').label('email address'),
396
+
397
+ phone: dsl('string:11!')
398
+ .pattern(/^1[3-9]\d{9}$/)
399
+ .label('phone number'),
400
+ });
401
+ ```
402
+
403
+ ### Conditional validation
404
+
405
+ ```typescript
406
+ // dsl.match — route to different rules based on a field value
407
+ const contactSchema = dsl({
408
+ type: 'email|phone|wechat',
409
+ contact: dsl.match('type', {
410
+ email: 'email!',
411
+ phone: 'string:11!',
412
+ wechat: 'string:6-20!',
413
+ })
414
+ });
415
+
416
+ // dsl.if — simple conditional branch
417
+ const orderSchema = dsl({
418
+ isVip: 'boolean!',
419
+ discount: dsl.if('isVip', 'number:10-50!', 'number:0-10')
420
+ });
421
+
422
+ // dsl.if chain assertion
423
+ dsl.if(d => !d.account)
424
+ .message('Account not found')
425
+ .and(d => d.account.balance < amount)
426
+ .message('Insufficient balance')
427
+ .assert(data);
428
+ ```
429
+
430
+ ---
431
+
432
+ ## 🌍 Internationalization
433
+
434
+ ```typescript
435
+ import { dsl, validate, Locale, I18nError } from 'schema-dsl';
436
+
437
+ // built-in locales: zh-CN / en-US / ja-JP / es-ES / fr-FR (auto-loaded, no configuration needed)
438
+ const result = validate(schema, data, { locale: 'en-US' });
439
+ // error messages automatically use the specified locale
440
+
441
+ // register a custom locale
442
+ Locale.addLocale('zh-CN', {
443
+ 'user.notFound': 'User not found',
444
+ 'user.forbidden': { code: 40003, message: 'Access forbidden' },
445
+ });
446
+
447
+ // throw i18n business errors
448
+ I18nError.assert(user, 'user.notFound'); // auto-throw when user is falsy
449
+ I18nError.throw('user.forbidden', {}, 403); // throw directly
450
+ I18nError.assert(ok, 'user.notFound', {}, 404, locale); // specify locale at runtime
451
+
452
+ // errors carry a numeric code; frontend can branch on it
453
+ try {
454
+ await api.getUser(id);
455
+ } catch (error) {
456
+ switch (error.code) {
457
+ case 40003: showForbiddenPage(); break;
458
+ }
459
+ }
460
+ ```
461
+
462
+ ---
463
+
464
+ ## 🔌 Plugin System
465
+
466
+ ```typescript
467
+ import { PluginManager, Validator, dsl } from 'schema-dsl';
468
+
469
+ const pluginManager = new PluginManager();
470
+
471
+ // register a custom format plugin (must provide an install function)
472
+ pluginManager.register({
473
+ name: 'extra-formats',
474
+ install(core) {
475
+ const validator = core as Validator;
476
+ // register custom formats on the Validator instance via addFormat
477
+ validator.addFormat('hex-color', {
478
+ validate: (v: string) => /^#[0-9A-F]{6}$/i.test(v)
479
+ });
480
+ validator.addFormat('mac-address', {
481
+ validate: (v: string) => /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/i.test(v)
482
+ });
483
+ }
484
+ });
485
+
486
+ // create a Validator and install plugins
487
+ const validator = new Validator();
488
+ pluginManager.install(validator);
489
+
490
+ // use the custom formats in a schema
491
+ const schema = dsl({ color: 'hex-color!', mac: 'mac-address' });
492
+ const result = validator.validate(schema, { color: '#FF5733', mac: '00:1A:2B:3C:4D:5E' });
493
+ ```
494
+
495
+ ---
496
+
497
+ ## 🔧 Core API Reference
498
+
499
+ | API | Purpose | Returns | Docs |
500
+ |-----|---------|---------|------|
501
+ | `dsl(schema)` | Create a schema | Schema object | [DSL Syntax](https://vextjs.github.io/schema-dsl/dsl-syntax) |
502
+ | `validate(schema, data)` | Synchronous validation | `{ valid, errors, data }` | [Validation Guide](https://vextjs.github.io/schema-dsl/validation-guide) |
503
+ | `validateAsync(schema, data)` | Asynchronous validation | Promise (throws on failure) | [Async Validation](https://vextjs.github.io/schema-dsl/validate-async) |
504
+ | `SchemaUtils.pick()` | Select fields | New schema | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
505
+ | `SchemaUtils.omit()` | Exclude fields | New schema | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
506
+ | `SchemaUtils.partial()` | Make all fields optional | New schema | [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils) |
507
+ | `dsl.if(condition)` | Conditional validation | ConditionalBuilder | [Conditional API](https://vextjs.github.io/schema-dsl/conditional-api) |
508
+ | `dsl.match(field, map)` | Branch validation | ConditionalBuilder | [Conditional API](https://vextjs.github.io/schema-dsl/conditional-api) |
509
+ | `I18nError.throw()` | Throw an i18n error | never | [Error Handling](https://vextjs.github.io/schema-dsl/error-handling) |
510
+ | `I18nError.assert()` | Assert then throw | void | [Error Handling](https://vextjs.github.io/schema-dsl/error-handling) |
511
+
512
+ ---
513
+
514
+ ## 📝 TypeScript Usage
515
+
516
+ ```typescript
517
+ import { dsl, validateAsync, ValidationError } from 'schema-dsl';
518
+
519
+ // ✅ wrap strings with dsl() in TypeScript for full type inference
520
+ const userSchema = dsl({
521
+ username: dsl('string:3-32!').label('username'),
522
+ email: dsl('email!').label('email'),
523
+ age: dsl('number:18-100').label('age')
524
+ });
525
+
526
+ try {
527
+ const validData = await validateAsync(userSchema, payload);
528
+ // validData has full type inference
529
+ } catch (error) {
530
+ if (error instanceof ValidationError) {
531
+ error.errors.forEach(e => console.log(`${e.path}: ${e.message}`));
532
+ }
533
+ }
534
+ ```
535
+
536
+ > **Note**: In TypeScript projects, wrap strings with `dsl('...')` to get type inference. In JavaScript projects you can pass strings directly.
537
+ > See the [TypeScript Guide](https://vextjs.github.io/schema-dsl/typescript-guide) for details.
538
+
539
+ ---
540
+
541
+ ## 🛠️ Development
542
+
543
+ ```bash
544
+ npm run build # compile TypeScript
545
+ npm run test # run tests
546
+ npm run typecheck # type check
547
+ ```
548
+
549
+ Local documentation preview:
550
+
551
+ ```bash
552
+ cd website
553
+ npm run dev
554
+ ```
555
+
556
+ ---
557
+
558
+ ## 🤝 Contributing
559
+
560
+ ```bash
561
+ git clone https://github.com/vextjs/schema-dsl.git
562
+ cd schema-dsl
563
+ npm install
564
+ npm test
565
+ ```
566
+
567
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
568
+
569
+ ---
570
+
571
+ ## 🔗 Links
572
+
573
+ ### 📖 Core documentation
574
+ - [Quick Start](https://vextjs.github.io/schema-dsl/quick-start) — up and running in 5 minutes
575
+ - [DSL Syntax Guide](https://vextjs.github.io/schema-dsl/dsl-syntax) — complete syntax reference
576
+ - [Validation Guide](https://vextjs.github.io/schema-dsl/validation-guide) — advanced validation techniques
577
+ - [API Reference](https://vextjs.github.io/schema-dsl/api-reference) — complete API docs
578
+ - [TypeScript Guide](https://vextjs.github.io/schema-dsl/typescript-guide) — required reading for TS users
579
+ - [Best Practices](https://vextjs.github.io/schema-dsl/best-practices) — avoid common pitfalls
580
+ - [Troubleshooting](https://vextjs.github.io/schema-dsl/troubleshooting) — diagnosing issues
581
+
582
+ ### 🎯 Feature documentation
583
+ - [SchemaUtils](https://vextjs.github.io/schema-dsl/schema-utils)
584
+ - [Conditional Validation API](https://vextjs.github.io/schema-dsl/conditional-api)
585
+ - [Async Validation](https://vextjs.github.io/schema-dsl/validate-async)
586
+ - [Error Handling & i18n](https://vextjs.github.io/schema-dsl/error-handling)
587
+ - [Union Types](https://vextjs.github.io/schema-dsl/union-types)
588
+ - [Enum Types](https://vextjs.github.io/schema-dsl/enum)
589
+
590
+ ### 🗄️ Export & integration
591
+ - [Export Guide](https://vextjs.github.io/schema-dsl/export-guide)
592
+ - [MongoDB Exporter](https://vextjs.github.io/schema-dsl/mongodb-exporter)
593
+ - [MySQL Exporter](https://vextjs.github.io/schema-dsl/mysql-exporter)
594
+ - [PostgreSQL Exporter](https://vextjs.github.io/schema-dsl/postgresql-exporter)
595
+ - [Markdown Exporter](https://vextjs.github.io/schema-dsl/markdown-exporter)
596
+ - [⚠️ Export Limitations](https://vextjs.github.io/schema-dsl/export-limitations)
597
+
598
+ ### 💻 Examples
599
+ - [quick-start.ts](./examples/docs/quick-start.ts) — basic usage and registration form
600
+ - [validate-async.ts](./examples/docs/validate-async.ts) — async validation and `ValidationError` handling
601
+ - [export-guide.ts](./examples/docs/export-guide.ts) — database export overview
602
+ - [error-handling.ts](./examples/docs/error-handling.ts) — field errors and business error handling
603
+ - [plugin-system.ts](./examples/docs/plugin-system.ts) — plugin system and hooks
604
+
605
+ ### 📝 Changelog & contributing
606
+ - [Changelog](./CHANGELOG.md)
607
+ - [Contributing Guide](./CONTRIBUTING.md)
608
+ - [Security Policy](./SECURITY.md)
609
+
610
+ ---
611
+
612
+ ## 📄 License
613
+
614
+ [MIT](./LICENSE)
615
+
616
+ ---
617
+
618
+ <div align="center">
619
+
620
+ If this project is useful to you, please consider giving it a Star ⭐
621
+
622
+ Made with ❤️ by the schema-dsl team
623
+
624
+ </div>
625
+
626
+
627
+
628
+