schema-dsl 1.2.5 → 2.0.0

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 (242) hide show
  1. package/CHANGELOG.md +87 -212
  2. package/README.md +391 -2249
  3. package/dist/DslBuilder-DQDN0ZxZ.d.cts +341 -0
  4. package/dist/DslBuilder-DkLaOo9Q.d.ts +341 -0
  5. package/dist/Validator-C7GsVQOH.d.cts +192 -0
  6. package/dist/Validator-hFWKGxir.d.ts +192 -0
  7. package/dist/index.cjs +6594 -0
  8. package/dist/index.d.cts +1145 -0
  9. package/dist/index.d.ts +1145 -0
  10. package/dist/index.js +6528 -0
  11. package/dist/plugin-CIKtTMtS.d.cts +246 -0
  12. package/dist/plugin-CIKtTMtS.d.ts +246 -0
  13. package/dist/plugins/custom-format.cjs +3802 -0
  14. package/dist/plugins/custom-format.d.cts +12 -0
  15. package/dist/plugins/custom-format.d.ts +12 -0
  16. package/dist/plugins/custom-format.js +3772 -0
  17. package/dist/plugins/custom-type-example.cjs +3795 -0
  18. package/dist/plugins/custom-type-example.d.cts +8 -0
  19. package/dist/plugins/custom-type-example.d.ts +8 -0
  20. package/dist/plugins/custom-type-example.js +3765 -0
  21. package/dist/plugins/custom-validator.cjs +146 -0
  22. package/dist/plugins/custom-validator.d.cts +10 -0
  23. package/dist/plugins/custom-validator.d.ts +10 -0
  24. package/dist/plugins/custom-validator.js +121 -0
  25. package/docs/FEATURE-INDEX.md +102 -68
  26. package/docs/add-custom-locale.md +48 -35
  27. package/docs/add-keyword.md +24 -0
  28. package/docs/api-reference.md +396 -154
  29. package/docs/api.md +13 -0
  30. package/docs/best-practices-project-structure.md +19 -10
  31. package/docs/best-practices.md +93 -53
  32. package/docs/cache-manager.md +23 -15
  33. package/docs/compile.md +45 -0
  34. package/docs/conditional-api.md +40 -11
  35. package/docs/custom-extensions-guide.md +80 -152
  36. package/docs/design-philosophy.md +76 -71
  37. package/docs/doc-index.md +324 -0
  38. package/docs/dsl-syntax.md +69 -19
  39. package/docs/dynamic-locale.md +24 -14
  40. package/docs/enum.md +12 -5
  41. package/docs/error-handling.md +53 -44
  42. package/docs/export-guide.md +47 -8
  43. package/docs/export-limitations.md +27 -11
  44. package/docs/faq.md +86 -67
  45. package/docs/frontend-i18n-guide.md +26 -12
  46. package/docs/i18n-user-guide.md +60 -47
  47. package/docs/i18n.md +51 -32
  48. package/docs/index.md +48 -0
  49. package/docs/json-schema-basics.md +40 -0
  50. package/docs/label-vs-description.md +12 -3
  51. package/docs/markdown-exporter.md +15 -6
  52. package/docs/mongodb-exporter.md +11 -4
  53. package/docs/multi-language.md +26 -0
  54. package/docs/multi-type-support.md +26 -33
  55. package/docs/mysql-exporter.md +9 -2
  56. package/docs/number-operators.md +12 -5
  57. package/docs/optional-marker-guide.md +28 -23
  58. package/docs/performance-guide.md +49 -0
  59. package/docs/plugin-system.md +205 -366
  60. package/docs/plugin-type-registration.md +34 -0
  61. package/docs/postgresql-exporter.md +9 -2
  62. package/docs/public/favicon.svg +5 -0
  63. package/docs/quick-start.md +37 -363
  64. package/docs/runtime-locale-support.md +20 -9
  65. package/docs/schema-helper.md +10 -5
  66. package/docs/schema-utils-advanced-issues.md +23 -0
  67. package/docs/schema-utils-best-practices.md +20 -0
  68. package/docs/schema-utils-chaining.md +7 -0
  69. package/docs/schema-utils.md +76 -42
  70. package/docs/security-checklist.md +20 -0
  71. package/docs/string-extensions.md +17 -9
  72. package/docs/troubleshooting.md +36 -21
  73. package/docs/type-converter.md +41 -50
  74. package/docs/type-reference.md +38 -15
  75. package/docs/typescript-guide.md +53 -42
  76. package/docs/union-type-guide.md +11 -1
  77. package/docs/union-types.md +10 -3
  78. package/docs/validate-async.md +36 -25
  79. package/docs/validate-batch.md +49 -0
  80. package/docs/validate-dsl-object-support.md +33 -28
  81. package/docs/validate.md +36 -16
  82. package/docs/validation-guide.md +25 -7
  83. package/docs/validator.md +39 -0
  84. package/package.json +85 -27
  85. package/plugins/custom-format.cjs +8 -0
  86. package/plugins/custom-type-example.cjs +8 -0
  87. package/plugins/custom-validator.cjs +8 -0
  88. package/src/adapters/DslAdapter.ts +111 -0
  89. package/src/adapters/index.ts +1 -0
  90. package/src/config/constants.ts +83 -0
  91. package/src/config/index.ts +2 -0
  92. package/src/config/patterns.ts +77 -0
  93. package/src/core/CacheManager.ts +159 -0
  94. package/src/core/ConditionalBuilder.ts +382 -0
  95. package/src/core/ConditionalRuntime.ts +28 -0
  96. package/src/core/ConditionalValidator.ts +255 -0
  97. package/src/core/DslBuilder.ts +677 -0
  98. package/src/core/ErrorCodes.ts +38 -0
  99. package/src/core/ErrorFormatter.ts +271 -0
  100. package/src/core/JSONSchemaCore.ts +65 -0
  101. package/src/core/Locale.ts +187 -0
  102. package/src/core/MessageTemplate.ts +42 -0
  103. package/src/core/ObjectDslBuilder.ts +64 -0
  104. package/src/core/PluginManager.ts +326 -0
  105. package/src/core/StringExtensions.ts +140 -0
  106. package/src/core/TemplateEngine.ts +44 -0
  107. package/src/core/Validator.ts +448 -0
  108. package/src/errors/I18nError.ts +159 -0
  109. package/src/errors/ValidationError.ts +105 -0
  110. package/src/exporters/BaseExporter.ts +60 -0
  111. package/src/exporters/MarkdownExporter.ts +305 -0
  112. package/src/exporters/MongoDBExporter.ts +126 -0
  113. package/src/exporters/MySQLExporter.ts +155 -0
  114. package/src/exporters/PostgreSQLExporter.ts +222 -0
  115. package/src/exporters/index.ts +18 -0
  116. package/src/index.ts +633 -0
  117. package/{lib/locales/en-US.js → src/locales/en-US.ts} +21 -37
  118. package/{lib/locales/es-ES.js → src/locales/es-ES.ts} +63 -16
  119. package/{lib/locales/fr-FR.js → src/locales/fr-FR.ts} +74 -27
  120. package/src/locales/index.ts +103 -0
  121. package/{lib/locales/ja-JP.js → src/locales/ja-JP.ts} +59 -17
  122. package/src/locales/types.ts +156 -0
  123. package/{lib/locales/zh-CN.js → src/locales/zh-CN.ts} +21 -38
  124. package/src/parser/ConstraintParser.ts +101 -0
  125. package/src/parser/DslParser.ts +470 -0
  126. package/src/parser/SchemaCompiler.ts +66 -0
  127. package/src/parser/TypeRegistry.ts +250 -0
  128. package/src/parser/index.ts +6 -0
  129. package/src/plugins/custom-format.ts +126 -0
  130. package/src/plugins/custom-type-example.ts +108 -0
  131. package/src/plugins/custom-validator.ts +140 -0
  132. package/src/types/conditional.ts +28 -0
  133. package/src/types/config.ts +59 -0
  134. package/src/types/dsl.ts +131 -0
  135. package/src/types/error.ts +60 -0
  136. package/src/types/index.ts +17 -0
  137. package/src/types/infer.ts +128 -0
  138. package/src/types/plugin.ts +58 -0
  139. package/src/types/safe-regex.d.ts +9 -0
  140. package/src/types/schema.ts +66 -0
  141. package/src/types/validate.ts +71 -0
  142. package/src/utils/SchemaHelper.ts +196 -0
  143. package/src/utils/SchemaUtils.ts +346 -0
  144. package/src/utils/TypeConverter.ts +215 -0
  145. package/src/utils/index.ts +10 -0
  146. package/src/validators/CustomKeywords.ts +477 -0
  147. package/.eslintignore +0 -11
  148. package/.eslintrc.json +0 -27
  149. package/CONTRIBUTING.md +0 -368
  150. package/STATUS.md +0 -491
  151. package/changelogs/v1.0.0.md +0 -328
  152. package/changelogs/v1.0.9.md +0 -367
  153. package/changelogs/v1.1.0.md +0 -389
  154. package/changelogs/v1.1.1.md +0 -308
  155. package/changelogs/v1.1.2.md +0 -183
  156. package/changelogs/v1.1.3.md +0 -161
  157. package/changelogs/v1.1.4.md +0 -432
  158. package/changelogs/v1.1.5.md +0 -493
  159. package/changelogs/v1.1.6.md +0 -211
  160. package/changelogs/v1.1.8.md +0 -376
  161. package/changelogs/v1.2.3.md +0 -124
  162. package/docs/INDEX.md +0 -252
  163. package/docs/issues-resolved-summary.md +0 -196
  164. package/docs/performance-benchmark-report.md +0 -179
  165. package/docs/performance-quick-reference.md +0 -123
  166. package/docs/user-questions-answered.md +0 -353
  167. package/docs/validation-rules-v1.0.2.md +0 -1608
  168. package/examples/README.md +0 -81
  169. package/examples/array-dsl-example.js +0 -227
  170. package/examples/conditional-example.js +0 -288
  171. package/examples/conditional-non-object.js +0 -129
  172. package/examples/conditional-validate-example.js +0 -321
  173. package/examples/custom-extension.js +0 -85
  174. package/examples/dsl-match-example.js +0 -74
  175. package/examples/dsl-style.js +0 -118
  176. package/examples/dynamic-locale-configuration.js +0 -348
  177. package/examples/dynamic-locale-example.js +0 -287
  178. package/examples/enum.examples.js +0 -324
  179. package/examples/export-demo.js +0 -130
  180. package/examples/express-integration.js +0 -376
  181. package/examples/i18n-error-handling-complete.js +0 -381
  182. package/examples/i18n-error-handling-quickstart.md +0 -0
  183. package/examples/i18n-error.examples.js +0 -181
  184. package/examples/i18n-full-demo.js +0 -301
  185. package/examples/i18n-memory-safety.examples.js +0 -268
  186. package/examples/markdown-export.js +0 -71
  187. package/examples/middleware-usage.js +0 -93
  188. package/examples/new-features-comparison.js +0 -315
  189. package/examples/password-reset/README.md +0 -153
  190. package/examples/password-reset/schema.js +0 -26
  191. package/examples/password-reset/test.js +0 -101
  192. package/examples/plugin-system.examples.js +0 -205
  193. package/examples/schema-utils-chaining.examples.js +0 -250
  194. package/examples/simple-example.js +0 -122
  195. package/examples/slug.examples.js +0 -179
  196. package/examples/string-extensions.js +0 -297
  197. package/examples/union-type-example.js +0 -127
  198. package/examples/union-types-example.js +0 -77
  199. package/examples/user-registration/README.md +0 -156
  200. package/examples/user-registration/routes.js +0 -92
  201. package/examples/user-registration/schema.js +0 -150
  202. package/examples/user-registration/server.js +0 -74
  203. package/index.d.ts +0 -3658
  204. package/index.js +0 -475
  205. package/index.mjs +0 -60
  206. package/lib/adapters/DslAdapter.js +0 -995
  207. package/lib/adapters/index.js +0 -20
  208. package/lib/config/constants.js +0 -286
  209. package/lib/config/patterns/common.js +0 -47
  210. package/lib/config/patterns/creditCard.js +0 -9
  211. package/lib/config/patterns/idCard.js +0 -9
  212. package/lib/config/patterns/index.js +0 -9
  213. package/lib/config/patterns/licensePlate.js +0 -4
  214. package/lib/config/patterns/passport.js +0 -4
  215. package/lib/config/patterns/phone.js +0 -9
  216. package/lib/config/patterns/postalCode.js +0 -5
  217. package/lib/core/CacheManager.js +0 -376
  218. package/lib/core/ConditionalBuilder.js +0 -503
  219. package/lib/core/DslBuilder.js +0 -1589
  220. package/lib/core/ErrorCodes.js +0 -233
  221. package/lib/core/ErrorFormatter.js +0 -445
  222. package/lib/core/JSONSchemaCore.js +0 -347
  223. package/lib/core/Locale.js +0 -130
  224. package/lib/core/MessageTemplate.js +0 -98
  225. package/lib/core/PluginManager.js +0 -448
  226. package/lib/core/StringExtensions.js +0 -240
  227. package/lib/core/Validator.js +0 -654
  228. package/lib/errors/I18nError.js +0 -328
  229. package/lib/errors/ValidationError.js +0 -191
  230. package/lib/exporters/MarkdownExporter.js +0 -420
  231. package/lib/exporters/MongoDBExporter.js +0 -162
  232. package/lib/exporters/MySQLExporter.js +0 -212
  233. package/lib/exporters/PostgreSQLExporter.js +0 -289
  234. package/lib/exporters/index.js +0 -24
  235. package/lib/locales/index.js +0 -8
  236. package/lib/utils/LRUCache.js +0 -174
  237. package/lib/utils/SchemaHelper.js +0 -240
  238. package/lib/utils/SchemaUtils.js +0 -445
  239. package/lib/utils/TypeConverter.js +0 -245
  240. package/lib/utils/index.js +0 -13
  241. package/lib/validators/CustomKeywords.js +0 -616
  242. package/lib/validators/index.js +0 -11
package/README.md CHANGED
@@ -1,314 +1,92 @@
1
- <div align="center">
1
+ <div align="center">
2
2
 
3
3
  # 🎯 schema-dsl
4
4
 
5
- **最简洁的数据验证库 - 代码量减少 65%**
6
-
7
- 一行 DSL 替代 10 行链式调用
5
+ **Declare field rules with the simplest DSL — let one schema drive validation, derivation, export, and documentation.**
8
6
 
9
7
  [![npm version](https://img.shields.io/npm/v/schema-dsl.svg?style=flat-square)](https://www.npmjs.com/package/schema-dsl)
10
8
  [![npm downloads](https://img.shields.io/npm/dm/schema-dsl.svg?style=flat-square)](https://www.npmjs.com/package/schema-dsl)
11
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/)
12
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
13
12
 
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);
13
+ [Quick Start](#-quick-start) · [Documentation](https://vextjs.github.io/schema-dsl) · [Feature Overview](#-feature-overview) · [Examples](./examples)
98
14
 
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');
15
+ ```bash
16
+ npm install schema-dsl
106
17
  ```
107
18
 
108
- **核心优势**:
109
- - 🎯 **参数更少**: 无需参数对象时从4个参数减少到2个
110
- - 🎯 **智能识别**: 自动判断第2个参数是语言还是参数对象
111
- - 🎯 **完全兼容**: 现有代码无需修改,渐进式增强
112
- - 🎯 **降低错误**: 不再需要传递空对象 `{}`
113
-
114
- 📖 [完整文档](./docs/error-handling.md) · [实现原理](./docs/i18n-implementation-analysis.md) · [变更日志](./changelogs/v1.1.8.md)
19
+ </div>
115
20
 
116
21
  ---
117
22
 
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)
23
+ ## TL;DR (30-second intro)
174
24
 
175
- ---
25
+ **What is schema-dsl?**
176
26
 
177
- ### 🔗 跨类型联合验证(v1.1.0)
27
+ Write field rules like this:
178
28
 
179
- **一行代码支持多种类型,告别繁琐的类型判断**
29
+ ```typescript
30
+ import { dsl, validate } from 'schema-dsl';
180
31
 
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' // 枚举或空值
32
+ const userSchema = dsl({
33
+ username: 'string:3-32!',
34
+ email: 'email!',
35
+ role: 'admin|user|guest',
36
+ contact: 'types:email|phone'
186
37
  });
187
38
 
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
- });
39
+ const result = validate(userSchema, req.body);
219
40
  ```
220
41
 
221
- **适用场景**:
222
- - ✅ 多语言 API(根据请求头动态返回)
223
- - ✅ 微服务架构(错误传递保持原语言)
224
- - ✅ 国际化应用(同一请求多种语言)
225
-
226
- 📖 [运行时多语言文档](./docs/runtime-locale-support.md)
42
+ Then that **same set of rules** continues to power:
227
43
 
228
- ---
229
-
230
- ### 其他新特性
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
231
50
 
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)
51
+ **5-minute tutorial**: [Quick Start](https://vextjs.github.io/schema-dsl/quick-start) | **Full docs**: [Online Documentation](https://vextjs.github.io/schema-dsl)
238
52
 
239
53
  ---
240
54
 
241
- ## 📦 功能清单(AI友好格式)
242
-
243
- > 方便AI快速理解所有功能
55
+ ## 🗺️ Documentation
244
56
 
245
- ### 核心功能
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
246
61
 
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
- ```
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
284
68
 
285
- ### API速查
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
286
73
 
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) |
74
+ **Full docs**: [Online Documentation](https://vextjs.github.io/schema-dsl) · [Feature Index](https://vextjs.github.io/schema-dsl/FEATURE-INDEX)
295
75
 
296
76
  ---
297
77
 
298
- ## ✨ 为什么选择 schema-dsl
299
-
300
- ### 🎯 极简 DSL 语法
78
+ ## ✨ Why schema-dsl?
301
79
 
302
- **3 行代码完成验证规则定义**
80
+ ### 🎯 Minimal DSL — 65% less code
303
81
 
304
82
  <table>
305
83
  <tr>
306
84
  <td width="50%" valign="top">
307
85
 
308
- **❌ 传统写法** - 繁琐冗长
86
+ **❌ Traditional approach** — verbose
309
87
 
310
88
  ```javascript
311
- // Joi - 需要 8
89
+ // Joi requires 8 lines
312
90
  const schema = Joi.object({
313
91
  username: Joi.string()
314
92
  .min(3).max(32).required(),
@@ -322,14 +100,14 @@ const schema = Joi.object({
322
100
  </td>
323
101
  <td width="50%" valign="top">
324
102
 
325
- **✅ schema-dsl** - 简洁优雅
103
+ **✅ schema-dsl** concise and clean
326
104
 
327
- ```javascript
328
- // 只需 3 行!
105
+ ```typescript
106
+ // just 3 lines
329
107
  const schema = dsl({
330
108
  username: 'string:3-32!',
331
- email: 'email!',
332
- age: 'number:18-120'
109
+ email: 'email!',
110
+ age: 'number:18-120'
333
111
  });
334
112
  ```
335
113
 
@@ -337,2150 +115,514 @@ const schema = dsl({
337
115
  </tr>
338
116
  </table>
339
117
 
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 |
118
+ ### 💪 Full-featured
509
119
 
510
- ### 🆕 v1.1.0 新特性:跨类型联合验证
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 |
511
132
 
512
- **一行代码支持多种类型**
133
+ ### 🎨 One schema, many uses (unique capability)
513
134
 
514
- ```javascript
515
- const { dsl, validate } = require('schema-dsl');
135
+ ```typescript
136
+ import { dsl, exporters, SchemaUtils } from 'schema-dsl';
516
137
 
517
- // 字段可以是字符串或数字
518
- const schema = dsl({
519
- value: 'types:string|number'
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!'
520
145
  });
521
146
 
522
- validate(schema, { value: 'hello' }); // ✅ 通过
523
- validate(schema, { value: 123 }); // ✅ 通过
524
- validate(schema, { value: true }); // ❌ 失败
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']);
525
151
 
526
- // 带约束的联合类型
527
- const advancedSchema = dsl({
528
- contact: 'types:email|phone!', // 邮箱或手机号
529
- price: 'types:number:0-|string:1-20' // 数字价格或"面议"
530
- });
531
- ```
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);
532
156
 
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
- });
157
+ // 📝 generate field documentation from the same schema
158
+ const markdown = exporters.MarkdownExporter.export(userSchema, { title: 'User Field Reference' });
541
159
  ```
542
160
 
543
- 📖 [完整文档](./docs/union-types.md) | [插件开发指南](./docs/plugin-type-registration.md)
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.
544
162
 
545
163
  ---
546
164
 
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
- ## �📦 安装
165
+ ## 📦 Installation
594
166
 
595
167
  ```bash
596
168
  npm install schema-dsl
597
169
  ```
598
170
 
171
+ **Runtime requirement**: Node.js >= 18.0.0
172
+
599
173
  ---
600
174
 
601
- ## 🚀 快速开始
175
+ ## 🚀 Quick Start
602
176
 
603
- ### 1. 基础验证(JavaScript)
177
+ ### 1. Basic validation
604
178
 
605
- ```javascript
606
- const { dsl, validate } = require('schema-dsl');
179
+ ```typescript
180
+ import { dsl, validate } from 'schema-dsl';
607
181
 
608
182
  const userSchema = dsl({
609
183
  username: 'string:3-32!',
610
- email: 'email!',
611
- age: 'number:18-120',
612
- tags: 'array<string>'
184
+ email: 'email!',
185
+ age: 'number:18-120',
186
+ role: 'admin|user|guest',
187
+ tags: 'array<string>'
613
188
  });
614
189
 
615
- // ✅ 验证成功
616
- const result1 = validate(userSchema, {
190
+ // ✅ validation passed
191
+ const result = validate(userSchema, {
617
192
  username: 'john_doe',
618
- email: 'john@example.com',
619
- age: 25,
620
- tags: ['admin', 'verified']
193
+ email: 'john@example.com',
194
+ age: 25,
195
+ role: 'user',
196
+ tags: ['verified']
621
197
  });
622
198
 
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
- });
199
+ console.log(result.valid); // true
200
+ console.log(result.data); // validated data
632
201
 
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
- */
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
+ // ]
642
209
  ```
643
210
 
644
- ### 1.5 TypeScript 用法
645
-
646
- **重要**: TypeScript 中**必须**使用 `dsl()` 包裹字符串以获得类型提示(v1.0.6+ 移除了全局 String 类型扩展以避免类型污染):
211
+ ### 2. Async validation + Express integration
647
212
 
648
213
  ```typescript
649
214
  import { dsl, validateAsync, ValidationError } from 'schema-dsl';
650
215
 
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
216
  const createUserSchema = dsl({
699
217
  username: 'string:3-32!',
700
- email: 'email!',
218
+ email: 'email!',
701
219
  password: 'string:8-32!'
702
220
  });
703
221
 
704
- // 在路由中使用
705
222
  app.post('/api/users', async (req, res, next) => {
706
223
  try {
707
- // validateAsync 验证失败时会抛出 ValidationError
224
+ // throws ValidationError automatically on failure
708
225
  const validData = await validateAsync(createUserSchema, req.body);
709
-
710
226
  const user = await db.users.create(validData);
711
227
  res.json({ success: true, data: user });
712
228
  } catch (error) {
713
- // ValidationError 会被全局错误处理器捕获
714
229
  next(error);
715
230
  }
716
231
  });
717
232
 
718
- // 全局错误处理 - 区分验证错误和其他错误
233
+ // global error handler
719
234
  app.use((error, req, res, next) => {
720
235
  if (error instanceof ValidationError) {
721
- // 验证错误返回 400
722
- return res.status(400).json({
723
- success: false,
724
- message: 'Validation failed',
725
- errors: error.errors // 详细的字段错误列表
726
- });
236
+ return res.status(400).json({ success: false, errors: error.errors });
727
237
  }
728
-
729
- // 其他错误继续传递
730
238
  next(error);
731
239
  });
732
240
  ```
733
241
 
734
- ### Schema 复用 - 按场景使用
242
+ ### 3. Schema reuse (create / update / public)
735
243
 
736
- ```javascript
737
- const { dsl, SchemaUtils } = require('schema-dsl');
244
+ ```typescript
245
+ import { dsl, SchemaUtils } from 'schema-dsl';
738
246
 
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!'
247
+ const userSchema = dsl({
248
+ id: 'uuid!',
249
+ username: 'string:3-32!',
250
+ email: 'email!',
251
+ password: 'string:8-64!',
252
+ createdAt: 'string!'
749
253
  });
750
254
 
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
- ```
255
+ // create endpoint: remove server-generated fields
256
+ const createSchema = SchemaUtils.omit(userSchema, ['id', 'createdAt']);
780
257
 
781
- ### 条件验证 - 一行代码搞定
258
+ // update endpoint: pick editable fields, all optional
259
+ const updateSchema = SchemaUtils.partial(
260
+ SchemaUtils.pick(userSchema, ['username', 'email'])
261
+ );
782
262
 
783
- **问题场景**:不同情况需要不同的验证规则
263
+ // public response: hide sensitive fields
264
+ const publicSchema = SchemaUtils.omit(userSchema, ['password']);
265
+ ```
784
266
 
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
- }
267
+ ### 4. Database schema export
795
268
 
796
- // ✅ 新做法:一行代码搞定
797
- dsl.if(d => d.age < 18)
798
- .message('未成年用户不能注册')
799
- .assert(userData); // 失败自动抛错
269
+ ```typescript
270
+ import { dsl, exporters } from 'schema-dsl';
800
271
 
801
- // 场景2:权限检查 - 快速判断
802
- // ❌ 传统做法:写 if 判断
803
- if (user.role !== 'admin' && user.role !== 'moderator') {
804
- return res.status(403).json({ error: '权限不足' });
805
- }
272
+ const productSchema = dsl({
273
+ name: 'string:1-100!',
274
+ price: 'number:>0!',
275
+ stock: 'integer:0-!',
276
+ category: 'string!',
277
+ createdAt: 'datetime!'
278
+ });
806
279
 
807
- // 新做法:一行搞定
808
- if (!dsl.if(d => d.role === 'admin' || d.role === 'moderator')
809
- .message('权限不足')
810
- .check(user)) {
811
- return res.status(403).json({ error: '权限不足' });
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
+ }
812
295
  }
296
+ */
813
297
 
814
- // 场景3:批量过滤 - 筛选符合条件的数据
815
- // 传统做法:写 filter 函数
816
- const adults = users.filter(u => u.age >= 18);
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
+ */
817
309
 
818
- // 新做法:语义更清晰
819
- const adults = users.filter(u =>
820
- !dsl.if(d => d.age < 18).message('未成年').check(u)
821
- );
310
+ // Markdown field documentation
311
+ const markdown = exporters.MarkdownExporter.export(productSchema, { title: 'Product Field Reference' });
822
312
  ```
823
313
 
824
- #### 四种方法,满足不同场景
314
+ ---
825
315
 
826
- | 方法 | 什么时候用 | 返回什么 | 示例 |
827
- |------|-----------|---------|------|
828
- | **`.validate()`** | 需要知道错误详情 | `{ valid, errors, data }` | 表单验证 |
829
- | **`.validateAsync()`** | async/await 场景 | Promise(失败抛错) | Express 中间件 |
830
- | **`.assert()`** | 快速失败,不想写 if | 失败直接抛错 | 函数入口检查 |
831
- | **`.check()`** | 只需要判断真假 | `true/false` | 数据过滤 |
316
+ ## 🗒️ Feature Overview
832
317
 
833
- #### 实际例子
318
+ ### Common use cases
834
319
 
835
- **表单验证 - 需要显示错误**
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) |
836
331
 
837
- ```javascript
838
- // 使用 .validate() 获取错误详情
839
- const result = dsl.if(d => d.age < 18)
840
- .message('未成年用户不能注册')
841
- .validate(formData);
332
+ ---
842
333
 
843
- if (!result.valid) {
844
- showError(result.errors[0].message); // 显示给用户
845
- }
846
- ```
334
+ ## 📖 DSL Syntax Reference
847
335
 
848
- **Express 中间件 - 异步验证**
336
+ ### Basic types
849
337
 
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
- });
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
+ })
865
367
  ```
866
368
 
867
- **函数参数检查 - 快速断言**
369
+ ### Built-in formats
868
370
 
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
- }
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
+ })
880
382
  ```
881
383
 
882
- **批量数据处理 - 快速过滤**
384
+ ### Fluent chain API (recommended for TypeScript)
883
385
 
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
- ```
386
+ ```typescript
387
+ import { dsl } from 'schema-dsl';
898
388
 
899
- **复用验证器**
389
+ const schema = dsl({
390
+ username: dsl('string:3-32!')
391
+ .username()
392
+ .label('username')
393
+ .messages({ required: 'Username is required' }),
900
394
 
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 }); // 快速判断
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
+ });
910
401
  ```
911
402
 
912
- #### 💡 选择建议
403
+ ### Conditional validation
913
404
 
914
- - 🎯 **表单验证**:用 `.validate()` - 需要显示错误给用户
915
- - 🚀 **API 接口**:用 `.validateAsync()` - 配合 try/catch
916
- - **函数入口**:用 `.assert()` - 快速失败,代码简洁
917
- - 🔍 **数据过滤**:用 `.check()` - 只需要判断真假
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
+ });
918
415
 
919
- **完整文档**: [ConditionalBuilder API](./docs/conditional-api.md)
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
+ });
920
421
 
921
- ---
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
+ ```
922
429
 
923
- ## � 进阶功能
430
+ ---
924
431
 
925
- ### 批量验证
432
+ ## 🌍 Internationalization
926
433
 
927
- **场景**: 验证 1000 条用户数据,性能提升 50 倍
434
+ ```typescript
435
+ import { dsl, validate, Locale, I18nError } from 'schema-dsl';
928
436
 
929
- ```javascript
930
- const { dsl, SchemaUtils, Validator } = require('schema-dsl');
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
931
440
 
932
- const userSchema = dsl({
933
- username: 'string:3-32!',
934
- email: 'email!',
935
- age: 'number:18-120'
441
+ // register a custom locale
442
+ Locale.addLocale('zh-CN', {
443
+ 'user.notFound': 'User not found',
444
+ 'user.forbidden': { code: 40003, message: 'Access forbidden' },
936
445
  });
937
446
 
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);
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
948
451
 
949
- console.log(result.summary);
950
- /*
951
- {
952
- total: 3,
953
- valid: 2,
954
- invalid: 1,
955
- duration: 5 // 毫秒
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
+ }
956
459
  }
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#嵌套对象验证)
460
+ ```
1049
461
 
1050
462
  ---
1051
463
 
1052
- ### 数组高级验证
1053
-
1054
- **场景**: 验证订单商品列表
464
+ ## 🔌 Plugin System
1055
465
 
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');
466
+ ```typescript
467
+ import { PluginManager, Validator, dsl } from 'schema-dsl';
1842
468
 
1843
469
  const pluginManager = new PluginManager();
1844
470
 
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
- // 注册自定义格式插件
471
+ // register a custom format plugin (must provide an install function)
1868
472
  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-!'
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)
2033
482
  });
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
483
  }
2042
484
  });
2043
- ```
2044
-
2045
- ---
2046
485
 
2047
- ### Q5: 如何自定义错误消息?
486
+ // create a Validator and install plugins
487
+ const validator = new Validator();
488
+ pluginManager.install(validator);
2048
489
 
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
- });
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' });
2065
493
  ```
2066
494
 
2067
495
  ---
2068
496
 
2069
- ### Q6: 类型对照表
497
+ ## 🔧 Core API Reference
2070
498
 
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-!'` |
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) |
2081
511
 
2082
512
  ---
2083
513
 
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');
514
+ ## 📝 TypeScript Usage
2167
515
 
2168
- // 方式1:直接抛出
2169
- I18nError.throw('account.notFound');
2170
- // 中文: "账户不存在"
2171
- // 英文: "Account not found"
516
+ ```typescript
517
+ import { dsl, validateAsync, ValidationError } from 'schema-dsl';
2172
518
 
2173
- // 方式2:带参数插值
2174
- I18nError.throw('account.insufficientBalance', {
2175
- balance: 50,
2176
- required: 100
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')
2177
524
  });
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
525
 
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
526
  try {
2236
- dsl.error.throw('account.notFound');
527
+ const validData = await validateAsync(userSchema, payload);
528
+ // validData has full type inference
2237
529
  } 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;
530
+ if (error instanceof ValidationError) {
531
+ error.errors.forEach(e => console.log(`${e.path}: ${e.message}`));
2261
532
  }
2262
533
  }
2263
534
  ```
2264
535
 
2265
- **优势**:
2266
- - 多语言共享相同的数字 `code`,前端统一处理
2267
- - ✅ 完全向后兼容,字符串格式自动转换
2268
- - ✅ `originalKey` 便于调试和日志追踪
2269
- - ✅ 数字 code 更简洁,易于管理和文档化
2270
-
2271
- **错误码规范建议**:
2272
- - `4xxxx` - 客户端错误(账户、权限、参数等)
2273
- - `5xxxx` - 业务逻辑错误(订单、支付、库存等)
2274
- - `6xxxx` - 系统错误(数据库、服务不可用等)
2275
-
2276
- 📖 详细说明: [错误处理文档](./docs/error-handling.md#v115-新功能对象格式错误配置)
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.
2277
538
 
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
- });
539
+ ---
2303
540
 
2304
- // 同一请求中使用不同语言
2305
- const error1 = dsl.error.create('account.notFound', {}, 404, 'zh-CN');
2306
- console.log(error1.message); // "账户不存在"
541
+ ## 🛠️ Development
2307
542
 
2308
- const error2 = dsl.error.create('account.notFound', {}, 404, 'en-US');
2309
- console.log(error2.message); // "Account not found"
543
+ ```bash
544
+ npm run build # compile TypeScript
545
+ npm run test # run tests
546
+ npm run typecheck # type check
2310
547
  ```
2311
548
 
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
- });
549
+ Local documentation preview:
2321
550
 
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
- });
551
+ ```bash
552
+ cd website
553
+ npm run dev
2333
554
  ```
2334
555
 
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
556
  ---
2345
557
 
2346
- ## 🤝 贡献指南
2347
-
2348
- 欢迎贡献代码、报告问题或提出建议!
2349
-
2350
- ### 开发环境
558
+ ## 🤝 Contributing
2351
559
 
2352
560
  ```bash
2353
- # 克隆仓库
2354
561
  git clone https://github.com/vextjs/schema-dsl.git
2355
562
  cd schema-dsl
2356
-
2357
- # 安装依赖
2358
563
  npm install
2359
-
2360
- # 运行测试
2361
564
  npm test
2362
-
2363
- # 代码检查
2364
- npm run lint
2365
-
2366
- # 查看测试覆盖率
2367
- npm run coverage
2368
565
  ```
2369
566
 
2370
- ### 提交规范
2371
-
2372
- - 🐛 **Bug 修复**: `fix: 修复XXX问题`
2373
- - ✨ **新功能**: `feat: 添加XXX功能`
2374
- - 📝 **文档**: `docs: 更新XXX文档`
2375
- - 🎨 **代码格式**: `style: 格式化代码`
2376
- - ♻️ **重构**: `refactor: 重构XXX模块`
2377
- - ✅ **测试**: `test: 添加XXX测试`
2378
-
2379
- 详见 [贡献指南](./CONTRIBUTING.md)
567
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
2380
568
 
2381
569
  ---
2382
570
 
2383
- ## 📄 开源协议
571
+ ## 🔗 Links
2384
572
 
2385
- [MIT License](./LICENSE)
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
2386
581
 
2387
- ---
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)
2388
597
 
2389
- ## 🙏 致谢
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
2390
604
 
2391
- - 感谢 [ajv](https://github.com/ajv-validator/ajv) 提供强大的验证引擎
2392
- - 感谢所有贡献者和用户的支持
605
+ ### 📝 Changelog & contributing
606
+ - [Changelog](./CHANGELOG.md)
607
+ - [Contributing Guide](./CONTRIBUTING.md)
608
+ - [Security Policy](./SECURITY.md)
2393
609
 
2394
610
  ---
2395
611
 
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) - 安全问题报告
612
+ ## 📄 License
613
+
614
+ [MIT](./LICENSE)
2476
615
 
2477
616
  ---
2478
617
 
2479
618
  <div align="center">
2480
619
 
2481
- **⭐ 如果这个项目对你有帮助,请给一个 Star!**
620
+ If this project is useful to you, please consider giving it a Star
2482
621
 
2483
- Made with ❤️ by schema-dsl team
622
+ Made with ❤️ by the schema-dsl team
2484
623
 
2485
624
  </div>
2486
625
 
626
+
627
+
628
+