schema-dsl 1.2.5 → 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 (243) hide show
  1. package/CHANGELOG.md +130 -238
  2. package/LICENSE +21 -21
  3. package/README.md +628 -2486
  4. package/dist/DslBuilder-BIgQOAXp.d.ts +343 -0
  5. package/dist/DslBuilder-CjHTucNQ.d.cts +343 -0
  6. package/dist/Validator-CllRdrY0.d.ts +192 -0
  7. package/dist/Validator-D6okG9tr.d.cts +192 -0
  8. package/dist/index.cjs +6640 -0
  9. package/dist/index.d.cts +1151 -0
  10. package/dist/index.d.ts +1151 -0
  11. package/dist/index.js +6574 -0
  12. package/dist/plugin-CIKtTMtS.d.cts +246 -0
  13. package/dist/plugin-CIKtTMtS.d.ts +246 -0
  14. package/dist/plugins/custom-format.cjs +3818 -0
  15. package/dist/plugins/custom-format.d.cts +12 -0
  16. package/dist/plugins/custom-format.d.ts +12 -0
  17. package/dist/plugins/custom-format.js +3788 -0
  18. package/dist/plugins/custom-type-example.cjs +3811 -0
  19. package/dist/plugins/custom-type-example.d.cts +8 -0
  20. package/dist/plugins/custom-type-example.d.ts +8 -0
  21. package/dist/plugins/custom-type-example.js +3781 -0
  22. package/dist/plugins/custom-validator.cjs +144 -0
  23. package/dist/plugins/custom-validator.d.cts +10 -0
  24. package/dist/plugins/custom-validator.d.ts +10 -0
  25. package/dist/plugins/custom-validator.js +119 -0
  26. package/docs/FEATURE-INDEX.md +553 -519
  27. package/docs/add-custom-locale.md +496 -483
  28. package/docs/add-keyword.md +24 -0
  29. package/docs/api-reference.md +1047 -805
  30. package/docs/api.md +13 -0
  31. package/docs/best-practices-project-structure.md +417 -408
  32. package/docs/best-practices.md +712 -672
  33. package/docs/cache-manager.md +344 -336
  34. package/docs/compile.md +45 -0
  35. package/docs/conditional-api.md +1307 -1278
  36. package/docs/custom-extensions-guide.md +339 -411
  37. package/docs/design-philosophy.md +606 -601
  38. package/docs/doc-index.md +324 -0
  39. package/docs/dsl-syntax.md +714 -664
  40. package/docs/dynamic-locale.md +608 -598
  41. package/docs/enum.md +482 -475
  42. package/docs/error-handling.md +1975 -1966
  43. package/docs/export-guide.md +501 -462
  44. package/docs/export-limitations.md +567 -551
  45. package/docs/faq.md +596 -577
  46. package/docs/frontend-i18n-guide.md +307 -293
  47. package/docs/i18n-user-guide.md +487 -474
  48. package/docs/i18n.md +476 -457
  49. package/docs/index.md +48 -0
  50. package/docs/json-schema-basics.md +40 -0
  51. package/docs/label-vs-description.md +271 -262
  52. package/docs/markdown-exporter.md +406 -397
  53. package/docs/mongodb-exporter.md +302 -295
  54. package/docs/multi-language.md +26 -0
  55. package/docs/multi-type-support.md +322 -329
  56. package/docs/mysql-exporter.md +280 -273
  57. package/docs/number-operators.md +449 -442
  58. package/docs/optional-marker-guide.md +326 -321
  59. package/docs/performance-guide.md +49 -0
  60. package/docs/plugin-system.md +381 -542
  61. package/docs/plugin-type-registration.md +34 -0
  62. package/docs/postgresql-exporter.md +311 -304
  63. package/docs/public/favicon.svg +5 -0
  64. package/docs/quick-start.md +435 -761
  65. package/docs/runtime-locale-support.md +532 -521
  66. package/docs/schema-helper.md +345 -340
  67. package/docs/schema-utils-advanced-issues.md +23 -0
  68. package/docs/schema-utils-best-practices.md +20 -0
  69. package/docs/schema-utils-chaining.md +150 -143
  70. package/docs/schema-utils.md +524 -490
  71. package/docs/security-checklist.md +20 -0
  72. package/docs/string-extensions.md +488 -480
  73. package/docs/troubleshooting.md +486 -471
  74. package/docs/type-converter.md +310 -319
  75. package/docs/type-reference.md +242 -219
  76. package/docs/typescript-guide.md +584 -573
  77. package/docs/union-type-guide.md +157 -147
  78. package/docs/union-types.md +284 -277
  79. package/docs/validate-async.md +491 -480
  80. package/docs/validate-batch.md +49 -0
  81. package/docs/validate-dsl-object-support.md +578 -573
  82. package/docs/validate.md +506 -486
  83. package/docs/validation-guide.md +502 -484
  84. package/docs/validator.md +39 -0
  85. package/package.json +131 -73
  86. package/plugins/custom-format.cjs +8 -0
  87. package/plugins/custom-type-example.cjs +8 -0
  88. package/plugins/custom-validator.cjs +8 -0
  89. package/src/adapters/DslAdapter.ts +111 -0
  90. package/src/adapters/index.ts +1 -0
  91. package/src/config/constants.ts +83 -0
  92. package/src/config/index.ts +2 -0
  93. package/src/config/patterns.ts +77 -0
  94. package/src/core/CacheManager.ts +169 -0
  95. package/src/core/ConditionalBuilder.ts +382 -0
  96. package/src/core/ConditionalRuntime.ts +28 -0
  97. package/src/core/ConditionalValidator.ts +255 -0
  98. package/src/core/DslBuilder.ts +687 -0
  99. package/src/core/ErrorCodes.ts +38 -0
  100. package/src/core/ErrorFormatter.ts +271 -0
  101. package/src/core/JSONSchemaCore.ts +65 -0
  102. package/src/core/Locale.ts +187 -0
  103. package/src/core/MessageTemplate.ts +42 -0
  104. package/src/core/ObjectDslBuilder.ts +64 -0
  105. package/src/core/PluginManager.ts +326 -0
  106. package/src/core/StringExtensions.ts +140 -0
  107. package/src/core/TemplateEngine.ts +44 -0
  108. package/src/core/Validator.ts +448 -0
  109. package/src/errors/I18nError.ts +159 -0
  110. package/src/errors/ValidationError.ts +105 -0
  111. package/src/exporters/BaseExporter.ts +60 -0
  112. package/src/exporters/MarkdownExporter.ts +305 -0
  113. package/src/exporters/MongoDBExporter.ts +126 -0
  114. package/src/exporters/MySQLExporter.ts +156 -0
  115. package/src/exporters/PostgreSQLExporter.ts +222 -0
  116. package/src/exporters/index.ts +18 -0
  117. package/src/index.ts +651 -0
  118. package/{lib/locales/en-US.js → src/locales/en-US.ts} +160 -176
  119. package/{lib/locales/es-ES.js → src/locales/es-ES.ts} +160 -113
  120. package/{lib/locales/fr-FR.js → src/locales/fr-FR.ts} +160 -113
  121. package/src/locales/index.ts +103 -0
  122. package/{lib/locales/ja-JP.js → src/locales/ja-JP.ts} +160 -118
  123. package/src/locales/types.ts +156 -0
  124. package/{lib/locales/zh-CN.js → src/locales/zh-CN.ts} +160 -177
  125. package/src/parser/ConstraintParser.ts +101 -0
  126. package/src/parser/DslParser.ts +470 -0
  127. package/src/parser/SchemaCompiler.ts +66 -0
  128. package/src/parser/TypeRegistry.ts +250 -0
  129. package/src/parser/index.ts +6 -0
  130. package/src/plugins/custom-format.ts +124 -0
  131. package/src/plugins/custom-type-example.ts +106 -0
  132. package/src/plugins/custom-validator.ts +138 -0
  133. package/src/types/conditional.ts +28 -0
  134. package/src/types/config.ts +59 -0
  135. package/src/types/dsl.ts +131 -0
  136. package/src/types/error.ts +60 -0
  137. package/src/types/index.ts +17 -0
  138. package/src/types/infer.ts +128 -0
  139. package/src/types/plugin.ts +58 -0
  140. package/src/types/safe-regex.d.ts +9 -0
  141. package/src/types/schema.ts +66 -0
  142. package/src/types/validate.ts +71 -0
  143. package/src/utils/SchemaHelper.ts +196 -0
  144. package/src/utils/SchemaUtils.ts +365 -0
  145. package/src/utils/TypeConverter.ts +215 -0
  146. package/src/utils/index.ts +10 -0
  147. package/src/validators/CustomKeywords.ts +477 -0
  148. package/.eslintignore +0 -11
  149. package/.eslintrc.json +0 -27
  150. package/CONTRIBUTING.md +0 -368
  151. package/STATUS.md +0 -491
  152. package/changelogs/v1.0.0.md +0 -328
  153. package/changelogs/v1.0.9.md +0 -367
  154. package/changelogs/v1.1.0.md +0 -389
  155. package/changelogs/v1.1.1.md +0 -308
  156. package/changelogs/v1.1.2.md +0 -183
  157. package/changelogs/v1.1.3.md +0 -161
  158. package/changelogs/v1.1.4.md +0 -432
  159. package/changelogs/v1.1.5.md +0 -493
  160. package/changelogs/v1.1.6.md +0 -211
  161. package/changelogs/v1.1.8.md +0 -376
  162. package/changelogs/v1.2.3.md +0 -124
  163. package/docs/INDEX.md +0 -252
  164. package/docs/issues-resolved-summary.md +0 -196
  165. package/docs/performance-benchmark-report.md +0 -179
  166. package/docs/performance-quick-reference.md +0 -123
  167. package/docs/user-questions-answered.md +0 -353
  168. package/docs/validation-rules-v1.0.2.md +0 -1608
  169. package/examples/README.md +0 -81
  170. package/examples/array-dsl-example.js +0 -227
  171. package/examples/conditional-example.js +0 -288
  172. package/examples/conditional-non-object.js +0 -129
  173. package/examples/conditional-validate-example.js +0 -321
  174. package/examples/custom-extension.js +0 -85
  175. package/examples/dsl-match-example.js +0 -74
  176. package/examples/dsl-style.js +0 -118
  177. package/examples/dynamic-locale-configuration.js +0 -348
  178. package/examples/dynamic-locale-example.js +0 -287
  179. package/examples/enum.examples.js +0 -324
  180. package/examples/export-demo.js +0 -130
  181. package/examples/express-integration.js +0 -376
  182. package/examples/i18n-error-handling-complete.js +0 -381
  183. package/examples/i18n-error-handling-quickstart.md +0 -0
  184. package/examples/i18n-error.examples.js +0 -181
  185. package/examples/i18n-full-demo.js +0 -301
  186. package/examples/i18n-memory-safety.examples.js +0 -268
  187. package/examples/markdown-export.js +0 -71
  188. package/examples/middleware-usage.js +0 -93
  189. package/examples/new-features-comparison.js +0 -315
  190. package/examples/password-reset/README.md +0 -153
  191. package/examples/password-reset/schema.js +0 -26
  192. package/examples/password-reset/test.js +0 -101
  193. package/examples/plugin-system.examples.js +0 -205
  194. package/examples/schema-utils-chaining.examples.js +0 -250
  195. package/examples/simple-example.js +0 -122
  196. package/examples/slug.examples.js +0 -179
  197. package/examples/string-extensions.js +0 -297
  198. package/examples/union-type-example.js +0 -127
  199. package/examples/union-types-example.js +0 -77
  200. package/examples/user-registration/README.md +0 -156
  201. package/examples/user-registration/routes.js +0 -92
  202. package/examples/user-registration/schema.js +0 -150
  203. package/examples/user-registration/server.js +0 -74
  204. package/index.d.ts +0 -3658
  205. package/index.js +0 -475
  206. package/index.mjs +0 -60
  207. package/lib/adapters/DslAdapter.js +0 -995
  208. package/lib/adapters/index.js +0 -20
  209. package/lib/config/constants.js +0 -286
  210. package/lib/config/patterns/common.js +0 -47
  211. package/lib/config/patterns/creditCard.js +0 -9
  212. package/lib/config/patterns/idCard.js +0 -9
  213. package/lib/config/patterns/index.js +0 -9
  214. package/lib/config/patterns/licensePlate.js +0 -4
  215. package/lib/config/patterns/passport.js +0 -4
  216. package/lib/config/patterns/phone.js +0 -9
  217. package/lib/config/patterns/postalCode.js +0 -5
  218. package/lib/core/CacheManager.js +0 -376
  219. package/lib/core/ConditionalBuilder.js +0 -503
  220. package/lib/core/DslBuilder.js +0 -1589
  221. package/lib/core/ErrorCodes.js +0 -233
  222. package/lib/core/ErrorFormatter.js +0 -445
  223. package/lib/core/JSONSchemaCore.js +0 -347
  224. package/lib/core/Locale.js +0 -130
  225. package/lib/core/MessageTemplate.js +0 -98
  226. package/lib/core/PluginManager.js +0 -448
  227. package/lib/core/StringExtensions.js +0 -240
  228. package/lib/core/Validator.js +0 -654
  229. package/lib/errors/I18nError.js +0 -328
  230. package/lib/errors/ValidationError.js +0 -191
  231. package/lib/exporters/MarkdownExporter.js +0 -420
  232. package/lib/exporters/MongoDBExporter.js +0 -162
  233. package/lib/exporters/MySQLExporter.js +0 -212
  234. package/lib/exporters/PostgreSQLExporter.js +0 -289
  235. package/lib/exporters/index.js +0 -24
  236. package/lib/locales/index.js +0 -8
  237. package/lib/utils/LRUCache.js +0 -174
  238. package/lib/utils/SchemaHelper.js +0 -240
  239. package/lib/utils/SchemaUtils.js +0 -445
  240. package/lib/utils/TypeConverter.js +0 -245
  241. package/lib/utils/index.js +0 -13
  242. package/lib/validators/CustomKeywords.js +0 -616
  243. package/lib/validators/index.js +0 -11
package/README.md CHANGED
@@ -1,2486 +1,628 @@
1
- <div align="center">
2
-
3
- # 🎯 schema-dsl
4
-
5
- **最简洁的数据验证库 - 代码量减少 65%**
6
-
7
- 一行 DSL 替代 10 行链式调用
8
-
9
- [![npm version](https://img.shields.io/npm/v/schema-dsl.svg?style=flat-square)](https://www.npmjs.com/package/schema-dsl)
10
- [![npm downloads](https://img.shields.io/npm/dm/schema-dsl.svg?style=flat-square)](https://www.npmjs.com/package/schema-dsl)
11
- [![Build Status](https://github.com/vextjs/schema-dsl/workflows/CI/badge.svg)](https://github.com/vextjs/schema-dsl/actions)
12
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
13
-
14
- [快速开始](#-快速开始) · [在线体验](https://runkit.com/npm/schema-dsl) · [完整文档](./docs/INDEX.md) · [示例代码](./examples) · [性能测试](./docs/performance-benchmark-report.md)
15
-
16
- </div>
17
-
18
- ---
19
-
20
- ## ⚡ TL;DR(30秒快速理解)
21
-
22
- **schema-dsl 是什么?**
23
- 最简洁的数据验证库,一行DSL代替10行链式调用,性能超越Zod/Joi/Yup。
24
-
25
- **核心优势:**
26
- - 🎯 **极简语法**: `'string:3-32!'` 代替 8行 Joi 代码(减少 65% 代码量)
27
- - 🚀 **性能第一**: 2,879,606 ops/s,比 Zod 快 1.58倍,比 Joi 快 9.61倍
28
- - 🌍 **完整多语言**: 内置5种语言,支持运行时动态切换(v1.1.0+)
29
- - 🎨 **独家功能**: 从验证规则直接生成 MongoDB/MySQL/PostgreSQL Schema
30
-
31
- **3行代码上手:**
32
- ```javascript
33
- const { dsl, validate } = require('schema-dsl');
34
- const schema = dsl({ email: 'email!', age: 'number:18-' });
35
- const result = validate(schema, { email: 'test@example.com', age: 25 });
36
- console.log(result.valid); // true
37
- ```
38
-
39
- **5分钟教程**: [快速开始](#-快速开始) | **完整文档**: [docs/INDEX.md](./docs/INDEX.md) | **在线体验**: [RunKit](https://runkit.com/npm/schema-dsl)
40
-
41
- ---
42
-
43
- ## 🗺️ 文档导航
44
-
45
- **新手入门**:
46
- - [快速开始](#-快速开始) - 5 分钟上手
47
- - [功能总览](#-功能总览) - 了解所有功能
48
- - [DSL 语法速查](#-dsl-语法速查) - 语法参考
49
-
50
- **核心功能**:
51
- - [基础验证](#1-基础验证javascript) - 表单验证
52
- - [批量验证](#批量验证) - 性能优化
53
- - [嵌套对象](#嵌套对象验证) - 复杂结构
54
- - [条件验证](#条件验证---一行代码搞定) - 动态规则
55
- - [多语言](#4-多语言支持) - 国际化
56
-
57
- **框架集成**:
58
- - [Express](#2-express-集成---自动错误处理)
59
- - [Koa](#koa-集成)
60
- - [Fastify](#fastify-集成)
61
-
62
- **高级功能**:
63
- - [数据库导出](#3-数据库-schema-导出) - 独家功能
64
- - [插件系统](#6-插件系统) - 扩展功能
65
- - [TypeScript](#15-typescript-用法-) - 类型支持
66
-
67
- **完整文档**: [docs/INDEX.md](./docs/INDEX.md) - 40+ 篇详细文档
68
-
69
- ---
70
-
71
- ## 🆕 最新特性(v1.1.8)
72
-
73
- ### 🎯 智能参数识别 - 简化语法支持(v1.1.8)
74
-
75
- **API 更简洁,从4个参数减少到2个参数**
76
-
77
- ```javascript
78
- const { dsl, Locale } = require('schema-dsl');
79
-
80
- // 配置语言包
81
- Locale.addLocale('zh-CN', {
82
- 'account.notFound': {
83
- code: 40001,
84
- message: '账户不存在'
85
- }
86
- });
87
-
88
- Locale.addLocale('en-US', {
89
- 'account.notFound': {
90
- code: 40001,
91
- message: 'Account not found'
92
- }
93
- });
94
-
95
- // ✅ 新增:简化语法(推荐)
96
- dsl.error.throw('account.notFound', 'zh-CN');
97
- dsl.error.throw('account.notFound', 'zh-CN', 404);
98
-
99
- // ✅ 标准语法(完全兼容)
100
- dsl.error.throw('account.notFound', {}, 404, 'zh-CN');
101
- dsl.error.throw('account.notFound', { id: '123' }, 404, 'zh-CN');
102
-
103
- // 所有方法都支持
104
- dsl.error.create('account.notFound', 'zh-CN');
105
- dsl.error.assert(account, 'account.notFound', 'zh-CN');
106
- ```
107
-
108
- **核心优势**:
109
- - 🎯 **参数更少**: 无需参数对象时从4个参数减少到2个
110
- - 🎯 **智能识别**: 自动判断第2个参数是语言还是参数对象
111
- - 🎯 **完全兼容**: 现有代码无需修改,渐进式增强
112
- - 🎯 **降低错误**: 不再需要传递空对象 `{}`
113
-
114
- 📖 [完整文档](./docs/error-handling.md) · [实现原理](./docs/i18n-implementation-analysis.md) · [变更日志](./changelogs/v1.1.8.md)
115
-
116
- ---
117
-
118
- ### 🎯 错误配置对象格式支持(v1.1.5)
119
-
120
- **统一错误代码,多语言共享,前端友好**
121
-
122
- ```javascript
123
- // 语言包配置(支持对象格式)
124
- const locales = {
125
- 'zh-CN': {
126
- 'account.notFound': {
127
- code: 40001, // 统一的数字错误代码
128
- message: '账户不存在'
129
- },
130
- 'account.insufficientBalance': {
131
- code: 40002,
132
- message: '余额不足,当前{{#balance}},需要{{#required}}'
133
- }
134
- },
135
- 'en-US': {
136
- 'account.notFound': {
137
- code: 40001, // 相同的数字 code
138
- message: 'Account not found'
139
- },
140
- 'account.insufficientBalance': {
141
- code: 40002,
142
- message: 'Insufficient balance: {{#balance}}, required: {{#required}}'
143
- }
144
- }
145
- };
146
-
147
- // 使用
148
- try {
149
- dsl.error.throw('account.notFound');
150
- } catch (error) {
151
- console.log(error.code); // 40001 (统一数字代码)
152
- console.log(error.originalKey); // 'account.notFound' (原始key)
153
- console.log(error.message); // 中文: "账户不存在" / 英文: "Account not found"
154
-
155
- // 增强的 error.is() - 两种方式都支持
156
- if (error.is('account.notFound')) { } // ✅ 使用 originalKey
157
- if (error.is(40001)) { } // 使用数字 code
158
- }
159
-
160
- // 前端统一处理
161
- switch (error.code) {
162
- case 40001: showNotFoundPage(); break; // 不受语言影响
163
- case 40002: showTopUpDialog(); break;
164
- }
165
- ```
166
-
167
- **核心优势**:
168
- - 🎯 **统一错误代码**: 不同语言使用相同的数字 `code`,便于前端统一处理
169
- - 🔄 **完全向后兼容**: 字符串格式自动转换,现有代码无需修改
170
- - 📊 **更好的错误追踪**: `originalKey` 和 `code` 分离,便于日志分析
171
- - 🌍 **多语言友好**: 前端可以用统一的数字 code 处理,不受语言影响
172
-
173
- 📖 [完整文档](./docs/error-handling.md#v115-新功能对象格式错误配置) · [变更日志](./changelogs/v1.1.5.md)
174
-
175
- ---
176
-
177
- ### 🔗 跨类型联合验证(v1.1.0)
178
-
179
- **一行代码支持多种类型,告别繁琐的类型判断**
180
-
181
- ```javascript
182
- const schema = dsl({
183
- contact: 'types:email|phone!', // 邮箱或手机号
184
- price: 'types:number:0-|string:1-20', // 数字价格或"面议"
185
- status: 'types:active|inactive|null' // 枚举或空值
186
- });
187
-
188
- validate(schema, { contact: 'test@example.com' }); // ✅ 通过
189
- validate(schema, { contact: '13800138000' }); // ✅ 通过
190
- validate(schema, { contact: 12345 }); // ❌ 失败
191
- ```
192
-
193
- **实际场景**:
194
- - ✅ 用户注册:支持邮箱或手机号登录
195
- - ✅ 商品价格:数字或"面议"字符串
196
- - ✅ 可选字段:允许null值
197
-
198
- 📖 [完整文档](./docs/union-types.md)
199
-
200
- ---
201
-
202
- ### 🌍 运行时多语言支持
203
-
204
- **无需修改全局设置,每次调用指定语言**
205
-
206
- ```javascript
207
- // 根据请求头动态返回不同语言的错误
208
- app.post('/api/account', (req, res) => {
209
- const locale = req.headers['accept-language'] || 'en-US';
210
-
211
- try {
212
- dsl.error.assert(account, 'account.notFound', {}, 404, locale);
213
- // 中文请求返回: "账户不存在"
214
- // 英文请求返回: "Account not found"
215
- } catch (error) {
216
- res.status(error.statusCode).json(error.toJSON());
217
- }
218
- });
219
- ```
220
-
221
- **适用场景**:
222
- - 多语言 API(根据请求头动态返回)
223
- - ✅ 微服务架构(错误传递保持原语言)
224
- - 国际化应用(同一请求多种语言)
225
-
226
- 📖 [运行时多语言文档](./docs/runtime-locale-support.md)
227
-
228
- ---
229
-
230
- ### ⚡ 其他新特性
231
-
232
- - ✅ **错误配置对象格式**: 支持 `{ code, message }` 统一错误代码(v1.1.5)
233
- - **统一错误抛出**: `I18nError` 类,支持多语言错误消息(v1.1.1)
234
- - **插件系统增强**: 自定义类型注册更简单(v1.1.0)
235
- - **TypeScript 类型完善**: 0个类型错误(v1.1.4)
236
-
237
- [查看完整更新日志](./CHANGELOG.md)
238
-
239
- ---
240
-
241
- ## 📦 功能清单(AI友好格式)
242
-
243
- > 方便AI快速理解所有功能
244
-
245
- ### 核心功能
246
-
247
- ```json
248
- {
249
- "validation": {
250
- "basic": ["string", "number", "boolean", "date", "email", "url", "phone", "idCard"],
251
- "advanced": ["regex", "custom", "conditional", "nested", "array"],
252
- "unionTypes": "v1.1.0+ 跨类型联合验证 (types:string|number)"
253
- },
254
- "i18n": {
255
- "supported": ["zh-CN", "en-US", "ja-JP", "es-ES", "fr-FR"],
256
- "features": ["配置加载", "运行时切换", "自定义消息", "参数插值"],
257
- "runtime": "v1.1.0+ 运行时指定语言 (dsl.error.create(code, params, statusCode, locale))"
258
- },
259
- "database": {
260
- "export": ["MongoDB", "MySQL", "PostgreSQL"],
261
- "unique": "从验证规则直接生成数据库Schema"
262
- },
263
- "framework": {
264
- "integration": ["Express", "Koa", "Fastify"],
265
- "async": "validateAsync() 失败自动抛出 ValidationError"
266
- },
267
- "api": {
268
- "main": ["dsl()", "validate()", "validateAsync()"],
269
- "utils": ["SchemaUtils.pick()", "SchemaUtils.omit()", "SchemaUtils.partial()"],
270
- "conditional": ["dsl.if()", "dsl.match()"],
271
- "errors": ["ValidationError", "I18nError"]
272
- },
273
- "performance": {
274
- "opsPerSecond": 2879606,
275
- "vs": {
276
- "Zod": "1.58x faster",
277
- "Joi": "9.61x faster",
278
- "Yup": "27.07x faster"
279
- },
280
- "optimization": ["WeakMap缓存", "智能编译", "批量验证优化"]
281
- }
282
- }
283
- ```
284
-
285
- ### API速查
286
-
287
- | API | 用途 | 返回值 | 文档 |
288
- |-----|------|--------|------|
289
- | `dsl(schema)` | 创建Schema | Schema对象 | [DSL语法](./docs/dsl-syntax.md) |
290
- | `validate(schema, data)` | 同步验证 | `{valid, errors, data}` | [验证指南](./docs/validation-guide.md) |
291
- | `validateAsync(schema, data)` | 异步验证 | Promise(失败抛错) | [异步验证](./docs/validate-async.md) |
292
- | `dsl.if(condition)` | 条件验证 | ConditionalBuilder | [条件API](./docs/conditional-api.md) |
293
- | `SchemaUtils.pick()` | 选择字段 | 新Schema | [SchemaUtils](./docs/schema-utils.md) |
294
- | `I18nError.throw()` | 抛出多语言错误 | never | [I18nError示例](./examples/i18n-error.examples.js) |
295
-
296
- ---
297
-
298
- ## 为什么选择 schema-dsl?
299
-
300
- ### 🎯 极简 DSL 语法
301
-
302
- **3 行代码完成验证规则定义**
303
-
304
- <table>
305
- <tr>
306
- <td width="50%" valign="top">
307
-
308
- **❌ 传统写法** - 繁琐冗长
309
-
310
- ```javascript
311
- // Joi - 需要 8
312
- const schema = Joi.object({
313
- username: Joi.string()
314
- .min(3).max(32).required(),
315
- email: Joi.string()
316
- .email().required(),
317
- age: Joi.number()
318
- .min(18).max(120)
319
- });
320
- ```
321
-
322
- </td>
323
- <td width="50%" valign="top">
324
-
325
- **✅ schema-dsl** - 简洁优雅
326
-
327
- ```javascript
328
- // 只需 3 行!
329
- const schema = dsl({
330
- username: 'string:3-32!',
331
- email: 'email!',
332
- age: 'number:18-120'
333
- });
334
- ```
335
-
336
- </td>
337
- </tr>
338
- </table>
339
-
340
- ### 🚀 性能卓越
341
-
342
- **测试结果:schema-dsl 是目前性能最优的验证库(基于最新基准测试)**
343
-
344
- | 验证库 | 性能 (ops/s) | 相对速度 | 评价 |
345
- |--------|-------------|---------|------|
346
- | **schema-dsl** | **2,879,606** | **基准 (1.00x)** | **🥇 第一名** |
347
- | Zod | 1,818,592 | 0.63x | 🥈 慢 58% |
348
- | Joi | 299,761 | 0.10x | 🥉 慢 861% |
349
- | Yup | 106,378 | 0.04x | 慢 2607% |
350
-
351
- **性能优势**:
352
- - ✅ 比 Zod 快 **1.58倍**
353
- - Joi 快 **9.61倍**
354
- - 比 Yup 快 **27.07倍**
355
-
356
- > 📊 **测试环境**: Node.js v20.x, Windows
357
- > 📊 **测试场景**: 用户注册表单验证(username, email, age, tags)
358
- > 📊 **测试工具**: [Benchmark.js](https://benchmarkjs.com/)
359
- > 📊 **运行测试**: `node test/benchmarks/library-comparison.js`
360
-
361
- ### 🌍 完整多语言支持
362
-
363
- **一行配置,自动加载所有语言包**
364
-
365
- ```javascript
366
- const { dsl, validate } = require('schema-dsl');
367
- const path = require('path');
368
-
369
- // ========== 应用启动时配置(只执行一次)==========
370
- dsl.config({
371
- i18n: path.join(__dirname, 'locales') // 自动加载目录下所有语言文件
372
- });
373
-
374
- // ========== 运行时直接切换语言(无需重新加载)==========
375
- const schema = dsl({ username: 'string:3-32!' });
376
-
377
- // 中文错误消息
378
- validate(schema, { username: 'ab' }, { locale: 'zh-CN' });
379
- // => "username长度不能少于3个字符"
380
-
381
- // 英文错误消息
382
- validate(schema, { username: 'ab' }, { locale: 'en-US' });
383
- // => "username length must be at least 3"
384
-
385
- // 日语错误消息
386
- validate(schema, { username: 'ab' }, { locale: 'ja-JP' });
387
- // => "usernameは3文字以上である必要があります"
388
- ```
389
-
390
- **🆕 运行时多语言支持(v1.1.0+)**
391
-
392
- 无需修改全局设置,可在每次调用时指定语言:
393
-
394
- ```javascript
395
- const { dsl, I18nError } = require('schema-dsl');
396
-
397
- // 方式1: 业务错误 - 运行时指定语言
398
- const error1 = dsl.error.create('account.notFound', {}, 404, 'zh-CN');
399
- console.log(error1.message); // "账户不存在"
400
-
401
- const error2 = dsl.error.create('account.notFound', {}, 404, 'en-US');
402
- console.log(error2.message); // "Account not found"
403
-
404
- // 方式2: 断言风格 - 根据请求头动态指定
405
- app.post('/api/withdraw', (req, res) => {
406
- const locale = req.headers['accept-language'] || 'en-US';
407
- const account = getAccount(req.user.id);
408
-
409
- // 根据请求头返回对应语言的错误
410
- I18nError.assert(account, 'account.notFound', {}, 404, locale);
411
- I18nError.assert(
412
- account.balance >= req.body.amount,
413
- 'account.insufficientBalance',
414
- { balance: account.balance, required: req.body.amount },
415
- 400,
416
- locale
417
- );
418
-
419
- // 验证通过,继续处理...
420
- });
421
- ```
422
-
423
- **适用场景**:
424
- - 多语言 API(根据请求头返回不同语言)
425
- - 微服务架构(错误在服务间传递时保持语言)
426
- - ✅ 同一请求中需要多种语言的错误消息
427
-
428
- **内置语言**: 中文、英文、日语、法语、西班牙语
429
-
430
- 📖 [完整多语言文档](./docs/i18n.md)
431
- 📖 [运行时多语言支持](./docs/runtime-locale-support.md)
432
-
433
- ### 🎨 数据库 Schema 导出
434
-
435
- **一份定义,多处使用**
436
-
437
- ```javascript
438
- const { dsl, exporters } = require('schema-dsl');
439
-
440
- const schema = dsl({
441
- username: 'string:3-32!',
442
- email: 'email!',
443
- age: 'number:18-120'
444
- });
445
-
446
- // 导出 MongoDB Schema
447
- const mongoExporter = new exporters.MongoDBExporter();
448
- const mongoSchema = mongoExporter.export(schema);
449
-
450
- // 导出 MySQL 建表语句
451
- const mysqlExporter = new exporters.MySQLExporter();
452
- const mysqlDDL = mysqlExporter.export('users', schema);
453
-
454
- // 导出 PostgreSQL 建表语句
455
- const pgExporter = new exporters.PostgreSQLExporter();
456
- const pgDDL = pgExporter.export('users', schema);
457
- ```
458
-
459
- **✅ 独家功能**:从验证规则直接生成数据库结构!
460
-
461
- ### ⚡ 5 分钟上手
462
-
463
- **学习成本极低,立即可用**
464
-
465
- ```javascript
466
- const { dsl, validate } = require('schema-dsl');
467
-
468
- // 1️⃣ 定义规则(1 分钟)
469
- const schema = dsl({
470
- username: 'string:3-32!',
471
- email: 'email!',
472
- password: 'string:8-!'
473
- });
474
-
475
- // 2️⃣ 验证数据(30 秒)
476
- const result = validate(schema, {
477
- username: 'john',
478
- email: 'john@example.com',
479
- password: '12345678'
480
- });
481
-
482
- // 3️⃣ 处理结果(30 秒)
483
- if (result.valid) {
484
- console.log('验证通过!');
485
- } else {
486
- console.log('错误:', result.errors);
487
- }
488
- ```
489
-
490
- **对比其他库**:
491
- - Joi/Yup:需要 30 分钟学习链式 API
492
- - Zod:需要 15 分钟学习 TypeScript 类型
493
- - Ajv:需要 20 分钟学习 JSON Schema 规范
494
-
495
- ### 💪 功能完整
496
-
497
- | 功能 | schema-dsl | 说明 |
498
- |------|-----------|------|
499
- | **基本验证** | | string、number、boolean、date、email、url... |
500
- | **高级验证** | ✅ | 正则、自定义、条件、嵌套、数组... |
501
- | **🆕 跨类型联合** | | `types:string|number` 一个字段支持多种类型 (v1.1.1) |
502
- | **错误格式化** | | 自动多语言翻译 |
503
- | **🆕 多语言错误** | | `I18nError` 统一的多语言错误抛出 (v1.1.1) |
504
- | **数据库导出** | | MongoDB、MySQL、PostgreSQL |
505
- | **TypeScript** | | 完整类型定义 |
506
- | **性能优化** | | WeakMap 缓存、智能编译 |
507
- | **插件系统** | | 支持自定义类型注册 (v1.1.1) |
508
- | **文档生成** | | Markdown、HTML |
509
-
510
- ### 🆕 v1.1.0 新特性:跨类型联合验证
511
-
512
- **一行代码支持多种类型**
513
-
514
- ```javascript
515
- const { dsl, validate } = require('schema-dsl');
516
-
517
- // 字段可以是字符串或数字
518
- const schema = dsl({
519
- value: 'types:string|number'
520
- });
521
-
522
- validate(schema, { value: 'hello' }); // ✅ 通过
523
- validate(schema, { value: 123 }); // ✅ 通过
524
- validate(schema, { value: true }); // ❌ 失败
525
-
526
- // 带约束的联合类型
527
- const advancedSchema = dsl({
528
- contact: 'types:email|phone!', // 邮箱或手机号
529
- price: 'types:number:0-|string:1-20' // 数字价格或"面议"
530
- });
531
- ```
532
-
533
- **实际场景示例**:
534
- ```javascript
535
- // 用户注册:支持邮箱或手机号
536
- const registerSchema = dsl({
537
- username: 'string:3-20!',
538
- contact: 'types:email|phone!', // 灵活的联系方式
539
- age: 'types:integer:1-150|null' // 年龄可选
540
- });
541
- ```
542
-
543
- 📖 [完整文档](./docs/union-types.md) | [插件开发指南](./docs/plugin-type-registration.md)
544
-
545
- ---
546
-
547
- ## � 功能总览
548
-
549
- > AI 和开发者快速了解所有功能
550
-
551
- ### 核心功能速查表
552
-
553
- | 分类 | 功能 | 代码示例 | 文档链接 |
554
- |------|------|---------|----------|
555
- | **基础验证** | DSL 语法 | `'string:3-32!'` | [DSL 语法](./docs/dsl-syntax.md) |
556
- | | 链式调用 | `'string!'.label('用户名')` | [String 扩展](./docs/string-extensions.md) |
557
- | | TypeScript | `dsl('string!')` | [TS 指南](./docs/typescript-guide.md) |
558
- | **高级验证** | 条件验证 | `dsl.if()/dsl.match()` | [条件 API](./docs/conditional-api.md) |
559
- | | 嵌套对象 | `{ user: { name: 'string!' } }` | [验证指南](./docs/validation-guide.md) |
560
- | | 数组验证 | `'array:1-10<string>'` | [类型参考](./docs/type-reference.md) |
561
- | | 联合类型 | `'types:string\|number'` | [联合类型](./docs/union-types.md) |
562
- | | 正则验证 | `.pattern(/^[A-Z]+$/)` | [自定义扩展](./docs/custom-extensions-guide.md) |
563
- | | 自定义验证 | `.custom((v) => ...)` | [自定义扩展](./docs/custom-extensions-guide.md) |
564
- | **Schema 工具** | 复用字段 | `SchemaUtils.pick()` | [SchemaUtils](./docs/schema-utils.md) |
565
- | | 批量验证 | `validateBatch(schema, array)` | [批量验证](#批量验证) |
566
- | | 字段库 | `createLibrary()` | [SchemaUtils](./docs/schema-utils.md) |
567
- | **框架集成** | Express | `validateAsync + try/catch` | [Express 示例](./examples/express-integration.js) |
568
- | | Koa | `validateAsync + ctx.throw` | [中间件示例](./examples/middleware-usage.js) |
569
- | | Fastify | `preValidation hook` | [中间件示例](./examples/middleware-usage.js) |
570
- | **多语言** | 配置语言 | `dsl.config({ i18n })` | [i18n 指南](./docs/i18n-user-guide.md) |
571
- | | 错误抛出 | `I18nError.throw()` | [I18nError 示例](./examples/i18n-error.examples.js) |
572
- | **数据库** | MongoDB | `MongoDBExporter.export()` | [MongoDB 导出](./docs/mongodb-exporter.md) |
573
- | | MySQL | `MySQLExporter.export()` | [MySQL 导出](./docs/mysql-exporter.md) |
574
- | | PostgreSQL | `PostgreSQLExporter.export()` | [PostgreSQL 导出](./docs/postgresql-exporter.md) |
575
- | **插件** | 自定义格式 | `pluginManager.register()` | [插件系统](./docs/plugin-system.md) |
576
- | **性能** | 缓存配置 | `config({ cache })` | [缓存管理](./docs/cache-manager.md) |
577
-
578
- ### 常见使用场景
579
-
580
- | 场景 | 代码示例 | 完整示例 |
581
- |------|---------|----------|
582
- | **API 参数验证** | [Express 集成](#2-express-集成---自动错误处理) | [完整代码](./examples/express-integration.js) |
583
- | **用户注册表单** | [基础验证](#1-基础验证javascript) | [完整代码](./examples/user-registration/) |
584
- | **批量数据处理** | [批量验证](#批量验证) | [完整代码](./examples/simple-example.js) |
585
- | **多语言应用** | [多语言支持](#4-多语言支持) | [完整代码](./examples/i18n-full-demo.js) |
586
- | **数据库建表** | [数据库导出](#3-数据库-schema-导出) | [完整代码](./examples/export-demo.js) |
587
- | **复杂嵌套结构** | [嵌套对象](#嵌套对象验证) | [验证指南](./docs/validation-guide.md) |
588
- | **正则格式验证** | [正则验证](#正则验证) | [自定义扩展](./docs/custom-extensions-guide.md) |
589
- | **业务逻辑验证** | [自定义验证器](#自定义验证器) | [自定义扩展](./docs/custom-extensions-guide.md) |
590
-
591
- ---
592
-
593
- ## �📦 安装
594
-
595
- ```bash
596
- npm install schema-dsl
597
- ```
598
-
599
- ---
600
-
601
- ## 🚀 快速开始
602
-
603
- ### 1. 基础验证(JavaScript)
604
-
605
- ```javascript
606
- const { dsl, validate } = require('schema-dsl');
607
-
608
- const userSchema = dsl({
609
- username: 'string:3-32!',
610
- email: 'email!',
611
- age: 'number:18-120',
612
- tags: 'array<string>'
613
- });
614
-
615
- // ✅ 验证成功
616
- const result1 = validate(userSchema, {
617
- username: 'john_doe',
618
- email: 'john@example.com',
619
- age: 25,
620
- tags: ['admin', 'verified']
621
- });
622
-
623
- console.log(result1.valid); // true
624
- console.log(result1.data); // 验证后的数据
625
-
626
- // ❌ 验证失败 - 看看如何处理错误
627
- const result2 = validate(userSchema, {
628
- username: 'ab', // 太短(最少3个字符)
629
- email: 'invalid-email', // 格式错误
630
- age: 15 // 小于最小值18
631
- });
632
-
633
- console.log(result2.valid); // false
634
- console.log(result2.errors); // 错误列表
635
- /*
636
- [
637
- { path: 'username', message: 'username must be at least 3 characters' },
638
- { path: 'email', message: 'must be a valid email' },
639
- { path: 'age', message: 'age must be at least 18' }
640
- ]
641
- */
642
- ```
643
-
644
- ### 1.5 TypeScript 用法 ⭐
645
-
646
- **重要**: TypeScript 中**必须**使用 `dsl()` 包裹字符串以获得类型提示(v1.0.6+ 移除了全局 String 类型扩展以避免类型污染):
647
-
648
- ```typescript
649
- import { dsl, validateAsync, ValidationError } from 'schema-dsl';
650
-
651
- // ✅ 正确:使用 dsl() 包裹字符串获得完整类型提示
652
- const userSchema = dsl({
653
- username: dsl('string:3-32!')
654
- .pattern(/^[a-zA-Z0-9_]+$/, '只能包含字母、数字和下划线')
655
- .label('用户名'),
656
-
657
- email: dsl('email!')
658
- .label('邮箱地址')
659
- .messages({ required: '邮箱必填' }),
660
-
661
- age: dsl('number:18-100')
662
- .label('年龄')
663
- });
664
-
665
- // 异步验证(推荐)
666
- try {
667
- const validData = await validateAsync(userSchema, {
668
- username: 'testuser',
669
- email: 'test@example.com',
670
- age: 25
671
- });
672
- console.log('验证通过:', validData);
673
- } catch (error) {
674
- if (error instanceof ValidationError) {
675
- error.errors.forEach(err => {
676
- console.log(`${err.path}: ${err.message}`);
677
- });
678
- }
679
- }
680
- ```
681
-
682
- **为什么必须用 `dsl()` 包裹?**
683
- - ✅ 完整的类型推导和 IDE 自动提示
684
- - ✅ 避免污染原生 String 类型(v1.0.6+ 重要改进)
685
- - ✅ 保证 `trim()`、`toLowerCase()` 等原生方法类型正确
686
- - ✅ 更好的开发体验和类型安全
687
-
688
- **JavaScript 用户不受影响**:在 JavaScript 中仍然可以直接使用 `'email!'.label('邮箱')` 语法。
689
-
690
- **详细说明**: 请查看 [TypeScript 使用指南](./docs/typescript-guide.md)
691
-
692
- ### 2. Express 集成 - 自动错误处理
693
-
694
- ```javascript
695
- const { dsl, validateAsync, ValidationError } = require('schema-dsl');
696
-
697
- // 定义验证 Schema
698
- const createUserSchema = dsl({
699
- username: 'string:3-32!',
700
- email: 'email!',
701
- password: 'string:8-32!'
702
- });
703
-
704
- // 在路由中使用
705
- app.post('/api/users', async (req, res, next) => {
706
- try {
707
- // validateAsync 验证失败时会抛出 ValidationError
708
- const validData = await validateAsync(createUserSchema, req.body);
709
-
710
- const user = await db.users.create(validData);
711
- res.json({ success: true, data: user });
712
- } catch (error) {
713
- // ValidationError 会被全局错误处理器捕获
714
- next(error);
715
- }
716
- });
717
-
718
- // 全局错误处理 - 区分验证错误和其他错误
719
- app.use((error, req, res, next) => {
720
- if (error instanceof ValidationError) {
721
- // 验证错误返回 400
722
- return res.status(400).json({
723
- success: false,
724
- message: 'Validation failed',
725
- errors: error.errors // 详细的字段错误列表
726
- });
727
- }
728
-
729
- // 其他错误继续传递
730
- next(error);
731
- });
732
- ```
733
-
734
- ### Schema 复用 - 按场景使用
735
-
736
- ```javascript
737
- const { dsl, SchemaUtils } = require('schema-dsl');
738
-
739
- // 完整的用户 Schema
740
- const fullUserSchema = dsl({
741
- id: 'string!',
742
- username: 'string:3-32!',
743
- email: 'email!',
744
- password: 'string:8-32!',
745
- age: 'number:18-120',
746
- role: 'admin|user|guest',
747
- createdAt: 'datetime!',
748
- updatedAt: 'datetime!'
749
- });
750
-
751
- // 场景1: 创建用户 - 排除自动生成的字段
752
- // 使用 omit() 排除不需要的字段
753
- const createSchema = SchemaUtils.omit(fullUserSchema, ['id', 'createdAt', 'updatedAt']);
754
-
755
- // 场景2: 查询用户 - 隐藏敏感字段
756
- // 使用 omit() 排除敏感信息
757
- const publicSchema = SchemaUtils.omit(fullUserSchema, ['password']);
758
-
759
- // 场景3: 更新用户 - 只允许更新部分字段
760
- // 使用 pick() 选择字段 + partial() 变为可选
761
- const updateSchema = SchemaUtils
762
- .pick(fullUserSchema, ['username', 'email', 'age'])
763
- .partial(); // 所有字段变为可选
764
-
765
- // 场景4: 注册接口 - 扩展额外字段
766
- // 使用 pick() + extend() 添加新字段
767
- const registerSchema = SchemaUtils
768
- .pick(fullUserSchema, ['username', 'email', 'password'])
769
- .extend({
770
- captcha: 'string:4-6!',
771
- agree: 'boolean!'
772
- });
773
-
774
- // 💡 快速记忆:
775
- // omit - 排除字段(隐藏敏感信息)
776
- // pick - 挑选字段(限制可修改字段)
777
- // extend - 扩展字段(添加新字段)
778
- // partial - 变为可选(用于更新接口)
779
- ```
780
-
781
- ### 条件验证 - 一行代码搞定
782
-
783
- **问题场景**:不同情况需要不同的验证规则
784
-
785
- ```javascript
786
- const { dsl } = require('schema-dsl');
787
-
788
- // 场景1:年龄限制 - 未成年不能注册
789
- // ❌ 传统做法:先验证,再判断,写两次
790
- const result = validate(schema, userData);
791
- if (!result.valid) return;
792
- if (userData.age < 18) {
793
- throw new Error('未成年用户不能注册');
794
- }
795
-
796
- // ✅ 新做法:一行代码搞定
797
- dsl.if(d => d.age < 18)
798
- .message('未成年用户不能注册')
799
- .assert(userData); // 失败自动抛错
800
-
801
- // 场景2:权限检查 - 快速判断
802
- // ❌ 传统做法:写 if 判断
803
- if (user.role !== 'admin' && user.role !== 'moderator') {
804
- return res.status(403).json({ error: '权限不足' });
805
- }
806
-
807
- // ✅ 新做法:一行搞定
808
- if (!dsl.if(d => d.role === 'admin' || d.role === 'moderator')
809
- .message('权限不足')
810
- .check(user)) {
811
- return res.status(403).json({ error: '权限不足' });
812
- }
813
-
814
- // 场景3:批量过滤 - 筛选符合条件的数据
815
- // ❌ 传统做法:写 filter 函数
816
- const adults = users.filter(u => u.age >= 18);
817
-
818
- // ✅ 新做法:语义更清晰
819
- const adults = users.filter(u =>
820
- !dsl.if(d => d.age < 18).message('未成年').check(u)
821
- );
822
- ```
823
-
824
- #### 四种方法,满足不同场景
825
-
826
- | 方法 | 什么时候用 | 返回什么 | 示例 |
827
- |------|-----------|---------|------|
828
- | **`.validate()`** | 需要知道错误详情 | `{ valid, errors, data }` | 表单验证 |
829
- | **`.validateAsync()`** | async/await 场景 | Promise(失败抛错) | Express 中间件 |
830
- | **`.assert()`** | 快速失败,不想写 if | 失败直接抛错 | 函数入口检查 |
831
- | **`.check()`** | 只需要判断真假 | `true/false` | 数据过滤 |
832
-
833
- #### 实际例子
834
-
835
- **表单验证 - 需要显示错误**
836
-
837
- ```javascript
838
- // 使用 .validate() 获取错误详情
839
- const result = dsl.if(d => d.age < 18)
840
- .message('未成年用户不能注册')
841
- .validate(formData);
842
-
843
- if (!result.valid) {
844
- showError(result.errors[0].message); // 显示给用户
845
- }
846
- ```
847
-
848
- **Express 中间件 - 异步验证**
849
-
850
- ```javascript
851
- // 使用 .validateAsync() 失败自动抛错
852
- app.post('/register', async (req, res, next) => {
853
- try {
854
- await dsl.if(d => d.age < 18)
855
- .message('未成年用户不能注册')
856
- .validateAsync(req.body);
857
-
858
- // 验证通过,继续处理
859
- const user = await createUser(req.body);
860
- res.json(user);
861
- } catch (error) {
862
- next(error); // 自动传递给错误处理中间件
863
- }
864
- });
865
- ```
866
-
867
- **函数参数检查 - 快速断言**
868
-
869
- ```javascript
870
- // 使用 .assert() 不满足直接抛错
871
- function registerUser(userData) {
872
- // 入口检查,不满足直接抛错,代码更清晰
873
- dsl.if(d => d.age < 18).message('未成年不能注册').assert(userData);
874
- dsl.if(d => !d.email).message('邮箱必填').assert(userData);
875
- dsl.if(d => !d.phone).message('手机号必填').assert(userData);
876
-
877
- // 检查通过,继续业务逻辑
878
- return createUser(userData);
879
- }
880
- ```
881
-
882
- **批量数据处理 - 快速过滤**
883
-
884
- ```javascript
885
- // 使用 .check() 只返回 true/false
886
- const canRegister = dsl.if(d => d.age < 18)
887
- .or(d => d.status === 'blocked')
888
- .message('不允许注册');
889
-
890
- // 过滤出可以注册的用户
891
- const validUsers = users.filter(u => !canRegister.check(u));
892
-
893
- // 统计未成年用户数量
894
- const minorCount = users.filter(u =>
895
- dsl.if(d => d.age < 18).message('未成年').check(u)
896
- ).length;
897
- ```
898
-
899
- **复用验证器**
900
-
901
- ```javascript
902
- // 创建一次,到处使用
903
- const ageValidator = dsl.if(d => d.age < 18)
904
- .message('未成年用户不能注册');
905
-
906
- // 不同场景使用不同方法
907
- const r1 = ageValidator.validate({ age: 16 }); // 同步,返回详情
908
- const r2 = await ageValidator.validateAsync(data); // 异步,失败抛错
909
- const r3 = ageValidator.check({ age: 20 }); // 快速判断
910
- ```
911
-
912
- #### 💡 选择建议
913
-
914
- - 🎯 **表单验证**:用 `.validate()` - 需要显示错误给用户
915
- - 🚀 **API 接口**:用 `.validateAsync()` - 配合 try/catch
916
- - ⚡ **函数入口**:用 `.assert()` - 快速失败,代码简洁
917
- - 🔍 **数据过滤**:用 `.check()` - 只需要判断真假
918
-
919
- **完整文档**: [ConditionalBuilder API](./docs/conditional-api.md)
920
-
921
- ---
922
-
923
- ## � 进阶功能
924
-
925
- ### 批量验证
926
-
927
- **场景**: 验证 1000 条用户数据,性能提升 50 倍
928
-
929
- ```javascript
930
- const { dsl, SchemaUtils, Validator } = require('schema-dsl');
931
-
932
- const userSchema = dsl({
933
- username: 'string:3-32!',
934
- email: 'email!',
935
- age: 'number:18-120'
936
- });
937
-
938
- // 批量数据
939
- const users = [
940
- { username: 'user1', email: 'user1@example.com', age: 25 },
941
- { username: 'u2', email: 'invalid', age: 15 }, // 两个错误
942
- { username: 'user3', email: 'user3@example.com', age: 30 }
943
- ];
944
-
945
- // 批量验证
946
- const validator = new Validator();
947
- const result = SchemaUtils.validateBatch(userSchema, users, validator);
948
-
949
- console.log(result.summary);
950
- /*
951
- {
952
- total: 3,
953
- valid: 2,
954
- invalid: 1,
955
- duration: 5 // 毫秒
956
- }
957
- */
958
-
959
- console.log(result.errors);
960
- /*
961
- [
962
- { index: 1, errors: [
963
- { path: 'username', message: '...' },
964
- { path: 'age', message: '...' }
965
- ]}
966
- ]
967
- */
968
-
969
- // 只获取有效数据
970
- const validUsers = result.results
971
- .filter(r => r.valid)
972
- .map(r => r.data);
973
- ```
974
-
975
- 📖 **详细文档**: [SchemaUtils.validateBatch](./docs/schema-utils.md#validatebatch---批量验证)
976
-
977
- ---
978
-
979
- ### 嵌套对象验证
980
-
981
- **场景**: 验证复杂的用户资料
982
-
983
- ```javascript
984
- const { dsl, validate } = require('schema-dsl');
985
-
986
- const profileSchema = dsl({
987
- user: {
988
- basic: {
989
- name: 'string:2-50!',
990
- email: 'email!',
991
- phone: 'string:11!'
992
- },
993
- address: {
994
- country: 'string!',
995
- city: 'string!',
996
- street: 'string',
997
- zipCode: 'string:6'
998
- },
999
- preferences: {
1000
- language: 'zh-CN|en-US|ja-JP',
1001
- timezone: 'string',
1002
- notifications: {
1003
- email: 'boolean',
1004
- sms: 'boolean',
1005
- push: 'boolean'
1006
- }
1007
- }
1008
- },
1009
- metadata: {
1010
- source: 'web|mobile|api',
1011
- createdAt: 'datetime!',
1012
- tags: 'array:0-10<string>'
1013
- }
1014
- });
1015
-
1016
- const result = validate(profileSchema, {
1017
- user: {
1018
- basic: {
1019
- name: 'John Doe',
1020
- email: 'john@example.com',
1021
- phone: '13800138000'
1022
- },
1023
- address: {
1024
- country: 'China',
1025
- city: 'Beijing',
1026
- zipCode: '100000'
1027
- },
1028
- preferences: {
1029
- language: 'zh-CN',
1030
- timezone: 'Asia/Shanghai',
1031
- notifications: {
1032
- email: true,
1033
- sms: false,
1034
- push: true
1035
- }
1036
- }
1037
- },
1038
- metadata: {
1039
- source: 'web',
1040
- createdAt: new Date().toISOString(),
1041
- tags: ['vip', 'active']
1042
- }
1043
- });
1044
-
1045
- console.log(result.valid); // true
1046
- ```
1047
-
1048
- 📖 **详细文档**: [嵌套对象验证](./docs/validation-guide.md#嵌套对象验证)
1049
-
1050
- ---
1051
-
1052
- ### 数组高级验证
1053
-
1054
- **场景**: 验证订单商品列表
1055
-
1056
- ```javascript
1057
- const { dsl, validate } = require('schema-dsl');
1058
-
1059
- // 方式 1: 简单数组
1060
- const schema1 = dsl({
1061
- tags: 'array:1-10<string>', // 1-10 个字符串
1062
- scores: 'array<number:0-100>' // 数字数组,每个 0-100
1063
- });
1064
-
1065
- // 方式 2: 对象数组
1066
- const orderSchema = dsl({
1067
- orderId: 'string!',
1068
- items: 'array:1-100!', // 必填,1-100 个商品
1069
- // 注意:数组元素的验证需要单独定义
1070
- _itemSchema: { // 约定:用 _ 前缀标记辅助 schema
1071
- productId: 'string!',
1072
- name: 'string:1-100!',
1073
- quantity: 'integer:1-999!',
1074
- price: 'number:>0!'
1075
- }
1076
- });
1077
-
1078
- // 验证订单
1079
- const order = {
1080
- orderId: 'ORD-12345',
1081
- items: [
1082
- { productId: 'P001', name: 'iPhone', quantity: 2, price: 5999.00 },
1083
- { productId: 'P002', name: 'AirPods', quantity: 1, price: 1299.00 }
1084
- ]
1085
- };
1086
-
1087
- // 先验证订单结构
1088
- const result1 = validate(orderSchema, order);
1089
- if (!result1.valid) {
1090
- console.log('订单结构错误:', result1.errors);
1091
- }
1092
-
1093
- // 再验证每个商品
1094
- const itemSchema = dsl(orderSchema._itemSchema);
1095
- for (const [index, item] of order.items.entries()) {
1096
- const result = validate(itemSchema, item);
1097
- if (!result.valid) {
1098
- console.log(`商品 ${index} 错误:`, result.errors);
1099
- }
1100
- }
1101
- ```
1102
-
1103
- 📖 **详细文档**: [数组验证](./docs/validation-guide.md#数组验证)
1104
-
1105
- ---
1106
-
1107
- ### 正则验证
1108
-
1109
- **场景**: 自定义格式验证
1110
-
1111
- ```javascript
1112
- const { dsl, validate } = require('schema-dsl');
1113
-
1114
- const schema = dsl({
1115
- // 车牌号
1116
- licensePlate: 'string!'
1117
- .pattern(/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/)
1118
- .label('车牌号')
1119
- .messages({
1120
- pattern: '请输入有效的中国车牌号'
1121
- }),
1122
-
1123
- // 身份证号(简化版)
1124
- idCard: 'string:18!'
1125
- .pattern(/^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$/)
1126
- .label('身份证号')
1127
- .messages({
1128
- pattern: '请输入有效的 18 位身份证号'
1129
- }),
1130
-
1131
- // 自定义代码格式
1132
- inviteCode: 'string:8!'
1133
- .pattern(/^[A-Z]{3}\\d{5}$/)
1134
- .label('邀请码')
1135
- .messages({
1136
- pattern: '邀请码格式:3个大写字母 + 5个数字(如 ABC12345)'
1137
- })
1138
- });
1139
-
1140
- const result = validate(schema, {
1141
- licensePlate: '京A12345',
1142
- idCard: '110101199003071234',
1143
- inviteCode: 'ABC12345'
1144
- });
1145
-
1146
- console.log(result.valid); // true
1147
- ```
1148
-
1149
- 📖 **详细文档**: [正则验证](./docs/validation-guide.md#正则验证) | [String 扩展](./docs/string-extensions.md)
1150
-
1151
- ---
1152
-
1153
- ### 自定义验证器
1154
-
1155
- **场景**: 业务逻辑验证
1156
-
1157
- ```javascript
1158
- const { dsl, validate, validateAsync } = require('schema-dsl');
1159
-
1160
- // 同步自定义验证
1161
- const schema1 = dsl({
1162
- username: 'string:3-32!'
1163
- .custom((value) => {
1164
- // 不能以数字开头
1165
- if (/^\\d/.test(value)) {
1166
- return '用户名不能以数字开头';
1167
- }
1168
- // 禁用敏感词
1169
- const blocked = ['admin', 'root', 'system'];
1170
- if (blocked.includes(value.toLowerCase())) {
1171
- return '该用户名不可用';
1172
- }
1173
- })
1174
- .label('用户名')
1175
- });
1176
-
1177
- // 异步自定义验证(检查唯一性)
1178
- const schema2 = dsl({
1179
- email: 'email!'
1180
- .custom(async (value) => {
1181
- const exists = await checkEmailExists(value);
1182
- if (exists) {
1183
- return '该邮箱已被注册';
1184
- }
1185
- })
1186
- .label('邮箱')
1187
- });
1188
-
1189
- // 多字段联合验证
1190
- const schema3 = dsl({
1191
- password: 'string:8-32!',
1192
- confirmPassword: 'string:8-32!'
1193
- })
1194
- .custom((data) => {
1195
- if (data.password !== data.confirmPassword) {
1196
- return { confirmPassword: '两次密码不一致' };
1197
- }
1198
- });
1199
-
1200
- // 使用
1201
- const result = validate(schema1, { username: 'admin' });
1202
- console.log(result.errors); // [{ path: 'username', message: '该用户名不可用' }]
1203
-
1204
- // 模拟数据库查询
1205
- async function checkEmailExists(email) {
1206
- // 实际项目中查询数据库
1207
- return email === 'exists@example.com';
1208
- }
1209
- ```
1210
-
1211
- 📖 **详细文档**: [自定义验证器](./docs/custom-extensions-guide.md) | [验证指南](./docs/validation-guide.md)
1212
-
1213
- ---
1214
-
1215
- ### 框架集成
1216
-
1217
- #### Koa 集成
1218
-
1219
- ```javascript
1220
- const Koa = require('koa');
1221
- const { dsl, validateAsync, ValidationError } = require('schema-dsl');
1222
-
1223
- const app = new Koa();
1224
-
1225
- const createUserSchema = dsl({
1226
- username: 'string:3-32!',
1227
- email: 'email!',
1228
- password: 'string:8-32!'
1229
- });
1230
-
1231
- // 路由
1232
- app.use(async (ctx) => {
1233
- if (ctx.path === '/api/users' && ctx.method === 'POST') {
1234
- try {
1235
- // 验证请求体
1236
- const validData = await validateAsync(createUserSchema, ctx.request.body);
1237
-
1238
- // 业务逻辑
1239
- const user = await createUser(validData);
1240
-
1241
- ctx.body = { success: true, data: user };
1242
- } catch (error) {
1243
- if (error instanceof ValidationError) {
1244
- ctx.status = 400;
1245
- ctx.body = {
1246
- success: false,
1247
- message: 'Validation failed',
1248
- errors: error.errors
1249
- };
1250
- } else {
1251
- throw error;
1252
- }
1253
- }
1254
- }
1255
- });
1256
-
1257
- app.listen(3000);
1258
-
1259
- // 模拟用户创建函数
1260
- async function createUser(data) {
1261
- return { id: '123', ...data };
1262
- }
1263
- ```
1264
-
1265
- #### Fastify 集成
1266
-
1267
- ```javascript
1268
- const fastify = require('fastify')();
1269
- const { dsl, validateAsync, ValidationError } = require('schema-dsl');
1270
-
1271
- const createUserSchema = dsl({
1272
- username: 'string:3-32!',
1273
- email: 'email!',
1274
- password: 'string:8-32!'
1275
- });
1276
-
1277
- // 使用 preValidation hook
1278
- fastify.post('/api/users', {
1279
- preValidation: async (request, reply) => {
1280
- try {
1281
- request.body = await validateAsync(createUserSchema, request.body);
1282
- } catch (error) {
1283
- if (error instanceof ValidationError) {
1284
- reply.code(400).send({
1285
- success: false,
1286
- message: 'Validation failed',
1287
- errors: error.errors
1288
- });
1289
- } else {
1290
- throw error;
1291
- }
1292
- }
1293
- }
1294
- }, async (request, reply) => {
1295
- // 验证通过,继续处理
1296
- const user = await createUser(request.body);
1297
- return { success: true, data: user };
1298
- });
1299
-
1300
- fastify.listen({ port: 3000 });
1301
-
1302
- // 模拟用户创建函数
1303
- async function createUser(data) {
1304
- return { id: '123', ...data };
1305
- }
1306
- ```
1307
-
1308
- 📖 **详细文档**: [中间件使用示例](./examples/middleware-usage.js) | [Express 集成](./examples/express-integration.js)
1309
-
1310
- ---
1311
-
1312
- ### 字段库复用
1313
-
1314
- **场景**: 大型项目的字段管理
1315
-
1316
- ```javascript
1317
- // fields/common.js - 定义字段库
1318
- const { dsl } = require('schema-dsl');
1319
-
1320
- module.exports = {
1321
- // 基础字段
1322
- email: () => 'email!'.label('邮箱地址'),
1323
- phone: (country = 'cn') => 'string:11!'.phoneNumber(country).label('手机号'),
1324
- username: () => 'string:3-32!'.username().label('用户名'),
1325
- password: (strength = 'medium') => 'string:8-32!'.password(strength).label('密码'),
1326
-
1327
- // 组合字段
1328
- userAuth: () => ({
1329
- username: 'string:3-32!'.username().label('用户名'),
1330
- password: 'string:8-32!'.password('strong').label('密码')
1331
- }),
1332
-
1333
- userProfile: () => ({
1334
- nickname: 'string:2-20!'.label('昵称'),
1335
- bio: 'string:-500',
1336
- avatar: 'url',
1337
- birthday: 'date'
1338
- }),
1339
-
1340
- address: () => ({
1341
- country: 'string!',
1342
- province: 'string!',
1343
- city: 'string!',
1344
- district: 'string',
1345
- street: 'string',
1346
- zipCode: 'string:6'
1347
- })
1348
- };
1349
-
1350
- // schemas/user.js - 使用字段库
1351
- const { dsl } = require('schema-dsl');
1352
- const fields = require('../fields/common');
1353
-
1354
- // 注册 Schema
1355
- exports.registerSchema = dsl({
1356
- ...fields.userAuth(), // 展开用户认证字段
1357
- email: fields.email(),
1358
- phone: fields.phone(),
1359
- agree: 'boolean!'
1360
- });
1361
-
1362
- // 个人资料 Schema
1363
- exports.profileSchema = dsl({
1364
- ...fields.userProfile(), // 展开用户资料字段
1365
- ...fields.address() // 展开地址字段
1366
- });
1367
-
1368
- // 登录 Schema
1369
- exports.loginSchema = dsl({
1370
- account: 'types:email|phone!', // 邮箱或手机号
1371
- password: fields.password('strong')
1372
- });
1373
- ```
1374
-
1375
- 📖 **详细文档**: [SchemaUtils 完整指南](./docs/schema-utils.md) | [字段库复用](./docs/schema-utils.md#字段库复用大型项目) | [最佳实践](./docs/best-practices.md)
1376
-
1377
- ---
1378
-
1379
- ## �📖 DSL 语法速查
1380
-
1381
- ### 基础类型
1382
-
1383
- ```javascript
1384
- dsl({
1385
- // 字符串
1386
- name: 'string!', // 必填字符串
1387
- code: 'string:6', // 🆕 v1.0.3: 精确长度 6(验证码)
1388
- bio: 'string:-500', // 🆕 v1.0.3: 最大长度 500
1389
- content: 'string:10-', // 🆕 v1.0.3: 最小长度 10
1390
- username: 'string:3-32', // 长度范围 3-32
1391
-
1392
- // 数字
1393
- age: 'number!', // 必填数字
1394
- price: 'number:0-9999.99', // 范围 0-9999.99
1395
- score: 'integer:0-100', // 整数 0-100
1396
-
1397
- // 🆕 v1.1.2: 数字比较运算符
1398
- minAge: 'number:>=18', // 大于等于 18
1399
- maxScore: 'number:<=100', // 小于等于 100
1400
- positiveNum: 'number:>0', // 大于 0(不包括0)
1401
- temperature: 'number:<100', // 小于 100(不包括100)
1402
- exactValue: 'number:=50', // 等于 50
1403
- negativeOk: 'number:>-10', // 支持负数:大于 -10
1404
- priceLimit: 'number:<=99.99', // 支持小数:小于等于 99.99
1405
-
1406
- // 💡 比较运算符 vs 范围语法
1407
- // 'number:18-120' → 18 <= x <= 120 (包括边界)
1408
- // 'number:>=18' → x >= 18 (语义更清晰)
1409
- // 'number:>0' → x > 0 (不包括0,范围语法无法表达)
1410
- // 'number:<100' → x < 100 (不包括100,范围语法无法表达)
1411
-
1412
- // 布尔值
1413
- active: 'boolean!',
1414
-
1415
- // 枚举 - 限定值只能是特定选项之一
1416
- status: 'active|inactive|pending', // ✅ 推荐:字符串枚举(简写)
1417
- role: 'enum:admin|user|guest!', // 等价写法(完整形式)
1418
-
1419
- isPublic: 'true|false', // ✅ 自动识别布尔值
1420
- isVerified: 'enum:boolean:true|false', // 显式指定类型(更清晰)
1421
-
1422
- priority: '1|2|3!', // ✅ 自动识别数字
1423
- level: 'enum:number:1|2|3|4|5', // 显式指定(避免字符串"1"通过验证)
1424
- grade: 'enum:integer:1|2|3', // 整数枚举(禁止小数)
1425
- rating: '1.0|1.5|2.0|2.5', // 小数枚举
1426
-
1427
- // 💡 使用建议:
1428
- // - 默认用简写(active|inactive)- 最简洁
1429
- // - 需要明确类型时用完整形式(enum:number:1|2|3)
1430
- // - 值可能混淆时用完整形式(避免"1"和1混用)
1431
-
1432
- // 数组
1433
- tags: 'array<string>', // 字符串数组
1434
- items: 'array:1-10<number>', // 1-10 个数字的数组
1435
-
1436
- // 对象
1437
- meta: 'object' // 任意对象
1438
- })
1439
- ```
1440
-
1441
- ### 内置格式
1442
-
1443
- ```javascript
1444
- dsl({
1445
- // 邮箱
1446
- email: 'email!',
1447
-
1448
- // URL
1449
- website: 'url!',
1450
- homepage: 'https-url!', // 必须 HTTPS
1451
-
1452
- // 日期时间
1453
- birthday: 'date!', // YYYY-MM-DD
1454
- createdAt: 'datetime!', // ISO 8601
1455
- publishTime: 'timestamp!', // Unix 时间戳
1456
-
1457
- // UUID
1458
- userId: 'uuid!',
1459
- requestId: 'uuid:v4!',
1460
-
1461
- // 中国手机号
1462
- phone: 'phone:cn!',
1463
-
1464
- // 身份证号
1465
- idCard: 'idCard:cn!',
1466
-
1467
- // 信用卡
1468
- cardNumber: 'creditCard:visa!',
1469
-
1470
- // 邮政编码
1471
- zipCode: 'postalCode:cn!',
1472
-
1473
- // 车牌号
1474
- plate: 'licensePlate:cn!',
1475
-
1476
- // 护照号
1477
- passport: 'passport:cn!'
1478
- })
1479
- ```
1480
-
1481
- ### ✨ v1.0.3 新增类型
1482
-
1483
- #### URL友好字符串(slug)- 用于博客和页面URL
1484
-
1485
- ```javascript
1486
- dsl({
1487
- // 博客文章URL: /posts/my-first-blog-post
1488
- articleSlug: 'slug:3-100!',
1489
-
1490
- // 分类URL: /category/javascript
1491
- categorySlug: 'slug!',
1492
-
1493
- // 链式调用
1494
- pageSlug: 'string!'.slug()
1495
- })
1496
-
1497
- // ✅ 有效格式: my-blog-post, hello-world-123, article
1498
- // ✅ 只能包含: 小写字母(a-z)、数字(0-9)、连字符(-)
1499
- // ❌ 不能包含: 大写字母、下划线、空格、特殊字符
1500
-
1501
- // 查看完整示例: node examples/slug.examples.js
1502
- ```
1503
-
1504
- #### 字符串验证增强 - 解决常见验证场景
1505
-
1506
- ```javascript
1507
- dsl({
1508
- // 用户名 - 只允许字母和数字(不允许下划线)
1509
- username: 'alphanum:3-20!', // 只允许 john123,不允许 john_123
1510
-
1511
- // 邮箱 - 统一小写存储
1512
- email: 'lower!', // 自动转小写
1513
-
1514
- // 验证码 - 强制大写
1515
- code: 'upper:6!', // 验证码大写: ABC123
1516
-
1517
- // JSON配置 - 验证JSON字符串格式
1518
- config: 'json!', // 存储JSON配置: {"theme":"dark"}
1519
-
1520
- // 端口号 - 限制有效范围
1521
- serverPort: 'port!', // 1-65535
1522
- dbPort: 'port!' // 数据库端口
1523
- })
1524
- ```
1525
-
1526
- #### 约束语法优化 ⚠️ 破坏性变更
1527
-
1528
- **v1.0.3 修复了单值语法**,使其更符合直觉:
1529
-
1530
- ```javascript
1531
- dsl({
1532
- code: 'string:6!', // 🆕 精确长度 6(之前是最大长度)
1533
- bio: 'string:-500', // 🆕 最大长度 500(新语法)
1534
- content: 'string:10-', // 🆕 最小长度 10(新语法)
1535
- username: 'string:3-32' // 长度范围 3-32(不变)
1536
- })
1537
- ```
1538
-
1539
- **迁移指南**:
1540
- - 如果你之前用 `'string:N'` 表示最大长度,请改为 `'string:-N'`
1541
- - 如果你期望精确长度,无需修改(新版本行为正确)
1542
-
1543
- **查看详细文档**:
1544
- - [完整验证规则参考](./docs/validation-rules-v1.0.2.md)
1545
- - [更新日志](./CHANGELOG.md)
1546
-
1547
- ### 高级特性
1548
-
1549
- ```javascript
1550
- dsl({
1551
- // 用户名(3-32字符,字母数字下划线)
1552
- username: 'string:3-32!'.username(),
1553
-
1554
- // 密码(8-32字符,必须包含大小写字母和数字)
1555
- password: 'string:8-32!'.password(),
1556
-
1557
- // 自定义正则
1558
- code: 'string!'.pattern(/^[A-Z]{3}\d{3}$/),
1559
-
1560
- // 自定义错误消息
1561
- age: 'number:18-120!'.messages({
1562
- 'number.min': '年龄必须大于18岁',
1563
- 'number.max': '年龄不能超过120岁'
1564
- }),
1565
-
1566
- // 字段标签(用于多语言)
1567
- email: 'email!'.label('用户邮箱'),
1568
-
1569
- // 字段描述
1570
- bio: 'string:10-500'.description('用户简介,10-500字符')
1571
- })
1572
- ```
1573
-
1574
- ### 条件验证 - dsl.match 和 dsl.if
1575
-
1576
- **根据其他字段的值动态决定验证规则**
1577
-
1578
- ```javascript
1579
- const { dsl } = require('schema-dsl');
1580
-
1581
- // 1. dsl.match - 根据字段值匹配不同规则(类似 switch-case)
1582
- const contactSchema = dsl({
1583
- contactType: 'email|phone|wechat',
1584
-
1585
- // 根据 contactType 的值决定 contact 字段的验证规则
1586
- contact: dsl.match('contactType', {
1587
- email: 'email!', // contactType='email' 时验证邮箱格式
1588
- phone: 'string:11!', // contactType='phone' 时验证11位手机号
1589
- wechat: 'string:6-20!', // contactType='wechat' 时验证微信号
1590
- _default: 'string' // 默认规则(可选)
1591
- })
1592
- });
1593
-
1594
- // ✅ 验证通过
1595
- validate(contactSchema, { contactType: 'email', contact: 'user@example.com' });
1596
- validate(contactSchema, { contactType: 'phone', contact: '13800138000' });
1597
-
1598
- // ❌ 验证失败
1599
- validate(contactSchema, { contactType: 'email', contact: 'invalid' });
1600
-
1601
-
1602
- // 2. dsl.if - 简单条件分支(类似 if-else)
1603
- const vipSchema = dsl({
1604
- isVip: 'boolean!',
1605
-
1606
- // 如果是 VIP,折扣必须在 10-50 之间;否则在 0-10 之间
1607
- discount: dsl.if('isVip', 'number:10-50!', 'number:0-10')
1608
- });
1609
-
1610
- // ✅ VIP 用户
1611
- validate(vipSchema, { isVip: true, discount: 30 });
1612
-
1613
- // ❌ 非 VIP 用户折扣超过 10
1614
- validate(vipSchema, { isVip: false, discount: 15 });
1615
-
1616
-
1617
- // 3. 实际应用场景:订单验证
1618
- const orderSchema = dsl({
1619
- paymentMethod: 'alipay|wechat|card|cod', // cod = 货到付款
1620
-
1621
- // 根据支付方式决定支付信息格式
1622
- paymentInfo: dsl.match('paymentMethod', {
1623
- alipay: 'email!', // 支付宝:邮箱
1624
- wechat: 'string:20-30', // 微信:支付串
1625
- card: 'string:16-19', // 银行卡:卡号
1626
- cod: 'string:0-0', // 货到付款:无需支付信息
1627
- _default: 'string'
1628
- }),
1629
-
1630
- // 货到付款需要详细地址
1631
- address: dsl.if('paymentMethod',
1632
- 'string:10-200!', // cod = 货到付款时地址必填
1633
- 'string:10-200' // 其他支付方式地址可选
1634
- )
1635
- });
1636
- ```
1637
-
1638
- **💡 使用场景**:
1639
- - ✅ 多种联系方式验证(邮箱/手机/微信)
1640
- - ✅ VIP 和普通用户不同的折扣范围
1641
- - ✅ 不同支付方式的支付信息格式
1642
- - ✅ 根据用户类型决定必填字段
1643
-
1644
- **查看完整示例**: [examples/dsl-match-example.js](./examples/dsl-match-example.js)
1645
-
1646
- ---
1647
-
1648
- ## 🔧 核心功能
1649
-
1650
- ### 1. String 扩展 - 链式调用
1651
-
1652
- ```javascript
1653
- // 直接在字符串上调用验证方法
1654
- const schema = dsl({
1655
- username: 'string:3-32!'.username().label('用户名'),
1656
- email: 'email!'.label('邮箱地址'),
1657
- phone: 'string:11!'.phoneNumber('cn').label('手机号'),
1658
- password: 'string:8-32!'.password().messages({
1659
- 'string.password': '密码必须包含大小写字母和数字'
1660
- })
1661
- });
1662
- ```
1663
-
1664
- ### 2. Schema 复用工具
1665
-
1666
- ```javascript
1667
- const { SchemaUtils } = require('schema-dsl');
1668
-
1669
- // 创建可复用的字段片段
1670
- const fields = SchemaUtils.createLibrary({
1671
- email: () => 'email!'.label('邮箱'),
1672
- phone: () => 'string:11!'.phoneNumber('cn').label('手机号'),
1673
- username: () => 'string:3-32!'.username().label('用户名')
1674
- });
1675
-
1676
- // 在多个 Schema 中复用
1677
- const loginSchema = dsl({
1678
- account: fields.email(),
1679
- password: 'string!'
1680
- });
1681
-
1682
- const registerSchema = dsl({
1683
- username: fields.username(),
1684
- email: fields.email(),
1685
- phone: fields.phone(),
1686
- password: 'string:8-32!'
1687
- });
1688
-
1689
- // Schema 组合操作
1690
- const baseUser = dsl({ name: 'string!', email: 'email!' });
1691
-
1692
- // 挑选字段
1693
- const publicUser = SchemaUtils.pick(baseUser, ['name', 'email']);
1694
-
1695
- // 排除字段
1696
- const safeUser = SchemaUtils.omit(baseUser, ['password']);
1697
-
1698
- // 扩展字段
1699
- const adminUser = SchemaUtils.extend(baseUser, {
1700
- role: 'admin|superadmin',
1701
- permissions: 'array<string>'
1702
- });
1703
-
1704
- // 部分验证(移除必填限制)
1705
- const updateUser = SchemaUtils.partial(baseUser, ['name', 'email']);
1706
- ```
1707
-
1708
- ### 3. 数据库 Schema 导出
1709
-
1710
- **唯一支持数据库 Schema 自动生成的验证库!**
1711
-
1712
- ```javascript
1713
- const { dsl, exporters } = require('schema-dsl');
1714
-
1715
- const userSchema = dsl({
1716
- username: 'string:3-32!',
1717
- email: 'email!',
1718
- age: 'number:18-120',
1719
- tags: 'array<string>',
1720
- createdAt: 'datetime!'
1721
- });
1722
-
1723
- // 导出为 MongoDB Schema
1724
- const mongoSchema = exporters.MongoDBExporter.export(userSchema);
1725
- console.log(mongoSchema);
1726
- /*
1727
- {
1728
- username: { type: String, required: true, minlength: 3, maxlength: 32 },
1729
- email: { type: String, required: true, match: /.../ },
1730
- age: { type: Number, min: 18, max: 120 },
1731
- tags: [{ type: String }],
1732
- createdAt: { type: Date, required: true }
1733
- }
1734
- */
1735
-
1736
- // 导出为 MySQL DDL
1737
- const mysqlExporter = new exporters.MySQLExporter();
1738
- const mysqlDDL = mysqlExporter.export('users', userSchema);
1739
- console.log(mysqlDDL);
1740
- /*
1741
- CREATE TABLE `users` (
1742
- `username` VARCHAR(32) NOT NULL,
1743
- `email` VARCHAR(255) NOT NULL,
1744
- `age` INT,
1745
- `tags` JSON,
1746
- `createdAt` DATETIME NOT NULL
1747
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1748
- */
1749
-
1750
- // 导出为 PostgreSQL DDL
1751
- const pgExporter = new exporters.PostgreSQLExporter();
1752
- const pgDDL = pgExporter.export('users', userSchema);
1753
-
1754
- // 导出为 Markdown 文档
1755
- const markdown = exporters.MarkdownExporter.export(userSchema, {
1756
- title: 'User API 文档'
1757
- });
1758
- ```
1759
-
1760
- ### 4. 多语言支持
1761
-
1762
- ```javascript
1763
- const { dsl, validate } = require('schema-dsl');
1764
- const path = require('path');
1765
-
1766
- // 方式 1: 从目录加载语言包(推荐)
1767
- dsl.config({
1768
- i18n: path.join(__dirname, 'i18n/dsl') // 直接传字符串路径
1769
- });
1770
-
1771
- // 方式 2: 直接传入语言包对象
1772
- dsl.config({
1773
- i18n: {
1774
- 'zh-CN': {
1775
- 'label.username': '用户名',
1776
- 'label.email': '邮箱地址',
1777
- 'required': '{{#label}}不能为空',
1778
- 'string.min': '{{#label}}长度不能少于{{#limit}}个字符'
1779
- },
1780
- 'en-US': {
1781
- 'label.username': 'Username',
1782
- 'label.email': 'Email Address',
1783
- 'required': '{{#label}} is required',
1784
- 'string.min': '{{#label}} must be at least {{#limit}} characters'
1785
- }
1786
- }
1787
- });
1788
-
1789
- // 使用 Label Key
1790
- const schema = dsl({
1791
- username: dsl('string:3-32!').label('label.username'),
1792
- email: dsl('email!').label('label.email')
1793
- });
1794
-
1795
- // 验证时指定语言
1796
- const result1 = validate(schema, data, { locale: 'zh-CN' });
1797
- // 错误消息:用户名长度不能少于3个字符
1798
-
1799
- const result2 = validate(schema, data, { locale: 'en-US' });
1800
- // 错误消息:Username must be at least 3 characters
1801
- ```
1802
-
1803
- ### 5. 缓存配置 (v1.0.4+)
1804
-
1805
- ```javascript
1806
- const { dsl, config } = require('schema-dsl');
1807
-
1808
- // 配置缓存选项(推荐在使用 DSL 之前调用)
1809
- config({
1810
- cache: {
1811
- maxSize: 1000, // 最大缓存条目数(默认:100)
1812
- ttl: 7200000, // 缓存过期时间(毫秒,默认:3600000,即1小时)
1813
- enabled: true, // 是否启用缓存(默认:true)
1814
- statsEnabled: true // 是否启用统计(默认:true)
1815
- }
1816
- });
1817
-
1818
- // 之后创建的 Schema 将使用新的缓存配置
1819
- const schema = dsl({ name: 'string!' });
1820
-
1821
- // 也可以在 Validator 创建后动态修改配置(向后兼容)
1822
- const { getDefaultValidator } = require('schema-dsl');
1823
- const validator = getDefaultValidator();
1824
- console.log('当前缓存配置:', validator.cache.options);
1825
-
1826
- // 动态修改
1827
- config({
1828
- cache: { maxSize: 5000 } // 只修改某个参数
1829
- });
1830
- ```
1831
-
1832
- **缓存说明**:
1833
- - Schema 编译结果会被缓存以提高性能
1834
- - 使用 LRU(最近最少使用)淘汰策略
1835
- - 支持 TTL(生存时间)自动过期
1836
- - 可通过 `validator.cache.getStats()` 查看缓存统计信息
1837
-
1838
- ### 6. 插件系统
1839
-
1840
- ```javascript
1841
- const { PluginManager } = require('schema-dsl');
1842
-
1843
- const pluginManager = new PluginManager();
1844
-
1845
- // 注册自定义验证器插件
1846
- pluginManager.register({
1847
- name: 'custom-validator',
1848
- version: '1.0.0',
1849
-
1850
- onBeforeValidate(schema, data) {
1851
- // 验证前预处理
1852
- console.log('验证开始');
1853
- },
1854
-
1855
- onAfterValidate(result) {
1856
- // 验证后处理
1857
- console.log('验证结束:', result.valid);
1858
- return result;
1859
- },
1860
-
1861
- onError(error) {
1862
- // 错误处理
1863
- console.error('验证出错:', error);
1864
- }
1865
- });
1866
-
1867
- // 注册自定义格式插件
1868
- pluginManager.register({
1869
- name: 'custom-formats',
1870
-
1871
- formats: {
1872
- 'hex-color': {
1873
- validate: (value) => /^#[0-9A-F]{6}$/i.test(value),
1874
- message: '必须是有效的十六进制颜色代码'
1875
- },
1876
- 'mac-address': {
1877
- validate: (value) => /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/i.test(value),
1878
- message: '必须是有效的 MAC 地址'
1879
- }
1880
- }
1881
- });
1882
-
1883
- // 使用自定义格式
1884
- const schema = dsl({
1885
- color: 'hex-color!',
1886
- mac: 'mac-address!'
1887
- });
1888
- ```
1889
-
1890
- ### 7. 错误处理
1891
-
1892
- ```javascript
1893
- const { validate, ValidationError } = require('schema-dsl');
1894
-
1895
- const schema = dsl({
1896
- email: 'email!',
1897
- age: 'number:18-120!'
1898
- });
1899
-
1900
- const result = validate(schema, { email: 'invalid', age: 15 });
1901
-
1902
- if (!result.valid) {
1903
- console.log(result.errors);
1904
- /*
1905
- [
1906
- {
1907
- field: 'email',
1908
- message: '邮箱格式不正确',
1909
- keyword: 'format',
1910
- params: { format: 'email' }
1911
- },
1912
- {
1913
- field: 'age',
1914
- message: '年龄必须大于等于18',
1915
- keyword: 'minimum',
1916
- params: { limit: 18 }
1917
- }
1918
- ]
1919
- */
1920
- }
1921
-
1922
- // 使用 validateAsync + try-catch
1923
- try {
1924
- const data = await validateAsync(schema, invalidData);
1925
- // 验证通过
1926
- } catch (error) {
1927
- if (error instanceof ValidationError) {
1928
- console.log(error.errors); // 错误列表
1929
- console.log(error.statusCode); // 400
1930
- console.log(error.toJSON()); // 标准 JSON 格式
1931
- }
1932
- }
1933
- ```
1934
-
1935
- ---
1936
-
1937
- ## ❓ 常见问题 FAQ
1938
-
1939
- ### Q1: 如何判断数据不能为空?(类似 `if(!data)`)
1940
-
1941
- **方案1:使用必填标记**(推荐)
1942
- ```javascript
1943
- const schema = dsl({
1944
- username: 'string!', // 必填,不能为空
1945
- email: 'email!'
1946
- });
1947
- ```
1948
-
1949
- **方案2:使用条件验证 + 抛错**
1950
- ```javascript
1951
- // 验证失败自动抛错
1952
- dsl.if(d => !d)
1953
- .message('数据不能为空')
1954
- .assert(data);
1955
- ```
1956
-
1957
- **方案3:异步验证**
1958
- ```javascript
1959
- // Express/Koa 推荐
1960
- await dsl.if(d => !d)
1961
- .message('数据不能为空')
1962
- .validateAsync(data);
1963
- ```
1964
-
1965
- ---
1966
-
1967
- ### Q2: 如何判断数据是否是对象?(类似 `typeof data === 'object'`)
1968
-
1969
- **方案1:使用内置 object 类型**(推荐)
1970
- ```javascript
1971
- const schema = dsl({
1972
- data: 'object!' // 必须是对象(排除 null 和 array)
1973
- });
1974
-
1975
- validate(schema, { data: { name: 'John' } }); // ✅ 通过
1976
- validate(schema, { data: 'string' }); // ❌ 失败
1977
- validate(schema, { data: [] }); // ❌ 失败
1978
- ```
1979
-
1980
- **方案2:条件验证 + 抛错**
1981
- ```javascript
1982
- dsl.if(d => typeof d !== 'object' || d === null || Array.isArray(d))
1983
- .message('data 必须是一个对象')
1984
- .assert(data);
1985
- ```
1986
-
1987
- **方案3:带结构验证**
1988
- ```javascript
1989
- const schema = dsl({
1990
- data: {
1991
- name: 'string!',
1992
- age: 'integer!',
1993
- email: 'email'
1994
- }
1995
- });
1996
-
1997
- await validateAsync(schema, input); // 验证对象结构
1998
- ```
1999
-
2000
- ---
2001
-
2002
- ### Q3: 如何验证嵌套对象?
2003
-
2004
- ```javascript
2005
- const schema = dsl({
2006
- user: {
2007
- profile: 'object!', // profile 必须是对象
2008
- settings: {
2009
- theme: 'string',
2010
- notifications: 'object!' // 嵌套对象验证
2011
- }
2012
- }
2013
- });
2014
- ```
2015
-
2016
- ---
2017
-
2018
- ### Q4: 如何在 Express/Koa 中使用?
2019
-
2020
- ```javascript
2021
- app.post('/api/user', async (req, res) => {
2022
- try {
2023
- // 1. 验证请求体是对象
2024
- await dsl.if(d => typeof d !== 'object' || d === null)
2025
- .message('请求体必须是对象')
2026
- .validateAsync(req.body);
2027
-
2028
- // 2. 验证字段
2029
- const schema = dsl({
2030
- username: 'string:3-32!',
2031
- email: 'email!',
2032
- password: 'string:8-!'
2033
- });
2034
-
2035
- const validData = await validateAsync(schema, req.body);
2036
-
2037
- // 继续处理...
2038
- res.json({ success: true, data: validData });
2039
- } catch (error) {
2040
- res.status(400).json({ error: error.message });
2041
- }
2042
- });
2043
- ```
2044
-
2045
- ---
2046
-
2047
- ### Q5: 如何自定义错误消息?
2048
-
2049
- ```javascript
2050
- const schema = dsl({
2051
- username: dsl('string:3-32!')
2052
- .label('用户名')
2053
- .messages({
2054
- minLength: '用户名至少需要 {{#limit}} 个字符',
2055
- required: '用户名不能为空'
2056
- }),
2057
-
2058
- email: dsl('email!')
2059
- .label('邮箱地址')
2060
- .messages({
2061
- format: '请输入有效的邮箱地址',
2062
- required: '邮箱不能为空'
2063
- })
2064
- });
2065
- ```
2066
-
2067
- ---
2068
-
2069
- ### Q6: 类型对照表
2070
-
2071
- | JavaScript 条件 | schema-dsl 写法 |
2072
- |----------------|----------------|
2073
- | `if (!data)` | `'string!'` 或 `.assert(data)` |
2074
- | `if (typeof data === 'object')` | `'object!'` |
2075
- | `if (typeof data === 'string')` | `'string!'` |
2076
- | `if (typeof data === 'number')` | `'number!'` |
2077
- | `if (Array.isArray(data))` | `'array!'` |
2078
- | `if (data === null)` | `'null!'` |
2079
- | `if (data > 0)` | `'number:0-!'` |
2080
- | `if (data.length >= 3)` | `'string:3-!'` |
2081
-
2082
- ---
2083
-
2084
- ### Q7: 如何合并多个 dsl.if() 验证?
2085
-
2086
- **原代码(多个独立验证)**:
2087
- ```javascript
2088
- dsl.if(d => !d)
2089
- .message('ACCOUNT_NOT_FOUND')
2090
- .assert(account);
2091
-
2092
- dsl.if(d => d.tradable_credits < amount)
2093
- .message('INSUFFICIENT_TRADABLE_CREDITS')
2094
- .assert(account.tradable_credits);
2095
- ```
2096
-
2097
- **✅ 方案1:使用 .and() 链式合并(v1.1.1 推荐)**
2098
- ```javascript
2099
- // ✅ 每个条件都有独立的错误消息
2100
- dsl.if(d => !d)
2101
- .message('ACCOUNT_NOT_FOUND')
2102
- .and(d => d.tradable_credits < amount)
2103
- .message('INSUFFICIENT_TRADABLE_CREDITS')
2104
- .assert(account);
2105
-
2106
- // 工作原理:
2107
- // - 第一个条件失败 → 返回 'ACCOUNT_NOT_FOUND'
2108
- // - 第二个条件失败 → 返回 'INSUFFICIENT_TRADABLE_CREDITS'
2109
- // - 所有条件通过 → 验证成功
2110
- ```
2111
-
2112
- **✅ 方案2:使用 .elseIf() 分支验证**
2113
- ```javascript
2114
- // ✅ 按优先级检查,找到第一个失败的
2115
- dsl.if(d => !d)
2116
- .message('ACCOUNT_NOT_FOUND')
2117
- .elseIf(d => d.tradable_credits < amount)
2118
- .message('INSUFFICIENT_TRADABLE_CREDITS')
2119
- .assert(account);
2120
- ```
2121
-
2122
- **✅ 方案3:保持独立验证**(最清晰)
2123
- ```javascript
2124
- // ✅ 两个独立的验证器
2125
- dsl.if(d => !d).message('ACCOUNT_NOT_FOUND').assert(account);
2126
- dsl.if(d => d.tradable_credits < amount)
2127
- .message('INSUFFICIENT_TRADABLE_CREDITS')
2128
- .assert(account.tradable_credits);
2129
- ```
2130
-
2131
- **⚠️ 注意事项**:
2132
- - `.and()` 用于组合多个条件,每个条件可以有**独立的** `.message()` (v1.1.1)
2133
- - 如果 `.and()` 后不调用 `.message()`,则使用前一个条件的消息
2134
- - `.elseIf()` 按顺序检查,找到第一个失败的就停止(if-else-if 逻辑)
2135
-
2136
- **何时使用**:
2137
- - ✅ 使用 `.and()` - 多个条件,每个有不同错误消息(v1.1.1)
2138
- - ✅ 使用 `.elseIf()` - 不同分支有不同验证规则
2139
- - ✅ 独立验证 - 最清晰,最可靠
2140
-
2141
- **实际应用示例**:
2142
- ```javascript
2143
- // 账户验证:检查存在性 + 余额 + 状态
2144
- dsl.if(d => !d)
2145
- .message('ACCOUNT_NOT_FOUND')
2146
- .and(d => d.status !== 'active')
2147
- .message('ACCOUNT_INACTIVE')
2148
- .and(d => d.tradable_credits < amount)
2149
- .message('INSUFFICIENT_TRADABLE_CREDITS')
2150
- .assert(account);
2151
-
2152
- // 每个失败条件都有清晰的错误消息!
2153
- ```
2154
-
2155
- 📖 更多示例请查看 [完整文档](./docs/INDEX.md)
2156
-
2157
- ---
2158
-
2159
- ### Q8: 如何统一抛出多语言错误?(v1.1.1+)
2160
-
2161
- **问题**: 业务代码中抛出的错误无法多语言,与 `.message()` 和 `.label()` 不一致
2162
-
2163
- **✅ 解决方案:使用 `I18nError` 或 `dsl.error`**
2164
-
2165
- ```javascript
2166
- const { I18nError, dsl } = require('schema-dsl');
2167
-
2168
- // 方式1:直接抛出
2169
- I18nError.throw('account.notFound');
2170
- // 中文: "账户不存在"
2171
- // 英文: "Account not found"
2172
-
2173
- // 方式2:带参数插值
2174
- I18nError.throw('account.insufficientBalance', {
2175
- balance: 50,
2176
- required: 100
2177
- });
2178
- // 输出: "余额不足,当前余额50,需要100"
2179
-
2180
- // 方式3:断言风格(推荐)
2181
- I18nError.assert(account, 'account.notFound');
2182
- I18nError.assert(
2183
- account.balance >= 100,
2184
- 'account.insufficientBalance',
2185
- { balance: account.balance, required: 100 }
2186
- );
2187
-
2188
- // 方式4:快捷方法
2189
- dsl.error.throw('user.noPermission');
2190
- dsl.error.assert(user.role === 'admin', 'user.noPermission');
2191
- ```
2192
-
2193
- **🆕 对象格式错误配置(v1.1.5)**
2194
-
2195
- 支持统一的数字错误代码,便于前端处理:
2196
-
2197
- ```javascript
2198
- // 语言包配置(lib/locales/zh-CN.js)
2199
- module.exports = {
2200
- // 字符串格式(向后兼容)
2201
- 'user.notFound': '用户不存在',
2202
-
2203
- // 对象格式(v1.1.5 新增)- 使用数字错误码
2204
- 'account.notFound': {
2205
- code: 40001, // 数字错误代码
2206
- message: '账户不存在'
2207
- },
2208
- 'account.insufficientBalance': {
2209
- code: 40002,
2210
- message: '余额不足,当前{{#balance}},需要{{#required}}'
2211
- },
2212
- 'order.notPaid': {
2213
- code: 50001,
2214
- message: '订单未支付'
2215
- }
2216
- };
2217
-
2218
- // lib/locales/en-US.js
2219
- module.exports = {
2220
- 'account.notFound': {
2221
- code: 40001, // 相同的数字 code
2222
- message: 'Account not found'
2223
- },
2224
- 'account.insufficientBalance': {
2225
- code: 40002,
2226
- message: 'Insufficient balance: {{#balance}}, required: {{#required}}'
2227
- },
2228
- 'order.notPaid': {
2229
- code: 50001,
2230
- message: 'Order not paid'
2231
- }
2232
- };
2233
-
2234
- // 使用
2235
- try {
2236
- dsl.error.throw('account.notFound');
2237
- } catch (error) {
2238
- error.code // 40001 (数字代码)
2239
- error.originalKey // 'account.notFound' (原始key)
2240
- error.message // '账户不存在'
2241
-
2242
- // 两种判断方式
2243
- error.is('account.notFound') // ✅ 使用 originalKey
2244
- error.is(40001) // ✅ 使用数字 code
2245
- }
2246
-
2247
- // 前端统一处理(不受语言影响)
2248
- try {
2249
- await api.getAccount(id);
2250
- } catch (error) {
2251
- switch (error.code) {
2252
- case 40001:
2253
- router.push('/account-not-found');
2254
- break;
2255
- case 40002:
2256
- showTopUpDialog(error.params.balance, error.params.required);
2257
- break;
2258
- case 50001:
2259
- showPaymentDialog();
2260
- break;
2261
- }
2262
- }
2263
- ```
2264
-
2265
- **优势**:
2266
- - ✅ 多语言共享相同的数字 `code`,前端统一处理
2267
- - ✅ 完全向后兼容,字符串格式自动转换
2268
- - ✅ `originalKey` 便于调试和日志追踪
2269
- - ✅ 数字 code 更简洁,易于管理和文档化
2270
-
2271
- **错误码规范建议**:
2272
- - `4xxxx` - 客户端错误(账户、权限、参数等)
2273
- - `5xxxx` - 业务逻辑错误(订单、支付、库存等)
2274
- - `6xxxx` - 系统错误(数据库、服务不可用等)
2275
-
2276
- 📖 详细说明: [错误处理文档](./docs/error-handling.md#v115-新功能对象格式错误配置)
2277
-
2278
- **🆕 运行时指定语言(v1.1.0+)**
2279
-
2280
- 无需修改全局语言设置,每次调用时指定:
2281
-
2282
- ```javascript
2283
- // 根据请求头动态返回不同语言
2284
- app.post('/api/account', (req, res, next) => {
2285
- const locale = req.headers['accept-language'] || 'en-US';
2286
- const account = getAccount(req.user.id);
2287
-
2288
- try {
2289
- // 第5个参数指定语言
2290
- dsl.error.assert(account, 'account.notFound', {}, 404, locale);
2291
- dsl.error.assert(
2292
- account.balance >= 100,
2293
- 'account.insufficientBalance',
2294
- { balance: account.balance, required: 100 },
2295
- 400,
2296
- locale
2297
- );
2298
- // 验证通过...
2299
- } catch (error) {
2300
- next(error);
2301
- }
2302
- });
2303
-
2304
- // 同一请求中使用不同语言
2305
- const error1 = dsl.error.create('account.notFound', {}, 404, 'zh-CN');
2306
- console.log(error1.message); // "账户不存在"
2307
-
2308
- const error2 = dsl.error.create('account.notFound', {}, 404, 'en-US');
2309
- console.log(error2.message); // "Account not found"
2310
- ```
2311
-
2312
- **Express/Koa 集成**:
2313
- ```javascript
2314
- // 错误处理中间件
2315
- app.use((error, req, res, next) => {
2316
- if (error instanceof I18nError) {
2317
- return res.status(error.statusCode).json(error.toJSON());
2318
- }
2319
- next(error);
2320
- });
2321
-
2322
- // 业务代码中使用
2323
- app.post('/withdraw', (req, res) => {
2324
- const account = getAccount(req.user.id);
2325
- I18nError.assert(account, 'account.notFound');
2326
- I18nError.assert(
2327
- account.balance >= req.body.amount,
2328
- 'account.insufficientBalance',
2329
- { balance: account.balance, required: req.body.amount }
2330
- );
2331
- // ...
2332
- });
2333
- ```
2334
-
2335
- **内置错误代码**:
2336
- - 通用: `error.notFound`, `error.forbidden`, `error.unauthorized`
2337
- - 账户: `account.notFound`, `account.insufficientBalance`
2338
- - 用户: `user.notFound`, `user.noPermission`
2339
- - 订单: `order.notPaid`, `order.paymentMissing`
2340
-
2341
- 📖 完整文档请查看 [examples/i18n-error.examples.js](./examples/i18n-error.examples.js)
2342
- 📖 运行时多语言支持请查看 [docs/runtime-locale-support.md](./docs/runtime-locale-support.md)
2343
-
2344
- ---
2345
-
2346
- ## 🤝 贡献指南
2347
-
2348
- 欢迎贡献代码、报告问题或提出建议!
2349
-
2350
- ### 开发环境
2351
-
2352
- ```bash
2353
- # 克隆仓库
2354
- git clone https://github.com/vextjs/schema-dsl.git
2355
- cd schema-dsl
2356
-
2357
- # 安装依赖
2358
- npm install
2359
-
2360
- # 运行测试
2361
- npm test
2362
-
2363
- # 代码检查
2364
- npm run lint
2365
-
2366
- # 查看测试覆盖率
2367
- npm run coverage
2368
- ```
2369
-
2370
- ### 提交规范
2371
-
2372
- - 🐛 **Bug 修复**: `fix: 修复XXX问题`
2373
- - ✨ **新功能**: `feat: 添加XXX功能`
2374
- - 📝 **文档**: `docs: 更新XXX文档`
2375
- - 🎨 **代码格式**: `style: 格式化代码`
2376
- - ♻️ **重构**: `refactor: 重构XXX模块`
2377
- - ✅ **测试**: `test: 添加XXX测试`
2378
-
2379
- 详见 [贡献指南](./CONTRIBUTING.md)
2380
-
2381
- ---
2382
-
2383
- ## 📄 开源协议
2384
-
2385
- [MIT License](./LICENSE)
2386
-
2387
- ---
2388
-
2389
- ## 🙏 致谢
2390
-
2391
- - 感谢 [ajv](https://github.com/ajv-validator/ajv) 提供强大的验证引擎
2392
- - 感谢所有贡献者和用户的支持
2393
-
2394
- ---
2395
-
2396
- ## 🔗 相关链接
2397
-
2398
- ### 📦 快速入口
2399
- - [npm 包](https://www.npmjs.com/package/schema-dsl) - 安装和版本历史
2400
- - [GitHub 仓库](https://github.com/vextjs/schema-dsl) - 源代码和 Star ⭐
2401
- - [在线体验](https://runkit.com/npm/schema-dsl) - RunKit 演练场
2402
- - [问题反馈](https://github.com/vextjs/schema-dsl/issues) - Bug 报告和功能请求
2403
- - [讨论区](https://github.com/vextjs/schema-dsl/discussions) - 社区交流
2404
-
2405
- ### 📖 核心文档
2406
- - [完整文档索引](./docs/INDEX.md) - 40+ 篇文档导航
2407
- - [快速开始](./docs/quick-start.md) - 5 分钟入门
2408
- - [DSL 语法](./docs/dsl-syntax.md) - 语法完整指南(2815 行)
2409
- - [API 参考](./docs/api-reference.md) - API 完整文档
2410
- - [TypeScript 指南](./docs/typescript-guide.md) - TS 用户必读
2411
- - [最佳实践](./docs/best-practices.md) - 避免常见坑
2412
- - [常见问题](./docs/faq.md) - FAQ 合集
2413
- - [故障排查](./docs/troubleshooting.md) - 问题诊断
2414
-
2415
- ### 🎯 功能文档
2416
- - [字符串扩展](./docs/string-extensions.md) - String 扩展方法
2417
- - [SchemaUtils 工具](./docs/schema-utils.md) - Schema 复用工具
2418
- - [条件验证 API](./docs/conditional-api.md) - dsl.if/dsl.match
2419
- - [验证指南](./docs/validation-guide.md) - 高级验证技巧
2420
- - [类型参考](./docs/type-reference.md) - 所有内置类型
2421
- - [枚举类型](./docs/enum.md) - 枚举验证详解
2422
- - [联合类型](./docs/union-types.md) - v1.1.0 新特性
2423
- - [数字运算符](./docs/number-operators.md) - v1.1.2 新特性
2424
- - [错误处理](./docs/error-handling.md) - 错误处理策略
2425
-
2426
- ### 🌍 多语言支持
2427
- - [多语言用户指南](./docs/i18n-user-guide.md) - 完整使用教程
2428
- - [多语言配置详解](./docs/i18n.md) - 配置说明
2429
- - [前端集成指南](./docs/frontend-i18n-guide.md) - 前端使用
2430
- - [添加自定义语言](./docs/add-custom-locale.md) - 扩展新语言
2431
- - [动态语言配置](./docs/dynamic-locale.md) - 动态切换
2432
- - [Label vs Description](./docs/label-vs-description.md) - 最佳实践
2433
-
2434
- ### 🗄️ 数据库导出
2435
- - [导出指南](./docs/export-guide.md) - 完整导出教程
2436
- - [MongoDB 导出器](./docs/mongodb-exporter.md) - MongoDB Schema 导出
2437
- - [MySQL 导出器](./docs/mysql-exporter.md) - MySQL DDL 生成
2438
- - [PostgreSQL 导出器](./docs/postgresql-exporter.md) - PostgreSQL DDL 生成
2439
- - [Markdown 导出器](./docs/markdown-exporter.md) - API 文档生成
2440
- - [⚠️ 导出限制说明](./docs/export-limitations.md) - **必读!了解哪些特性无法导出**
2441
-
2442
- ### 🔌 插件和扩展
2443
- - [插件系统](./docs/plugin-system.md) - 插件开发和使用
2444
- - [插件类型注册](./docs/plugin-type-registration.md) - 自定义类型
2445
- - [自定义扩展指南](./docs/custom-extensions-guide.md) - 添加自定义验证
2446
-
2447
- ### 📊 性能和设计
2448
- - [性能基准测试报告](./docs/performance-benchmark-report.md) - 性能对比数据
2449
- - [设计理念](./docs/design-philosophy.md) - 架构和权衡
2450
- - [缓存管理器](./docs/cache-manager.md) - 缓存配置和优化
2451
-
2452
- ### 💻 示例代码
2453
- - [examples/](./examples/) - 所有示例代码目录
2454
- - [Express 集成](./examples/express-integration.js) - Express 完整示例
2455
- - [中间件使用](./examples/middleware-usage.js) - Koa/Fastify 示例
2456
- - [用户注册](./examples/user-registration/) - 完整注册流程
2457
- - [密码重置](./examples/password-reset/) - 密码重置流程
2458
- - [条件验证](./examples/conditional-example.js) - 条件验证示例
2459
- - [dsl.match 示例](./examples/dsl-match-example.js) - match 用法
2460
- - [多语言完整示例](./examples/i18n-full-demo.js) - i18n 完整演示
2461
- - [I18nError 示例](./examples/i18n-error.examples.js) - 多语言错误
2462
- - [数据库导出](./examples/export-demo.js) - 导出示例
2463
- - [Markdown 导出](./examples/markdown-export.js) - 文档生成
2464
- - [插件系统](./examples/plugin-system.examples.js) - 插件示例
2465
- - [联合类型](./examples/union-type-example.js) - 联合类型示例
2466
- - [Slug 验证](./examples/slug.examples.js) - URL slug 示例
2467
- - [字符串扩展](./examples/string-extensions.js) - String 扩展示例
2468
- - [批量操作](./examples/batch-operations.examples.js) - 批量验证
2469
- - [简单示例](./examples/simple-example.js) - 快速上手
2470
-
2471
- ### 📝 版本和贡献
2472
- - [更新日志](./CHANGELOG.md) - 详细版本历史
2473
- - [贡献指南](./CONTRIBUTING.md) - 如何参与贡献
2474
- - [状态文档](./STATUS.md) - 项目状态和路线图
2475
- - [安全策略](./SECURITY.md) - 安全问题报告
2476
-
2477
- ---
2478
-
2479
- <div align="center">
2480
-
2481
- **⭐ 如果这个项目对你有帮助,请给一个 Star!**
2482
-
2483
- Made with ❤️ by schema-dsl team
2484
-
2485
- </div>
2486
-
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
+