schema-dsl 1.2.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/CHANGELOG.md +130 -238
  2. package/LICENSE +21 -21
  3. package/README.md +628 -2486
  4. package/dist/DslBuilder-BIgQOAXp.d.ts +343 -0
  5. package/dist/DslBuilder-CjHTucNQ.d.cts +343 -0
  6. package/dist/Validator-CllRdrY0.d.ts +192 -0
  7. package/dist/Validator-D6okG9tr.d.cts +192 -0
  8. package/dist/index.cjs +6640 -0
  9. package/dist/index.d.cts +1151 -0
  10. package/dist/index.d.ts +1151 -0
  11. package/dist/index.js +6574 -0
  12. package/dist/plugin-CIKtTMtS.d.cts +246 -0
  13. package/dist/plugin-CIKtTMtS.d.ts +246 -0
  14. package/dist/plugins/custom-format.cjs +3818 -0
  15. package/dist/plugins/custom-format.d.cts +12 -0
  16. package/dist/plugins/custom-format.d.ts +12 -0
  17. package/dist/plugins/custom-format.js +3788 -0
  18. package/dist/plugins/custom-type-example.cjs +3811 -0
  19. package/dist/plugins/custom-type-example.d.cts +8 -0
  20. package/dist/plugins/custom-type-example.d.ts +8 -0
  21. package/dist/plugins/custom-type-example.js +3781 -0
  22. package/dist/plugins/custom-validator.cjs +144 -0
  23. package/dist/plugins/custom-validator.d.cts +10 -0
  24. package/dist/plugins/custom-validator.d.ts +10 -0
  25. package/dist/plugins/custom-validator.js +119 -0
  26. package/docs/FEATURE-INDEX.md +553 -519
  27. package/docs/add-custom-locale.md +496 -483
  28. package/docs/add-keyword.md +24 -0
  29. package/docs/api-reference.md +1047 -805
  30. package/docs/api.md +13 -0
  31. package/docs/best-practices-project-structure.md +417 -408
  32. package/docs/best-practices.md +712 -672
  33. package/docs/cache-manager.md +344 -336
  34. package/docs/compile.md +45 -0
  35. package/docs/conditional-api.md +1307 -1278
  36. package/docs/custom-extensions-guide.md +339 -411
  37. package/docs/design-philosophy.md +606 -601
  38. package/docs/doc-index.md +324 -0
  39. package/docs/dsl-syntax.md +714 -664
  40. package/docs/dynamic-locale.md +608 -598
  41. package/docs/enum.md +482 -475
  42. package/docs/error-handling.md +1975 -1966
  43. package/docs/export-guide.md +501 -462
  44. package/docs/export-limitations.md +567 -551
  45. package/docs/faq.md +596 -577
  46. package/docs/frontend-i18n-guide.md +307 -293
  47. package/docs/i18n-user-guide.md +487 -474
  48. package/docs/i18n.md +476 -457
  49. package/docs/index.md +48 -0
  50. package/docs/json-schema-basics.md +40 -0
  51. package/docs/label-vs-description.md +271 -262
  52. package/docs/markdown-exporter.md +406 -397
  53. package/docs/mongodb-exporter.md +302 -295
  54. package/docs/multi-language.md +26 -0
  55. package/docs/multi-type-support.md +322 -329
  56. package/docs/mysql-exporter.md +280 -273
  57. package/docs/number-operators.md +449 -442
  58. package/docs/optional-marker-guide.md +326 -321
  59. package/docs/performance-guide.md +49 -0
  60. package/docs/plugin-system.md +381 -542
  61. package/docs/plugin-type-registration.md +34 -0
  62. package/docs/postgresql-exporter.md +311 -304
  63. package/docs/public/favicon.svg +5 -0
  64. package/docs/quick-start.md +435 -761
  65. package/docs/runtime-locale-support.md +532 -521
  66. package/docs/schema-helper.md +345 -340
  67. package/docs/schema-utils-advanced-issues.md +23 -0
  68. package/docs/schema-utils-best-practices.md +20 -0
  69. package/docs/schema-utils-chaining.md +150 -143
  70. package/docs/schema-utils.md +524 -490
  71. package/docs/security-checklist.md +20 -0
  72. package/docs/string-extensions.md +488 -480
  73. package/docs/troubleshooting.md +486 -471
  74. package/docs/type-converter.md +310 -319
  75. package/docs/type-reference.md +242 -219
  76. package/docs/typescript-guide.md +584 -573
  77. package/docs/union-type-guide.md +157 -147
  78. package/docs/union-types.md +284 -277
  79. package/docs/validate-async.md +491 -480
  80. package/docs/validate-batch.md +49 -0
  81. package/docs/validate-dsl-object-support.md +578 -573
  82. package/docs/validate.md +506 -486
  83. package/docs/validation-guide.md +502 -484
  84. package/docs/validator.md +39 -0
  85. package/package.json +131 -73
  86. package/plugins/custom-format.cjs +8 -0
  87. package/plugins/custom-type-example.cjs +8 -0
  88. package/plugins/custom-validator.cjs +8 -0
  89. package/src/adapters/DslAdapter.ts +111 -0
  90. package/src/adapters/index.ts +1 -0
  91. package/src/config/constants.ts +83 -0
  92. package/src/config/index.ts +2 -0
  93. package/src/config/patterns.ts +77 -0
  94. package/src/core/CacheManager.ts +169 -0
  95. package/src/core/ConditionalBuilder.ts +382 -0
  96. package/src/core/ConditionalRuntime.ts +28 -0
  97. package/src/core/ConditionalValidator.ts +255 -0
  98. package/src/core/DslBuilder.ts +687 -0
  99. package/src/core/ErrorCodes.ts +38 -0
  100. package/src/core/ErrorFormatter.ts +271 -0
  101. package/src/core/JSONSchemaCore.ts +65 -0
  102. package/src/core/Locale.ts +187 -0
  103. package/src/core/MessageTemplate.ts +42 -0
  104. package/src/core/ObjectDslBuilder.ts +64 -0
  105. package/src/core/PluginManager.ts +326 -0
  106. package/src/core/StringExtensions.ts +140 -0
  107. package/src/core/TemplateEngine.ts +44 -0
  108. package/src/core/Validator.ts +448 -0
  109. package/src/errors/I18nError.ts +159 -0
  110. package/src/errors/ValidationError.ts +105 -0
  111. package/src/exporters/BaseExporter.ts +60 -0
  112. package/src/exporters/MarkdownExporter.ts +305 -0
  113. package/src/exporters/MongoDBExporter.ts +126 -0
  114. package/src/exporters/MySQLExporter.ts +156 -0
  115. package/src/exporters/PostgreSQLExporter.ts +222 -0
  116. package/src/exporters/index.ts +18 -0
  117. package/src/index.ts +651 -0
  118. package/{lib/locales/en-US.js → src/locales/en-US.ts} +160 -176
  119. package/{lib/locales/es-ES.js → src/locales/es-ES.ts} +160 -113
  120. package/{lib/locales/fr-FR.js → src/locales/fr-FR.ts} +160 -113
  121. package/src/locales/index.ts +103 -0
  122. package/{lib/locales/ja-JP.js → src/locales/ja-JP.ts} +160 -118
  123. package/src/locales/types.ts +156 -0
  124. package/{lib/locales/zh-CN.js → src/locales/zh-CN.ts} +160 -177
  125. package/src/parser/ConstraintParser.ts +101 -0
  126. package/src/parser/DslParser.ts +470 -0
  127. package/src/parser/SchemaCompiler.ts +66 -0
  128. package/src/parser/TypeRegistry.ts +250 -0
  129. package/src/parser/index.ts +6 -0
  130. package/src/plugins/custom-format.ts +124 -0
  131. package/src/plugins/custom-type-example.ts +106 -0
  132. package/src/plugins/custom-validator.ts +138 -0
  133. package/src/types/conditional.ts +28 -0
  134. package/src/types/config.ts +59 -0
  135. package/src/types/dsl.ts +131 -0
  136. package/src/types/error.ts +60 -0
  137. package/src/types/index.ts +17 -0
  138. package/src/types/infer.ts +128 -0
  139. package/src/types/plugin.ts +58 -0
  140. package/src/types/safe-regex.d.ts +9 -0
  141. package/src/types/schema.ts +66 -0
  142. package/src/types/validate.ts +71 -0
  143. package/src/utils/SchemaHelper.ts +196 -0
  144. package/src/utils/SchemaUtils.ts +365 -0
  145. package/src/utils/TypeConverter.ts +215 -0
  146. package/src/utils/index.ts +10 -0
  147. package/src/validators/CustomKeywords.ts +477 -0
  148. package/.eslintignore +0 -11
  149. package/.eslintrc.json +0 -27
  150. package/CONTRIBUTING.md +0 -368
  151. package/STATUS.md +0 -491
  152. package/changelogs/v1.0.0.md +0 -328
  153. package/changelogs/v1.0.9.md +0 -367
  154. package/changelogs/v1.1.0.md +0 -389
  155. package/changelogs/v1.1.1.md +0 -308
  156. package/changelogs/v1.1.2.md +0 -183
  157. package/changelogs/v1.1.3.md +0 -161
  158. package/changelogs/v1.1.4.md +0 -432
  159. package/changelogs/v1.1.5.md +0 -493
  160. package/changelogs/v1.1.6.md +0 -211
  161. package/changelogs/v1.1.8.md +0 -376
  162. package/changelogs/v1.2.3.md +0 -124
  163. package/docs/INDEX.md +0 -252
  164. package/docs/issues-resolved-summary.md +0 -196
  165. package/docs/performance-benchmark-report.md +0 -179
  166. package/docs/performance-quick-reference.md +0 -123
  167. package/docs/user-questions-answered.md +0 -353
  168. package/docs/validation-rules-v1.0.2.md +0 -1608
  169. package/examples/README.md +0 -81
  170. package/examples/array-dsl-example.js +0 -227
  171. package/examples/conditional-example.js +0 -288
  172. package/examples/conditional-non-object.js +0 -129
  173. package/examples/conditional-validate-example.js +0 -321
  174. package/examples/custom-extension.js +0 -85
  175. package/examples/dsl-match-example.js +0 -74
  176. package/examples/dsl-style.js +0 -118
  177. package/examples/dynamic-locale-configuration.js +0 -348
  178. package/examples/dynamic-locale-example.js +0 -287
  179. package/examples/enum.examples.js +0 -324
  180. package/examples/export-demo.js +0 -130
  181. package/examples/express-integration.js +0 -376
  182. package/examples/i18n-error-handling-complete.js +0 -381
  183. package/examples/i18n-error-handling-quickstart.md +0 -0
  184. package/examples/i18n-error.examples.js +0 -181
  185. package/examples/i18n-full-demo.js +0 -301
  186. package/examples/i18n-memory-safety.examples.js +0 -268
  187. package/examples/markdown-export.js +0 -71
  188. package/examples/middleware-usage.js +0 -93
  189. package/examples/new-features-comparison.js +0 -315
  190. package/examples/password-reset/README.md +0 -153
  191. package/examples/password-reset/schema.js +0 -26
  192. package/examples/password-reset/test.js +0 -101
  193. package/examples/plugin-system.examples.js +0 -205
  194. package/examples/schema-utils-chaining.examples.js +0 -250
  195. package/examples/simple-example.js +0 -122
  196. package/examples/slug.examples.js +0 -179
  197. package/examples/string-extensions.js +0 -297
  198. package/examples/union-type-example.js +0 -127
  199. package/examples/union-types-example.js +0 -77
  200. package/examples/user-registration/README.md +0 -156
  201. package/examples/user-registration/routes.js +0 -92
  202. package/examples/user-registration/schema.js +0 -150
  203. package/examples/user-registration/server.js +0 -74
  204. package/index.d.ts +0 -3658
  205. package/index.js +0 -475
  206. package/index.mjs +0 -60
  207. package/lib/adapters/DslAdapter.js +0 -995
  208. package/lib/adapters/index.js +0 -20
  209. package/lib/config/constants.js +0 -286
  210. package/lib/config/patterns/common.js +0 -47
  211. package/lib/config/patterns/creditCard.js +0 -9
  212. package/lib/config/patterns/idCard.js +0 -9
  213. package/lib/config/patterns/index.js +0 -9
  214. package/lib/config/patterns/licensePlate.js +0 -4
  215. package/lib/config/patterns/passport.js +0 -4
  216. package/lib/config/patterns/phone.js +0 -9
  217. package/lib/config/patterns/postalCode.js +0 -5
  218. package/lib/core/CacheManager.js +0 -376
  219. package/lib/core/ConditionalBuilder.js +0 -503
  220. package/lib/core/DslBuilder.js +0 -1589
  221. package/lib/core/ErrorCodes.js +0 -233
  222. package/lib/core/ErrorFormatter.js +0 -445
  223. package/lib/core/JSONSchemaCore.js +0 -347
  224. package/lib/core/Locale.js +0 -130
  225. package/lib/core/MessageTemplate.js +0 -98
  226. package/lib/core/PluginManager.js +0 -448
  227. package/lib/core/StringExtensions.js +0 -240
  228. package/lib/core/Validator.js +0 -654
  229. package/lib/errors/I18nError.js +0 -328
  230. package/lib/errors/ValidationError.js +0 -191
  231. package/lib/exporters/MarkdownExporter.js +0 -420
  232. package/lib/exporters/MongoDBExporter.js +0 -162
  233. package/lib/exporters/MySQLExporter.js +0 -212
  234. package/lib/exporters/PostgreSQLExporter.js +0 -289
  235. package/lib/exporters/index.js +0 -24
  236. package/lib/locales/index.js +0 -8
  237. package/lib/utils/LRUCache.js +0 -174
  238. package/lib/utils/SchemaHelper.js +0 -240
  239. package/lib/utils/SchemaUtils.js +0 -445
  240. package/lib/utils/TypeConverter.js +0 -245
  241. package/lib/utils/index.js +0 -13
  242. package/lib/validators/CustomKeywords.js +0 -616
  243. package/lib/validators/index.js +0 -11
@@ -1,573 +1,578 @@
1
- # validate() 函数支持 DSL 对象说明
2
-
3
- ## 问题
4
-
5
- 用户问:`validate(schema, { email: 'test@example.com', age: 25 })` 中的 `schema` 能否直接是个对象,为什么必须是 schema?
6
-
7
- ## 答案
8
-
9
- **现在可以了!** 🎉 v1.1.7 开始,`validate()` 和 `validateAsync()` 都支持直接传入 DSL 对象。
10
-
11
- ---
12
-
13
- ## 支持的三种方式
14
-
15
- ### 方式1:传入 DSL 对象(✅ v1.1.7 新增)
16
-
17
- ```javascript
18
- const { validate } = require('schema-dsl');
19
-
20
- // ✅ 直接传入 DSL 对象,无需 dsl() 包裹
21
- const result = validate(
22
- { email: 'email!', age: 'number:18-120' }, // DSL 对象
23
- { email: 'test@example.com', age: 25 }
24
- );
25
-
26
- console.log(result.valid); // true
27
- ```
28
-
29
- **优点**:
30
- - ✅ 最简洁,无需 `dsl()` 包裹
31
- - ✅ 代码更直观,适合简单场景
32
-
33
- **⚠️ 注意**:DSL 对象也支持混合使用 DslBuilder 实例:
34
-
35
- ```javascript
36
- const { dsl, validate } = require('schema-dsl');
37
-
38
- // ✅ 混合使用:DslBuilder + DSL 字符串
39
- const result = validate(
40
- {
41
- username: dsl('string:3-32!')
42
- .pattern(/^[a-zA-Z0-9_]+$/)
43
- .messages({ 'string.pattern': '只能包含字母、数字和下划线' }),
44
- email: 'email!', // 纯 DSL 字符串
45
- age: 'number:18-'
46
- },
47
- data
48
- );
49
- ```
50
-
51
- ### 方式2:使用 dsl() 包裹(推荐)
52
-
53
- ```javascript
54
- const { dsl, validate } = require('schema-dsl');
55
-
56
- // ✅ 先转换为 JSON Schema,再验证
57
- const schema = dsl({
58
- email: 'email!',
59
- age: 'number:18-120'
60
- });
61
-
62
- const result = validate(schema, { email: 'test@example.com', age: 25 });
63
- ```
64
-
65
- **优点**:
66
- - ✅ 更明确,意图清晰
67
- - ✅ 可复用 schema
68
- - ✅ 支持链式调用扩展
69
-
70
- ### 方式3:传入标准 JSON Schema
71
-
72
- ```javascript
73
- const { validate } = require('schema-dsl');
74
-
75
- // ✅ 传入标准 JSON Schema
76
- const result = validate(
77
- {
78
- type: 'object',
79
- properties: {
80
- email: { type: 'string', format: 'email' },
81
- age: { type: 'number', minimum: 18, maximum: 120 }
82
- },
83
- required: ['email']
84
- },
85
- { email: 'test@example.com', age: 25 }
86
- );
87
- ```
88
-
89
- **优点**:
90
- - ✅ 兼容标准 JSON Schema
91
- - ✅ 可与其他 JSON Schema 工具互操作
92
-
93
- ---
94
-
95
- ## 实现原理
96
-
97
- ### 自动检测逻辑
98
-
99
- `validate()` 函数会自动检测传入的 schema 类型:
100
-
101
- ```javascript
102
- function validate(schema, data, options = {}) {
103
- // 自动检测并转换 DSL 对象
104
- if (_isDslObject(schema)) {
105
- schema = DslAdapter.parseObject(schema);
106
- }
107
-
108
- const validator = new Validator(options);
109
- return validator.validate(schema, data, options);
110
- }
111
- ```
112
-
113
- ### 检测规则
114
-
115
- 判断是否为 DSL 对象的逻辑(`_isDslObject()`):
116
-
117
- 1. **排除非对象**:不是普通对象返回 false
118
- 2. **排除 DslBuilder**:有 `toSchema()` 方法返回 false
119
- 3. **排除 ConditionalBuilder**:有 `_isConditional` 标记返回 false
120
- 4. **排除标准 JSON Schema**:
121
- - `type` 字段且值为标准类型(string/number/object等)
122
- - `properties` 的所有值都包含 `type` 字段
123
- 5. **识别 DSL 对象**:
124
- - 属性值包含 DSL 字符串(如 `'email!'`, `'string:3-32'`)
125
- - 属性值包含嵌套的 DSL 对象
126
-
127
- ---
128
-
129
- ## 为什么之前必须是 schema?
130
-
131
- ### 历史原因
132
-
133
- v1.1.7 之前,`validate()` 不会自动转换 DSL 对象:
134
-
135
- ```javascript
136
- // ❌ v1.1.6 及之前版本会失败
137
- const result = validate(
138
- { email: 'email!', age: 'number!' }, // 被当作 JSON Schema
139
- { email: 'test@example.com', age: 25 }
140
- );
141
- // 错误:Schema compilation failed: unknown keyword: "email"
142
- ```
143
-
144
- **原因**:`validate()` 会把 DSL 对象当作标准 JSON Schema,而 `"email!"` 不是有效的 JSON Schema 关键字。
145
-
146
- ### 解决方案
147
-
148
- v1.1.7 添加了自动检测和转换逻辑:
149
-
150
- 1. **检测 DSL 对象**:识别对象中的 DSL 字符串
151
- 2. **自动转换**:调用 `DslAdapter.parseObject()` 转换为 JSON Schema
152
- 3. **透明处理**:用户无需关心内部转换
153
-
154
- ---
155
-
156
- ## 使用建议
157
-
158
- ### 简单场景:直接用 DSL 对象
159
-
160
- 适用于:脚本、原型开发、测试代码、一次性验证
161
-
162
- ```javascript
163
- // ✅ 简单验证,直接传 DSL 对象
164
- app.post('/api/user', (req, res) => {
165
- const result = validate(
166
- { email: 'email!', age: 'number:18-' },
167
- req.body
168
- );
169
-
170
- if (!result.valid) {
171
- return res.status(400).json({ errors: result.errors });
172
- }
173
-
174
- // 处理数据...
175
- });
176
- ```
177
-
178
- ### 复杂场景:项目启动时配置 schema(推荐)
179
-
180
- 适用于:生产环境、高并发服务、需要复用的场景
181
-
182
- ```javascript
183
- // ✅ 最佳实践:在单独的文件中定义所有 schema
184
-
185
- // schemas/user.js - 项目启动时加载,转换一次
186
- const { dsl } = require('schema-dsl');
187
-
188
- module.exports = {
189
- register: dsl({
190
- username: dsl('string:3-32!')
191
- .pattern(/^[a-zA-Z0-9_]+$/)
192
- .messages({ 'string.pattern': '只能包含字母、数字和下划线' }),
193
- email: 'email!',
194
- password: 'password:strong!',
195
- age: 'number:18-120'
196
- }),
197
-
198
- login: dsl({
199
- username: 'string!',
200
- password: 'string!'
201
- }),
202
-
203
- updateProfile: dsl({
204
- nickname: 'string:2-20',
205
- avatar: 'url',
206
- bio: 'string:0-500'
207
- })
208
- };
209
-
210
- // routes/user.js - 路由中直接使用,不再转换
211
- const userSchemas = require('../schemas/user');
212
- const { validate } = require('schema-dsl');
213
-
214
- app.post('/api/register', (req, res) => {
215
- const result = validate(userSchemas.register, req.body); // 直接使用
216
- // ...
217
- });
218
-
219
- app.post('/api/login', (req, res) => {
220
- const result = validate(userSchemas.login, req.body); // 直接使用
221
- // ...
222
- });
223
-
224
- app.put('/api/user/profile', (req, res) => {
225
- const result = validate(userSchemas.updateProfile, req.body); // ✅ 直接使用
226
- // ...
227
- });
228
- ```
229
-
230
- **性能优势**:
231
- - 避免每次请求都转换 DSL 对象
232
- - ✅ schema 只在项目启动时创建一次
233
- - ✅ 适合高并发场景
234
-
235
- ### 需要链式调用:混合使用 DslBuilder
236
-
237
- 适用于:需要自定义错误消息、复杂验证规则
238
-
239
- ```javascript
240
- // 需要自定义消息
241
- const schema = dsl({
242
- email: dsl('email!')
243
- .label('邮箱地址')
244
- .messages({ 'string.email': '请输入有效的邮箱' }),
245
-
246
- username: dsl('string:3-32!')
247
- .pattern(/^[a-zA-Z0-9_]+$/)
248
- .messages({ 'string.pattern': '只能包含字母、数字和下划线' })
249
- });
250
-
251
- const result = validate(schema, data);
252
- ```
253
-
254
- ---
255
-
256
- ## 对比总结
257
-
258
- | 方式 | 简洁性 | 灵活性 | 复用性 | 适用场景 |
259
- |------|-------|-------|-------|---------|
260
- | DSL 对象 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | 简单验证、一次性使用 |
261
- | dsl() 包裹 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 复杂验证、需要复用 |
262
- | JSON Schema | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 与其他工具互操作 |
263
-
264
- ---
265
-
266
- ## 注意事项
267
-
268
- ### 1. 性能考虑
269
-
270
- DSL 对象会在每次 `validate()` 调用时转换,如果需要高性能:
271
-
272
- ```javascript
273
- // ❌ 不推荐:每次请求都转换
274
- app.post('/api/user', (req, res) => {
275
- const result = validate(
276
- { email: 'email!', age: 'number!' }, // 每次都转换
277
- req.body
278
- );
279
- });
280
-
281
- // 推荐:提前转换,复用 schema
282
- const userSchema = dsl({ email: 'email!', age: 'number!' });
283
-
284
- app.post('/api/user', (req, res) => {
285
- const result = validate(userSchema, req.body); // 直接使用
286
- });
287
- ```
288
-
289
- ### 2. 类型混淆
290
-
291
- 确保 DSL 对象不会被误识别为 JSON Schema:
292
-
293
- ```javascript
294
- // ✅ 明确的 DSL 对象
295
- { email: 'email!', age: 'number!' } // 自动识别
296
-
297
- // ⚠️ 可能混淆
298
- {
299
- type: 'object', // 有 type 字段
300
- email: 'email!' // 但还有 DSL 字符串
301
- }
302
- // 会被识别为 JSON Schema(type 优先级高)
303
- ```
304
-
305
- ### 3. 嵌套对象
306
-
307
- 嵌套的 DSL 对象会被正确处理:
308
-
309
- ```javascript
310
- // ✅ 支持嵌套
311
- const result = validate(
312
- {
313
- user: {
314
- profile: {
315
- name: 'string!',
316
- age: 'number!'
317
- }
318
- }
319
- },
320
- data
321
- );
322
- ```
323
-
324
- ---
325
-
326
- ## 完整示例
327
-
328
- ```javascript
329
- const { validate, validateAsync } = require('schema-dsl');
330
-
331
- // 示例1:同步验证
332
- const result = validate(
333
- {
334
- email: 'email!',
335
- password: 'password:strong!',
336
- age: 'number:18-120',
337
- username: 'string:3-32!'
338
- },
339
- {
340
- email: 'test@example.com',
341
- password: 'MyP@ssw0rd!',
342
- age: 25,
343
- username: 'john_doe'
344
- }
345
- );
346
-
347
- if (result.valid) {
348
- console.log('验证通过');
349
- } else {
350
- console.log('验证失败:', result.errors);
351
- }
352
-
353
- // 示例2:异步验证
354
- (async () => {
355
- try {
356
- const data = await validateAsync(
357
- { email: 'email!', age: 'number!' },
358
- { email: 'test@example.com', age: 25 }
359
- );
360
- console.log('验证通过:', data);
361
- } catch (error) {
362
- console.error('验证失败:', error.errors);
363
- }
364
- })();
365
- ```
366
-
367
- ---
368
-
369
- ## 总结
370
-
371
- **问:为什么必须是 schema?**
372
-
373
- **答:现在不必了!**
374
-
375
- - ✅ v1.1.7 开始支持直接传入 DSL 对象
376
- - ✅ 自动检测并转换,无需手动包裹
377
- - 完全向后兼容,不影响原有功能
378
- - 同时支持 JSON Schema、DslBuilder、DSL 对象三种方式
379
-
380
- **推荐使用**:
381
- - 简单场景:直接用 DSL 对象
382
- - 复杂场景:先用 `dsl()` 转换,便于复用和扩展
383
-
384
- ---
385
-
386
- ## 常见问题
387
-
388
- ### Q1: DSL 对象中可以使用链式调用吗?
389
-
390
- **A: 可以!** 支持混合使用 DslBuilder 实例和 DSL 字符串:
391
-
392
- ```javascript
393
- const result = validate(
394
- {
395
- username: dsl('string:3-32!')
396
- .pattern(/^[a-zA-Z0-9_]+$/)
397
- .messages({ 'string.pattern': '只能包含字母、数字和下划线' }),
398
- email: 'email!', // 纯 DSL 字符串
399
- age: 'number:18-'
400
- },
401
- data
402
- );
403
- ```
404
-
405
- 嵌套对象中也支持:
406
-
407
- ```javascript
408
- const result = validate(
409
- {
410
- user: {
411
- name: dsl('string:3-32!').messages({ 'string.min': '名字太短了' }),
412
- email: 'email!'
413
- }
414
- },
415
- data
416
- );
417
- ```
418
-
419
- ### Q2: 直接用对象会有什么影响?
420
-
421
- **性能影响**:
422
-
423
- 每次调用 `validate()` 时,DSL 对象都会被转换为 JSON Schema:
424
-
425
- ```javascript
426
- // ❌ 性能较差:每次请求都重复转换(~3.4秒/1000次)
427
- app.post('/api/user', (req, res) => {
428
- const result = validate(
429
- { email: 'email!', age: 'number!' }, // ❌ 每次请求都会执行 DSL → JSON Schema 转换
430
- req.body
431
- );
432
- });
433
-
434
- // ✅ 性能最优:项目启动时转换一次,复用 schema(~3.3秒/1000次)
435
- const userSchema = dsl({ email: 'email!', age: 'number!' }); // ✅ 启动时转换一次
436
-
437
- app.post('/api/user', (req, res) => {
438
- const result = validate(userSchema, req.body); // ✅ 直接使用,不再转换
439
- });
440
- ```
441
-
442
- **性能差异**:约 3-5%(对于简单 schema)
443
-
444
- **✅ 您的理解完全正确!**
445
-
446
- **最佳实践**:在项目启动时配置好所有 schema
447
-
448
- ```javascript
449
- // ✅ 推荐:在单独的文件中定义所有 schema(schemas/user.js)
450
- const { dsl } = require('schema-dsl');
451
-
452
- // 项目启动时转换一次,后续直接复用
453
- const userSchemas = {
454
- register: dsl({
455
- username: dsl('string:3-32!')
456
- .pattern(/^[a-zA-Z0-9_]+$/)
457
- .messages({ 'string.pattern': '只能包含字母、数字和下划线' }),
458
- email: 'email!',
459
- password: 'password:strong!',
460
- age: 'number:18-120'
461
- }),
462
-
463
- login: dsl({
464
- username: 'string!',
465
- password: 'string!'
466
- }),
467
-
468
- updateProfile: dsl({
469
- nickname: 'string:2-20',
470
- avatar: 'url',
471
- bio: 'string:0-500'
472
- })
473
- };
474
-
475
- module.exports = userSchemas;
476
-
477
- // 在路由中使用(routes/user.js)
478
- const userSchemas = require('../schemas/user');
479
-
480
- app.post('/api/register', (req, res) => {
481
- const result = validate(userSchemas.register, req.body); // ✅ 直接使用
482
- // ...
483
- });
484
-
485
- app.post('/api/login', (req, res) => {
486
- const result = validate(userSchemas.login, req.body); // ✅ 直接使用
487
- // ...
488
- });
489
- ```
490
-
491
- **场景建议**:
492
-
493
- | 场景 | 推荐方式 | 原因 |
494
- |------|---------|------|
495
- | **生产环境 API** | ✅ 项目启动时配置 schema | 避免每次请求都转换,性能最优 |
496
- | **高并发服务** | ✅ 项目启动时配置 schema | 3-5% 的性能损失会被放大 |
497
- | **单次脚本** | ✅ 直接用 DSL 对象 | 只执行一次,性能影响可忽略 |
498
- | **原型开发** | ✅ 直接用 DSL 对象 | 快速迭代,无需在意性能 |
499
- | **测试代码** | 直接用 DSL 对象 | 简洁清晰,易于维护 |
500
-
501
- ### Q3: 为什么之前不这样设计?
502
-
503
- **历史原因**:
504
-
505
- 1. **明确的职责分离**(设计哲学)
506
- ```javascript
507
- // 转换阶段:DSL → JSON Schema
508
- const schema = dsl({ email: 'email!', age: 'number!' });
509
-
510
- // 验证阶段:JSON Schema + data → result
511
- const result = validate(schema, data);
512
- ```
513
- 这种设计让每个步骤的职责更清晰。
514
-
515
- 2. **避免隐式转换**(最小惊喜原则)
516
- ```javascript
517
- // 用户传入什么,就是什么
518
- validate(jsonSchema, data); // JSON Schema
519
- validate(dslBuilder, data); // DslBuilder
520
-
521
- // ❌ 之前不支持隐式转换
522
- validate({ email: 'email!' }, data); // 会被当作 JSON Schema
523
- ```
524
-
525
- 3. **类型安全考虑**(TypeScript)
526
- ```typescript
527
- // 明确的类型定义
528
- function validate(
529
- schema: JSONSchema | DslBuilder, // 明确的类型
530
- data: any
531
- ): ValidationResult;
532
-
533
- // 如果支持任意对象,类型推断会变复杂
534
- function validate(
535
- schema: JSONSchema | DslBuilder | Record<string, any>, // 太宽泛
536
- data: any
537
- ): ValidationResult;
538
- ```
539
-
540
- 4. **性能考虑**(避免重复转换)
541
- ```javascript
542
- // 避免用户不经意间写出性能差的代码
543
- for (let i = 0; i < 10000; i++) {
544
- validate({ email: 'email!' }, data); // 每次都转换
545
- }
546
- ```
547
-
548
- **为什么现在改变了?**
549
-
550
- 1. **用户反馈**:很多用户期望更简洁的 API
551
- 2. **智能检测**:通过 `_isDslObject()` 准确区分 DSL 对象和 JSON Schema
552
- 3. **性能可接受**:转换开销很小(~3-5%)
553
- 4. **向后兼容**:不影响现有代码
554
- 5. **使用体验优先**:简化常见场景的使用
555
-
556
- **设计权衡**:
557
-
558
- | 设计方案 | 优点 | 缺点 |
559
- |---------|------|------|
560
- | **显式转换**(v1.1.6) | 职责清晰、类型安全、性能最优 | 代码冗长、学习成本高 |
561
- | **自动转换**(v1.1.7) | 简洁直观、学习成本低 | 隐式行为、可能误用 |
562
-
563
- **最终选择**:两者都支持,让用户自由选择!
564
-
565
- ```javascript
566
- // ✅ 简单场景:直接用 DSL 对象
567
- validate({ email: 'email!' }, data);
568
-
569
- // ✅ 复杂场景:显式转换
570
- const schema = dsl({ email: 'email!' });
571
- validate(schema, data);
572
- ```
573
-
1
+ # validate() 函数支持 DSL 对象说明
2
+
3
+ ## 问题
4
+
5
+ 用户问:`validate(schema, { email: 'test@example.com', age: 25 })` 中的 `schema` 能否直接是个对象,为什么必须是 schema?
6
+
7
+ ## 答案
8
+
9
+ **现在可以了!** 🎉 当前 TypeScript 重构版中,顶层 `validate()` 和 `validateAsync()` 都支持直接传入 DSL 对象。
10
+
11
+ ---
12
+
13
+ ## 支持的三种方式
14
+
15
+ ### 方式1:传入 DSL 对象(✅ 当前版本支持)
16
+
17
+ ```javascript
18
+ const { validate } = require('schema-dsl');
19
+
20
+ // ✅ 直接传入 DSL 对象,无需 dsl() 包裹
21
+ const result = validate(
22
+ { email: 'email!', age: 'number:18-120' }, // DSL 对象
23
+ { email: 'test@example.com', age: 25 }
24
+ );
25
+
26
+ console.log(result.valid); // true
27
+ ```
28
+
29
+ **优点**:
30
+ - ✅ 最简洁,无需 `dsl()` 包裹
31
+ - ✅ 代码更直观,适合简单场景
32
+
33
+ **⚠️ 注意**:DSL 对象也支持混合使用 DslBuilder 实例:
34
+
35
+ ```javascript
36
+ const { dsl, validate } = require('schema-dsl');
37
+
38
+ // ✅ 混合使用:DslBuilder + DSL 字符串
39
+ const result = validate(
40
+ {
41
+ username: dsl('string:3-32!')
42
+ .pattern(/^[a-zA-Z0-9_]+$/)
43
+ .messages({ 'string.pattern': '只能包含字母、数字和下划线' }),
44
+ email: 'email!', // 纯 DSL 字符串
45
+ age: 'number:18-'
46
+ },
47
+ data
48
+ );
49
+ ```
50
+
51
+ ### 方式2:使用 dsl() 包裹(推荐)
52
+
53
+ ```javascript
54
+ const { dsl, validate } = require('schema-dsl');
55
+
56
+ // ✅ 先转换为 JSON Schema,再验证
57
+ const schema = dsl({
58
+ email: 'email!',
59
+ age: 'number:18-120'
60
+ });
61
+
62
+ const result = validate(schema, { email: 'test@example.com', age: 25 });
63
+ ```
64
+
65
+ **优点**:
66
+ - ✅ 更明确,意图清晰
67
+ - ✅ 可复用 schema
68
+ - ✅ 支持链式调用扩展
69
+
70
+ ### 方式3:传入标准 JSON Schema
71
+
72
+ ```javascript
73
+ const { validate } = require('schema-dsl');
74
+
75
+ // ✅ 传入标准 JSON Schema
76
+ const result = validate(
77
+ {
78
+ type: 'object',
79
+ properties: {
80
+ email: { type: 'string', format: 'email' },
81
+ age: { type: 'number', minimum: 18, maximum: 120 }
82
+ },
83
+ required: ['email']
84
+ },
85
+ { email: 'test@example.com', age: 25 }
86
+ );
87
+ ```
88
+
89
+ **优点**:
90
+ - ✅ 兼容标准 JSON Schema
91
+ - ✅ 可与其他 JSON Schema 工具互操作
92
+
93
+ ---
94
+
95
+ ## 实现原理
96
+
97
+ ### 自动检测逻辑
98
+
99
+ 顶层 `validate()` / `validateAsync()` 会先归一化传入的 schema
100
+
101
+ ```javascript
102
+ function validate(schema, data, options = {}) {
103
+ const normalizedSchema = _normalizeSchemaInput(schema);
104
+ const validator = getDefaultValidator();
105
+ return validator.validate(normalizedSchema, data, options);
106
+ }
107
+ ```
108
+
109
+ ### 检测规则
110
+
111
+ 判断是否为 DSL 对象的逻辑(`_isDslObject()`):
112
+
113
+ 1. **排除非对象**:不是普通对象返回 false
114
+ 2. **排除 DslBuilder**:有 `toSchema()` 方法返回 false
115
+ 3. **排除 ConditionalBuilder**:有 `_isConditional` 标记返回 false
116
+ 4. **排除标准 JSON Schema**:
117
+ - `type` 字段且值为标准类型(string/number/object等)
118
+ - `properties` 的所有值都包含 `type` 字段
119
+ 5. **识别 DSL 对象**:
120
+ - 属性值包含 DSL 字符串(如 `'email!'`, `'string:3-32'`)
121
+ - 属性值包含嵌套的 DSL 对象
122
+
123
+ ---
124
+
125
+ ## 为什么之前必须是 schema?
126
+
127
+ ### 背景
128
+
129
+ 早期实现中,顶层 `validate()` 不会自动转换 DSL 对象:
130
+
131
+ ```javascript
132
+ // ❌ v1.1.6 及之前版本会失败
133
+ const result = validate(
134
+ { email: 'email!', age: 'number!' }, // 被当作 JSON Schema
135
+ { email: 'test@example.com', age: 25 }
136
+ );
137
+ // 错误:Schema compilation failed: unknown keyword: "email"
138
+ ```
139
+
140
+ **原因**:`validate()` 会把 DSL 对象当作标准 JSON Schema,而 `"email!"` 不是有效的 JSON Schema 关键字。
141
+
142
+ ### 当前方案
143
+
144
+ 当前 TypeScript 重构版已补齐自动检测和转换逻辑:
145
+
146
+ 1. **检测 DSL 对象**:识别对象中的 DSL 字符串
147
+ 2. **自动转换**:调用 `DslAdapter.parseObject()` 转换为 JSON Schema
148
+ 3. **透明处理**:用户无需关心内部转换
149
+
150
+ ---
151
+
152
+ ## 使用建议
153
+
154
+ ### 简单场景:直接用 DSL 对象
155
+
156
+ 适用于:脚本、原型开发、测试代码、一次性验证
157
+
158
+ ```javascript
159
+ // ✅ 简单验证,直接传 DSL 对象
160
+ app.post('/api/user', (req, res) => {
161
+ const result = validate(
162
+ { email: 'email!', age: 'number:18-' },
163
+ req.body
164
+ );
165
+
166
+ if (!result.valid) {
167
+ return res.status(400).json({ errors: result.errors });
168
+ }
169
+
170
+ // 处理数据...
171
+ });
172
+ ```
173
+
174
+ ### 复杂场景:项目启动时配置 schema(推荐)
175
+
176
+ 适用于:生产环境、高并发服务、需要复用的场景
177
+
178
+ ```javascript
179
+ // ✅ 最佳实践:在单独的文件中定义所有 schema
180
+
181
+ // schemas/user.js - 项目启动时加载,转换一次
182
+ const { dsl } = require('schema-dsl');
183
+
184
+ module.exports = {
185
+ register: dsl({
186
+ username: dsl('string:3-32!')
187
+ .pattern(/^[a-zA-Z0-9_]+$/)
188
+ .messages({ 'string.pattern': '只能包含字母、数字和下划线' }),
189
+ email: 'email!',
190
+ password: dsl('string!').password('strong'),
191
+ age: 'number:18-120'
192
+ }),
193
+
194
+ login: dsl({
195
+ username: 'string!',
196
+ password: 'string!'
197
+ }),
198
+
199
+ updateProfile: dsl({
200
+ nickname: 'string:2-20',
201
+ avatar: 'url',
202
+ bio: 'string:0-500'
203
+ })
204
+ };
205
+
206
+ // routes/user.js - 路由中直接使用,不再转换
207
+ const userSchemas = require('../schemas/user');
208
+ const { validate } = require('schema-dsl');
209
+
210
+ app.post('/api/register', (req, res) => {
211
+ const result = validate(userSchemas.register, req.body); // ✅ 直接使用
212
+ // ...
213
+ });
214
+
215
+ app.post('/api/login', (req, res) => {
216
+ const result = validate(userSchemas.login, req.body); // ✅ 直接使用
217
+ // ...
218
+ });
219
+
220
+ app.put('/api/user/profile', (req, res) => {
221
+ const result = validate(userSchemas.updateProfile, req.body); // ✅ 直接使用
222
+ // ...
223
+ });
224
+ ```
225
+
226
+ **性能优势**:
227
+ - ✅ 避免每次请求都转换 DSL 对象
228
+ - ✅ schema 只在项目启动时创建一次
229
+ - ✅ 适合高并发场景
230
+
231
+ ### 需要链式调用:混合使用 DslBuilder
232
+
233
+ 适用于:需要自定义错误消息、复杂验证规则
234
+
235
+ ```javascript
236
+ // ✅ 需要自定义消息
237
+ const schema = dsl({
238
+ email: dsl('email!')
239
+ .label('邮箱地址')
240
+ .messages({ 'string.email': '请输入有效的邮箱' }),
241
+
242
+ username: dsl('string:3-32!')
243
+ .pattern(/^[a-zA-Z0-9_]+$/)
244
+ .messages({ 'string.pattern': '只能包含字母、数字和下划线' })
245
+ });
246
+
247
+ const result = validate(schema, data);
248
+ ```
249
+
250
+ ---
251
+
252
+ ## 对比总结
253
+
254
+ | 方式 | 简洁性 | 灵活性 | 复用性 | 适用场景 |
255
+ |------|-------|-------|-------|---------|
256
+ | DSL 对象 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | 简单验证、一次性使用 |
257
+ | dsl() 包裹 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 复杂验证、需要复用 |
258
+ | JSON Schema | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 与其他工具互操作 |
259
+
260
+ ---
261
+
262
+ ## 注意事项
263
+
264
+ ### 1. 性能考虑
265
+
266
+ DSL 对象会在每次 `validate()` 调用时转换,如果需要高性能:
267
+
268
+ ```javascript
269
+ // ❌ 不推荐:每次请求都转换
270
+ app.post('/api/user', (req, res) => {
271
+ const result = validate(
272
+ { email: 'email!', age: 'number!' }, // 每次都转换
273
+ req.body
274
+ );
275
+ });
276
+
277
+ // ✅ 推荐:提前转换,复用 schema
278
+ const userSchema = dsl({ email: 'email!', age: 'number!' });
279
+
280
+ app.post('/api/user', (req, res) => {
281
+ const result = validate(userSchema, req.body); // 直接使用
282
+ });
283
+ ```
284
+
285
+ ### 2. 类型混淆
286
+
287
+ 确保 DSL 对象不会被误识别为 JSON Schema:
288
+
289
+ ```javascript
290
+ // ✅ 明确的 DSL 对象
291
+ { email: 'email!', age: 'number!' } // 自动识别
292
+
293
+ // ⚠️ 可能混淆
294
+ {
295
+ type: 'object', // type 字段
296
+ email: 'email!' // 但还有 DSL 字符串
297
+ }
298
+ // 会被识别为 JSON Schema(type 优先级高)
299
+ ```
300
+
301
+ ### 3. 嵌套对象
302
+
303
+ 嵌套的 DSL 对象会被正确处理:
304
+
305
+ ```javascript
306
+ // ✅ 支持嵌套
307
+ const result = validate(
308
+ {
309
+ user: {
310
+ profile: {
311
+ name: 'string!',
312
+ age: 'number!'
313
+ }
314
+ }
315
+ },
316
+ data
317
+ );
318
+ ```
319
+
320
+ ---
321
+
322
+ ## 完整示例
323
+
324
+ ```javascript
325
+ const { dsl, validate, validateAsync } = require('schema-dsl');
326
+
327
+ // 示例1:同步验证
328
+ const result = validate(
329
+ {
330
+ email: 'email!',
331
+ password: dsl('string!').password('strong'),
332
+ age: 'number:18-120',
333
+ username: 'string:3-32!'
334
+ },
335
+ {
336
+ email: 'test@example.com',
337
+ password: 'MyP@ssw0rd!',
338
+ age: 25,
339
+ username: 'john_doe'
340
+ }
341
+ );
342
+
343
+ if (result.valid) {
344
+ console.log('验证通过');
345
+ } else {
346
+ console.log('验证失败:', result.errors);
347
+ }
348
+
349
+ // 示例2:异步验证
350
+ (async () => {
351
+ try {
352
+ const data = await validateAsync(
353
+ { email: 'email!', age: 'number!' },
354
+ { email: 'test@example.com', age: 25 }
355
+ );
356
+ console.log('验证通过:', data);
357
+ } catch (error) {
358
+ console.error('验证失败:', error.errors);
359
+ }
360
+ })();
361
+ ```
362
+
363
+ ---
364
+
365
+ ## 总结
366
+
367
+ **问:为什么必须是 schema?**
368
+
369
+ **答:现在不必了!**
370
+
371
+ - ✅ 当前版本支持直接传入 DSL 对象
372
+ - ✅ 自动检测并转换,无需手动包裹
373
+ - ✅ 完全向后兼容,不影响原有功能
374
+ - ✅ 同时支持 JSON Schema、DslBuilder、DSL 对象三种方式
375
+
376
+ **推荐使用**:
377
+ - 简单场景:直接用 DSL 对象
378
+ - 复杂场景:先用 `dsl()` 转换,便于复用和扩展
379
+
380
+ ---
381
+
382
+ ## 常见问题
383
+
384
+ ### Q1: DSL 对象中可以使用链式调用吗?
385
+
386
+ **A: 可以!** 支持混合使用 DslBuilder 实例和 DSL 字符串:
387
+
388
+ ```javascript
389
+ const result = validate(
390
+ {
391
+ username: dsl('string:3-32!')
392
+ .pattern(/^[a-zA-Z0-9_]+$/)
393
+ .messages({ 'string.pattern': '只能包含字母、数字和下划线' }),
394
+ email: 'email!', // 纯 DSL 字符串
395
+ age: 'number:18-'
396
+ },
397
+ data
398
+ );
399
+ ```
400
+
401
+ 嵌套对象中也支持:
402
+
403
+ ```javascript
404
+ const result = validate(
405
+ {
406
+ user: {
407
+ name: dsl('string:3-32!').messages({ 'string.min': '名字太短了' }),
408
+ email: 'email!'
409
+ }
410
+ },
411
+ data
412
+ );
413
+ ```
414
+
415
+ ### Q2: 直接用对象会有什么影响?
416
+
417
+ **性能影响**:
418
+
419
+ 每次调用 `validate()` 时,DSL 对象都会被转换为 JSON Schema:
420
+
421
+ ```javascript
422
+ // ❌ 性能较差:每次请求都重复转换
423
+ app.post('/api/user', (req, res) => {
424
+ const result = validate(
425
+ { email: 'email!', age: 'number!' }, // ❌ 每次请求都会执行 DSL → JSON Schema 转换
426
+ req.body
427
+ );
428
+ });
429
+
430
+ // ✅ 性能最优:项目启动时转换一次,复用 schema
431
+ const userSchema = dsl({ email: 'email!', age: 'number!' }); // ✅ 启动时转换一次
432
+
433
+ app.post('/api/user', (req, res) => {
434
+ const result = validate(userSchema, req.body); // ✅ 直接使用,不再转换
435
+ });
436
+ ```
437
+
438
+ > ℹ️ 具体耗时取决于机器性能、Node 版本、schema 复杂度和命中率;这里强调的是“预先转换后复用通常显著快于每次请求都重新转换”的相对结论,而不是固定秒数。
439
+
440
+ **性能差异**:约 3-5%(对于简单 schema)
441
+
442
+ **✅ 您的理解完全正确!**
443
+
444
+ **最佳实践**:在项目启动时配置好所有 schema
445
+
446
+ ```javascript
447
+ // ✅ 推荐:在单独的文件中定义所有 schema(schemas/user.js)
448
+ const { dsl } = require('schema-dsl');
449
+
450
+ // 项目启动时转换一次,后续直接复用
451
+ const userSchemas = {
452
+ register: dsl({
453
+ username: dsl('string:3-32!')
454
+ .pattern(/^[a-zA-Z0-9_]+$/)
455
+ .messages({ 'string.pattern': '只能包含字母、数字和下划线' }),
456
+ email: 'email!',
457
+ password: dsl('string!').password('strong'),
458
+ age: 'number:18-120'
459
+ }),
460
+
461
+ login: dsl({
462
+ username: 'string!',
463
+ password: 'string!'
464
+ }),
465
+
466
+ updateProfile: dsl({
467
+ nickname: 'string:2-20',
468
+ avatar: 'url',
469
+ bio: 'string:0-500'
470
+ })
471
+ };
472
+
473
+ module.exports = userSchemas;
474
+
475
+ // 在路由中使用(routes/user.js)
476
+ const userSchemas = require('../schemas/user');
477
+
478
+ app.post('/api/register', (req, res) => {
479
+ const result = validate(userSchemas.register, req.body); // ✅ 直接使用
480
+ // ...
481
+ });
482
+
483
+ app.post('/api/login', (req, res) => {
484
+ const result = validate(userSchemas.login, req.body); // ✅ 直接使用
485
+ // ...
486
+ });
487
+ ```
488
+
489
+ **场景建议**:
490
+
491
+ | 场景 | 推荐方式 | 原因 |
492
+ |------|---------|------|
493
+ | **生产环境 API** | 项目启动时配置 schema | 避免每次请求都转换,性能最优 |
494
+ | **高并发服务** | ✅ 项目启动时配置 schema | 3-5% 的性能损失会被放大 |
495
+ | **单次脚本** | ✅ 直接用 DSL 对象 | 只执行一次,性能影响可忽略 |
496
+ | **原型开发** | ✅ 直接用 DSL 对象 | 快速迭代,无需在意性能 |
497
+ | **测试代码** | ✅ 直接用 DSL 对象 | 简洁清晰,易于维护 |
498
+
499
+ ### Q3: 为什么复杂场景仍然建议先用 `dsl()` 转换?
500
+
501
+ **历史原因**:
502
+
503
+ 1. **明确的职责分离**(设计哲学)
504
+ ```javascript
505
+ // 转换阶段:DSL → JSON Schema
506
+ const schema = dsl({ email: 'email!', age: 'number!' });
507
+
508
+ // 验证阶段:JSON Schema + data result
509
+ const result = validate(schema, data);
510
+ ```
511
+ 这种设计让每个步骤的职责更清晰。
512
+
513
+ 2. **避免在高频路径里滥用隐式转换**(最小惊喜原则)
514
+ ```javascript
515
+ // 用户传入什么,就是什么
516
+ validate(jsonSchema, data); // JSON Schema
517
+ validate(dslBuilder, data); // DslBuilder
518
+
519
+ // ⚠️ 当前虽然支持隐式转换,但高频场景仍建议预先转换后复用
520
+ validate({ email: 'email!' }, data);
521
+ ```
522
+
523
+ 3. **类型安全考虑**(TypeScript)
524
+ ```typescript
525
+ // 明确的类型定义
526
+ function validate(
527
+ schema: JSONSchema | DslBuilder, // 明确的类型
528
+ data: any
529
+ ): ValidationResult;
530
+
531
+ // 如果支持任意对象,类型推断会变复杂
532
+ function validate(
533
+ schema: JSONSchema | DslBuilder | Record<string, any>, // 太宽泛
534
+ data: any
535
+ ): ValidationResult;
536
+ ```
537
+
538
+ 4. **性能考虑**(避免重复转换)
539
+ ```javascript
540
+ // 避免用户不经意间写出性能差的代码
541
+ for (let i = 0; i < 10000; i++) {
542
+ validate({ email: 'email!' }, data); // 每次都转换
543
+ }
544
+ ```
545
+
546
+ **为什么当前版本要补齐这个能力?**
547
+
548
+ 1. **用户反馈**:很多用户期望更简洁的 API
549
+ 2. **智能检测**:通过 `_isDslObject()` 准确区分 DSL 对象和 JSON Schema
550
+ 3. **性能可接受**:转换开销很小(~3-5%)
551
+ 4. **向后兼容**:不影响现有代码
552
+ 5. **使用体验优先**:简化常见场景的使用
553
+
554
+ **设计权衡**:
555
+
556
+ | 设计方案 | 优点 | 缺点 |
557
+ |---------|------|------|
558
+ | **显式转换** | 职责清晰、类型安全、性能最优 | 代码稍长 |
559
+ | **自动转换**(当前顶层便捷函数) | 简洁直观、学习成本低 | 在高频路径里有额外转换开销 |
560
+
561
+ **最终选择**:两者都支持,让用户自由选择!
562
+
563
+ ```javascript
564
+ // ✅ 简单场景:直接用 DSL 对象
565
+ validate({ email: 'email!' }, data);
566
+
567
+ // 复杂场景:显式转换
568
+ const schema = dsl({ email: 'email!' });
569
+ validate(schema, data);
570
+ ```
571
+
572
+ ---
573
+
574
+ ## 对应示例文件
575
+
576
+ **示例入口**: [validate-dsl-object-support.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/validate-dsl-object-support.ts)
577
+ **说明**: 覆盖直接传入 DSL 对象、混合使用 `DslBuilder` 与 DSL 字符串,以及顶层 `validate()` / `validateAsync()` 的真实支持边界。
578
+