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,408 +1,417 @@
1
- # Schema-DSL 项目最佳实践示例
2
-
3
- ## 推荐的项目结构
4
-
5
- ```
6
- your-project/
7
- ├── schemas/ # ✅ 所有 schema 定义(项目启动时加载)
8
- │ ├── index.js # 统一导出
9
- │ ├── user.js # 用户相关 schema
10
- │ ├── order.js # 订单相关 schema
11
- │ └── product.js # 产品相关 schema
12
- ├── routes/
13
- │ ├── user.js # 用户路由(使用 schemas/user.js)
14
- │ ├── order.js # 订单路由(使用 schemas/order.js)
15
- │ └── product.js # 产品路由(使用 schemas/product.js)
16
- └── app.js # 主应用入口
17
- ```
18
-
19
- ---
20
-
21
- ## 完整示例代码
22
-
23
- ### 1. 定义 Schema(schemas/user.js)
24
-
25
- ```javascript
26
- const { dsl } = require('schema-dsl');
27
-
28
- /**
29
- * 用户相关的所有 schema
30
- *
31
- * ✅ 在项目启动时转换一次,后续直接复用
32
- * ✅ 避免每次请求都重复转换
33
- */
34
- const userSchemas = {
35
- // 注册 schema
36
- register: dsl({
37
- username: dsl('string:3-32!')
38
- .pattern(/^[a-zA-Z0-9_]+$/)
39
- .label('用户名')
40
- .messages({
41
- 'string.pattern': '用户名只能包含字母、数字和下划线',
42
- 'string.min': '用户名至少需要3个字符',
43
- 'string.max': '用户名最多32个字符'
44
- }),
45
-
46
- email: dsl('email!')
47
- .label('邮箱')
48
- .messages({
49
- 'string.email': '请输入有效的邮箱地址'
50
- }),
51
-
52
- password: dsl('password:strong!')
53
- .label('密码')
54
- .messages({
55
- 'string.password': '密码必须包含大小写字母、数字和特殊字符'
56
- }),
57
-
58
- age: 'number:18-120',
59
-
60
- phone: dsl('phone')
61
- .label('手机号')
62
- .messages({
63
- 'string.phone': '请输入有效的手机号'
64
- })
65
- }),
66
-
67
- // 登录 schema
68
- login: dsl({
69
- username: 'string!',
70
- password: 'string!'
71
- }),
72
-
73
- // 更新个人资料 schema
74
- updateProfile: dsl({
75
- nickname: 'string:2-20',
76
- avatar: 'url',
77
- bio: 'string:0-500',
78
- birthday: 'date',
79
- gender: 'male|female|other'
80
- }),
81
-
82
- // 修改密码 schema
83
- changePassword: dsl({
84
- oldPassword: 'string!',
85
- newPassword: 'password:strong!'
86
- })
87
- };
88
-
89
- module.exports = userSchemas;
90
- ```
91
-
92
- ### 2. 定义 Schema(schemas/order.js)
93
-
94
- ```javascript
95
- const { dsl } = require('schema-dsl');
96
-
97
- const orderSchemas = {
98
- // 创建订单
99
- create: dsl({
100
- items: 'array:1-100<object>!',
101
- shippingAddress: dsl({
102
- name: 'string:2-50!',
103
- phone: 'phone!',
104
- address: 'string:10-200!',
105
- zipCode: 'string:6'
106
- }),
107
- paymentMethod: 'alipay|wechat|card!',
108
- couponCode: 'string:6-20'
109
- }),
110
-
111
- // 更新订单状态
112
- updateStatus: dsl({
113
- status: 'pending|paid|shipped|completed|cancelled!',
114
- note: 'string:0-500'
115
- })
116
- };
117
-
118
- module.exports = orderSchemas;
119
- ```
120
-
121
- ### 3. 统一导出(schemas/index.js)
122
-
123
- ```javascript
124
- /**
125
- * 统一导出所有 schema
126
- *
127
- * 使用方式:
128
- * const schemas = require('./schemas');
129
- * const result = validate(schemas.user.register, data);
130
- */
131
- module.exports = {
132
- user: require('./user'),
133
- order: require('./order'),
134
- product: require('./product')
135
- };
136
- ```
137
-
138
- ### 4. 在路由中使用(routes/user.js)
139
-
140
- ```javascript
141
- const express = require('express');
142
- const router = express.Router();
143
- const { validate } = require('schema-dsl');
144
- const userSchemas = require('../schemas/user');
145
-
146
- /**
147
- * 用户注册
148
- *
149
- * ✅ 使用预定义的 schema,不再重复转换
150
- */
151
- router.post('/register', async (req, res) => {
152
- // ✅ 直接使用,性能最优
153
- const result = validate(userSchemas.register, req.body);
154
-
155
- if (!result.valid) {
156
- return res.status(400).json({
157
- code: 'VALIDATION_ERROR',
158
- message: '数据验证失败',
159
- errors: result.errors
160
- });
161
- }
162
-
163
- // 处理注册逻辑
164
- try {
165
- const user = await createUser(result.data);
166
- res.status(201).json({
167
- code: 'SUCCESS',
168
- data: user
169
- });
170
- } catch (error) {
171
- res.status(500).json({
172
- code: 'SERVER_ERROR',
173
- message: error.message
174
- });
175
- }
176
- });
177
-
178
- /**
179
- * 用户登录
180
- */
181
- router.post('/login', async (req, res) => {
182
- // ✅ 直接使用
183
- const result = validate(userSchemas.login, req.body);
184
-
185
- if (!result.valid) {
186
- return res.status(400).json({
187
- code: 'VALIDATION_ERROR',
188
- errors: result.errors
189
- });
190
- }
191
-
192
- // 处理登录逻辑
193
- // ...
194
- });
195
-
196
- /**
197
- * 更新个人资料
198
- */
199
- router.put('/profile', authenticate, async (req, res) => {
200
- // ✅ 直接使用
201
- const result = validate(userSchemas.updateProfile, req.body);
202
-
203
- if (!result.valid) {
204
- return res.status(400).json({
205
- code: 'VALIDATION_ERROR',
206
- errors: result.errors
207
- });
208
- }
209
-
210
- // 处理更新逻辑
211
- // ...
212
- });
213
-
214
- module.exports = router;
215
- ```
216
-
217
- ### 5. 主应用入口(app.js)
218
-
219
- ```javascript
220
- const express = require('express');
221
- const app = express();
222
-
223
- // ✅ 在应用启动时加载所有 schema(只转换一次)
224
- const schemas = require('./schemas');
225
- console.log('✅ Schemas loaded:', Object.keys(schemas));
226
-
227
- // 中间件
228
- app.use(express.json());
229
-
230
- // 路由
231
- app.use('/api/user', require('./routes/user'));
232
- app.use('/api/order', require('./routes/order'));
233
- app.use('/api/product', require('./routes/product'));
234
-
235
- // 启动服务
236
- const PORT = process.env.PORT || 3000;
237
- app.listen(PORT, () => {
238
- console.log(`✅ Server started on port ${PORT}`);
239
- console.log('✅ All schemas are pre-compiled and ready to use');
240
- });
241
-
242
- module.exports = app;
243
- ```
244
-
245
- ---
246
-
247
- ## 性能对比
248
-
249
- ### ❌ 不推荐:每次请求都转换
250
-
251
- ```javascript
252
- // ❌ 错误示例
253
- router.post('/register', (req, res) => {
254
- const result = validate(
255
- { // ❌ 每次请求都转换
256
- username: 'string:3-32!',
257
- email: 'email!',
258
- password: 'password:strong!'
259
- },
260
- req.body
261
- );
262
- // ...
263
- });
264
- ```
265
-
266
- **性能问题**:
267
- - ❌ 每次请求都执行 DSL → JSON Schema 转换
268
- - ❌ 1000 次请求 = 1000 次转换
269
- - ❌ 高并发时性能损失明显
270
-
271
- ### ✅ 推荐:项目启动时转换
272
-
273
- ```javascript
274
- // ✅ 正确示例
275
- const userSchemas = require('../schemas/user'); // ✅ 启动时加载
276
-
277
- router.post('/register', (req, res) => {
278
- const result = validate(
279
- userSchemas.register, // ✅ 直接使用
280
- req.body
281
- );
282
- // ...
283
- });
284
- ```
285
-
286
- **性能优势**:
287
- - ✅ 启动时转换 1 次
288
- - ✅ 1000 次请求 = 0 次转换
289
- - ✅ 高并发时性能最优
290
-
291
- ---
292
-
293
- ## 使用场景总结
294
-
295
- | 场景 | 推荐方式 | 代码示例 | 原因 |
296
- |------|---------|---------|------|
297
- | **生产环境 API** | ✅ 项目启动时配置 | `const schemas = require('./schemas')` | 避免每次请求都转换 |
298
- | **高并发服务** | ✅ 项目启动时配置 | 同上 | 3-5% 的性能损失会被放大 |
299
- | **微服务** | ✅ 项目启动时配置 | 同上 | 保证响应时间稳定 |
300
- | **单次脚本** | ✅ 直接用 DSL 对象 | `validate({ email: 'email!' }, data)` | 只执行一次,性能影响可忽略 |
301
- | **原型开发** | ✅ 直接用 DSL 对象 | 同上 | 快速迭代,无需在意性能 |
302
- | **测试代码** | ✅ 直接用 DSL 对象 | 同上 | 简洁清晰,易于维护 |
303
-
304
- ---
305
-
306
- ## 常见错误
307
-
308
- ### ❌ 错误1:在路由文件中定义 schema
309
-
310
- ```javascript
311
- // ❌ 不推荐
312
- router.post('/register', (req, res) => {
313
- const schema = dsl({ // ❌ 每次请求都创建
314
- username: 'string:3-32!',
315
- email: 'email!'
316
- });
317
-
318
- const result = validate(schema, req.body);
319
- // ...
320
- });
321
- ```
322
-
323
- **问题**:每次请求都创建新的 schema 对象,浪费性能。
324
-
325
- ### ❌ 错误2:在函数内部定义 schema
326
-
327
- ```javascript
328
- // ❌ 不推荐
329
- function validateUser(data) {
330
- const schema = dsl({ // ❌ 每次调用都创建
331
- username: 'string:3-32!',
332
- email: 'email!'
333
- });
334
-
335
- return validate(schema, data);
336
- }
337
- ```
338
-
339
- **问题**:每次调用函数都创建新的 schema,应该提到函数外部。
340
-
341
- ### ✅ 正确:在模块顶部定义
342
-
343
- ```javascript
344
- // ✅ 推荐:模块加载时创建一次
345
- const userSchema = dsl({
346
- username: 'string:3-32!',
347
- email: 'email!'
348
- });
349
-
350
- router.post('/register', (req, res) => {
351
- const result = validate(userSchema, req.body); // ✅ 直接使用
352
- // ...
353
- });
354
- ```
355
-
356
- ---
357
-
358
- ## TypeScript 支持
359
-
360
- ```typescript
361
- // schemas/user.ts
362
- import { dsl } from 'schema-dsl';
363
-
364
- export const userSchemas = {
365
- register: dsl({
366
- username: dsl('string:3-32!')
367
- .pattern(/^[a-zA-Z0-9_]+$/)
368
- .messages({ 'string.pattern': '只能包含字母、数字和下划线' }),
369
- email: 'email!',
370
- password: 'password:strong!',
371
- age: 'number:18-120'
372
- }),
373
-
374
- login: dsl({
375
- username: 'string!',
376
- password: 'string!'
377
- })
378
- };
379
-
380
- // routes/user.ts
381
- import { validate } from 'schema-dsl';
382
- import { userSchemas } from '../schemas/user';
383
-
384
- router.post('/register', (req, res) => {
385
- const result = validate(userSchemas.register, req.body);
386
- // ...
387
- });
388
- ```
389
-
390
- ---
391
-
392
- ## 总结
393
-
394
- **✅ 最佳实践**:
395
- 1. 在单独的 `schemas/` 目录定义所有 schema
396
- 2. 项目启动时加载,转换一次
397
- 3. 路由中直接使用,不再转换
398
- 4. 适合生产环境和高并发场景
399
-
400
- **✅ 性能优势**:
401
- - 避免每次请求都重复转换
402
- - schema 复用,内存占用更小
403
- - 响应时间更稳定
404
-
405
- **✅ 代码优势**:
406
- - 集中管理所有验证规则
407
- - 易于维护和修改
408
- - 类型安全(TypeScript)
1
+ # Schema-DSL 项目最佳实践示例
2
+
3
+ ## 推荐的项目结构
4
+
5
+ ```text
6
+ your-project/
7
+ ├── schemas/ # ✅ 所有 schema 定义(项目启动时加载)
8
+ │ ├── index.js # 统一导出
9
+ │ ├── user.js # 用户相关 schema
10
+ │ ├── order.js # 订单相关 schema
11
+ │ └── product.js # 产品相关 schema
12
+ ├── routes/
13
+ │ ├── user.js # 用户路由(使用 schemas/user.js)
14
+ │ ├── order.js # 订单路由(使用 schemas/order.js)
15
+ │ └── product.js # 产品路由(使用 schemas/product.js)
16
+ └── app.js # 主应用入口
17
+ ```
18
+
19
+ ---
20
+
21
+ ## 完整示例代码
22
+
23
+ ### 1. 定义 Schema(schemas/user.js)
24
+
25
+ ```javascript
26
+ const { dsl } = require('schema-dsl');
27
+
28
+ /**
29
+ * 用户相关的所有 schema
30
+ *
31
+ * ✅ 在项目启动时转换一次,后续直接复用
32
+ * ✅ 避免每次请求都重复转换
33
+ */
34
+ const userSchemas = {
35
+ // 注册 schema
36
+ register: dsl({
37
+ username: dsl('string:3-32!')
38
+ .pattern(/^[a-zA-Z0-9_]+$/)
39
+ .label('用户名')
40
+ .messages({
41
+ 'string.pattern': '用户名只能包含字母、数字和下划线',
42
+ 'string.min': '用户名至少需要3个字符',
43
+ 'string.max': '用户名最多32个字符'
44
+ }),
45
+
46
+ email: dsl('email!')
47
+ .label('邮箱')
48
+ .messages({
49
+ 'string.email': '请输入有效的邮箱地址'
50
+ }),
51
+
52
+ password: dsl('string!').password('strong')
53
+ .label('密码')
54
+ .messages({
55
+ 'string.password': '密码必须包含大小写字母、数字和特殊字符'
56
+ }),
57
+
58
+ age: 'number:18-120',
59
+
60
+ phone: dsl('phone')
61
+ .label('手机号')
62
+ .messages({
63
+ 'string.phone': '请输入有效的手机号'
64
+ })
65
+ }),
66
+
67
+ // 登录 schema
68
+ login: dsl({
69
+ username: 'string!',
70
+ password: 'string!'
71
+ }),
72
+
73
+ // 更新个人资料 schema
74
+ updateProfile: dsl({
75
+ nickname: 'string:2-20',
76
+ avatar: 'url',
77
+ bio: 'string:0-500',
78
+ birthday: 'date',
79
+ gender: 'male|female|other'
80
+ }),
81
+
82
+ // 修改密码 schema
83
+ changePassword: dsl({
84
+ oldPassword: 'string!',
85
+ newPassword: dsl('string!').password('strong')
86
+ })
87
+ };
88
+
89
+ module.exports = userSchemas;
90
+ ```
91
+
92
+ ### 2. 定义 Schema(schemas/order.js)
93
+
94
+ ```javascript
95
+ const { dsl } = require('schema-dsl');
96
+
97
+ const orderSchemas = {
98
+ // 创建订单
99
+ create: dsl({
100
+ items: 'array:1-100<object>!',
101
+ shippingAddress: dsl({
102
+ name: 'string:2-50!',
103
+ phone: 'phone!',
104
+ address: 'string:10-200!',
105
+ zipCode: 'string:6'
106
+ }),
107
+ paymentMethod: 'alipay|wechat|card!',
108
+ couponCode: 'string:6-20'
109
+ }),
110
+
111
+ // 更新订单状态
112
+ updateStatus: dsl({
113
+ status: 'pending|paid|shipped|completed|cancelled!',
114
+ note: 'string:0-500'
115
+ })
116
+ };
117
+
118
+ module.exports = orderSchemas;
119
+ ```
120
+
121
+ ### 3. 统一导出(schemas/index.js)
122
+
123
+ ```javascript
124
+ /**
125
+ * 统一导出所有 schema
126
+ *
127
+ * 使用方式:
128
+ * const schemas = require('./schemas');
129
+ * const result = validate(schemas.user.register, data);
130
+ */
131
+ module.exports = {
132
+ user: require('./user'),
133
+ order: require('./order'),
134
+ product: require('./product')
135
+ };
136
+ ```
137
+
138
+ ### 4. 在路由中使用(routes/user.js)
139
+
140
+ ```javascript
141
+ const express = require('express');
142
+ const router = express.Router();
143
+ const { validate } = require('schema-dsl');
144
+ const userSchemas = require('../schemas/user');
145
+
146
+ /**
147
+ * 用户注册
148
+ *
149
+ * ✅ 使用预定义的 schema,不再重复转换
150
+ */
151
+ router.post('/register', async (req, res) => {
152
+ // ✅ 直接使用,性能最优
153
+ const result = validate(userSchemas.register, req.body);
154
+
155
+ if (!result.valid) {
156
+ return res.status(400).json({
157
+ code: 'VALIDATION_ERROR',
158
+ message: '数据验证失败',
159
+ errors: result.errors
160
+ });
161
+ }
162
+
163
+ // 处理注册逻辑
164
+ try {
165
+ const user = await createUser(result.data);
166
+ res.status(201).json({
167
+ code: 'SUCCESS',
168
+ data: user
169
+ });
170
+ } catch (error) {
171
+ res.status(500).json({
172
+ code: 'SERVER_ERROR',
173
+ message: error.message
174
+ });
175
+ }
176
+ });
177
+
178
+ /**
179
+ * 用户登录
180
+ */
181
+ router.post('/login', async (req, res) => {
182
+ // ✅ 直接使用
183
+ const result = validate(userSchemas.login, req.body);
184
+
185
+ if (!result.valid) {
186
+ return res.status(400).json({
187
+ code: 'VALIDATION_ERROR',
188
+ errors: result.errors
189
+ });
190
+ }
191
+
192
+ // 处理登录逻辑
193
+ // ...
194
+ });
195
+
196
+ /**
197
+ * 更新个人资料
198
+ */
199
+ router.put('/profile', authenticate, async (req, res) => {
200
+ // ✅ 直接使用
201
+ const result = validate(userSchemas.updateProfile, req.body);
202
+
203
+ if (!result.valid) {
204
+ return res.status(400).json({
205
+ code: 'VALIDATION_ERROR',
206
+ errors: result.errors
207
+ });
208
+ }
209
+
210
+ // 处理更新逻辑
211
+ // ...
212
+ });
213
+
214
+ module.exports = router;
215
+ ```
216
+
217
+ ### 5. 主应用入口(app.js)
218
+
219
+ ```javascript
220
+ const express = require('express');
221
+ const app = express();
222
+
223
+ // ✅ 在应用启动时加载所有 schema(只转换一次)
224
+ const schemas = require('./schemas');
225
+ console.log('✅ Schemas loaded:', Object.keys(schemas));
226
+
227
+ // 中间件
228
+ app.use(express.json());
229
+
230
+ // 路由
231
+ app.use('/api/user', require('./routes/user'));
232
+ app.use('/api/order', require('./routes/order'));
233
+ app.use('/api/product', require('./routes/product'));
234
+
235
+ // 启动服务
236
+ const PORT = process.env.PORT || 3000;
237
+ app.listen(PORT, () => {
238
+ console.log(`✅ Server started on port ${PORT}`);
239
+ console.log('✅ All schemas are loaded and ready to validate');
240
+ });
241
+
242
+ module.exports = app;
243
+ ```
244
+
245
+ ---
246
+
247
+ ## 性能对比
248
+
249
+ ### ❌ 不推荐:每次请求都转换
250
+
251
+ ```javascript
252
+ // ❌ 错误示例
253
+ router.post('/register', (req, res) => {
254
+ const result = validate(
255
+ { // ❌ 每次请求都转换
256
+ username: 'string:3-32!',
257
+ email: 'email!',
258
+ password: dsl('string!').password('strong')
259
+ },
260
+ req.body
261
+ );
262
+ // ...
263
+ });
264
+ ```
265
+
266
+ **性能问题**:
267
+ - ❌ 每次请求都执行 DSL → JSON Schema 转换
268
+ - ❌ 1000 次请求 = 1000 次转换
269
+ - ❌ 高并发时性能损失明显
270
+
271
+ ### ✅ 推荐:项目启动时转换
272
+
273
+ ```javascript
274
+ // ✅ 正确示例
275
+ const userSchemas = require('../schemas/user'); // ✅ 启动时加载
276
+
277
+ router.post('/register', (req, res) => {
278
+ const result = validate(
279
+ userSchemas.register, // ✅ 直接使用
280
+ req.body
281
+ );
282
+ // ...
283
+ });
284
+ ```
285
+
286
+ **性能优势**:
287
+ - ✅ 启动时转换 1 次
288
+ - ✅ 1000 次请求 = 0 次转换
289
+ - ✅ 高并发时性能最优
290
+
291
+ ---
292
+
293
+ ## 使用场景总结
294
+
295
+ | 场景 | 推荐方式 | 代码示例 | 原因 |
296
+ |------|---------|---------|------|
297
+ | **生产环境 API** | ✅ 项目启动时配置 | `const schemas = require('./schemas')` | 避免每次请求都转换 |
298
+ | **高并发服务** | ✅ 项目启动时配置 | 同上 | 3-5% 的性能损失会被放大 |
299
+ | **微服务** | ✅ 项目启动时配置 | 同上 | 保证响应时间稳定 |
300
+ | **单次脚本** | ✅ 直接用 DSL 对象(当前版便捷函数支持) | `validate({ email: 'email!' }, data)` | 只执行一次,性能影响可忽略 |
301
+ | **原型开发** | ✅ 直接用 DSL 对象(当前版便捷函数支持) | 同上 | 快速迭代,无需在意性能 |
302
+ | **测试代码** | ✅ 直接用 DSL 对象(当前版便捷函数支持) | 同上 | 简洁清晰,易于维护 |
303
+
304
+ ---
305
+
306
+ ## 常见错误
307
+
308
+ ### ❌ 错误1:在路由文件中定义 schema
309
+
310
+ ```javascript
311
+ // ❌ 不推荐
312
+ router.post('/register', (req, res) => {
313
+ const schema = dsl({ // ❌ 每次请求都创建
314
+ username: 'string:3-32!',
315
+ email: 'email!'
316
+ });
317
+
318
+ const result = validate(schema, req.body);
319
+ // ...
320
+ });
321
+ ```
322
+
323
+ **问题**:每次请求都创建新的 schema 对象,浪费性能。
324
+
325
+ ### ❌ 错误2:在函数内部定义 schema
326
+
327
+ ```javascript
328
+ // ❌ 不推荐
329
+ function validateUser(data) {
330
+ const schema = dsl({ // ❌ 每次调用都创建
331
+ username: 'string:3-32!',
332
+ email: 'email!'
333
+ });
334
+
335
+ return validate(schema, data);
336
+ }
337
+ ```
338
+
339
+ **问题**:每次调用函数都创建新的 schema,应该提到函数外部。
340
+
341
+ ### ✅ 正确:在模块顶部定义
342
+
343
+ ```javascript
344
+ // ✅ 推荐:模块加载时创建一次
345
+ const userSchema = dsl({
346
+ username: 'string:3-32!',
347
+ email: 'email!'
348
+ });
349
+
350
+ router.post('/register', (req, res) => {
351
+ const result = validate(userSchema, req.body); // ✅ 直接使用
352
+ // ...
353
+ });
354
+ ```
355
+
356
+ ---
357
+
358
+ ## TypeScript 支持
359
+
360
+ ```typescript
361
+ // schemas/user.ts
362
+ import { dsl } from 'schema-dsl';
363
+
364
+ export const userSchemas = {
365
+ register: dsl({
366
+ username: dsl('string:3-32!')
367
+ .pattern(/^[a-zA-Z0-9_]+$/)
368
+ .error({ pattern: '只能包含字母、数字和下划线' }),
369
+ email: 'email!',
370
+ password: dsl('string:8-64!')
371
+ .pattern(/^(?=.*[A-Za-z])(?=.*\d).{8,}$/)
372
+ .error({ pattern: '密码至少 8 位且必须包含字母和数字' }),
373
+ age: 'number:18-120'
374
+ }),
375
+
376
+ login: dsl({
377
+ username: 'string!',
378
+ password: 'string!'
379
+ })
380
+ };
381
+
382
+ // routes/user.ts
383
+ import { validate } from 'schema-dsl';
384
+ import { userSchemas } from '../schemas/user';
385
+
386
+ router.post('/register', (req, res) => {
387
+ const result = validate(userSchemas.register, req.body);
388
+ // ...
389
+ });
390
+ ```
391
+
392
+ ---
393
+
394
+ ## 总结
395
+
396
+ **✅ 最佳实践**:
397
+ 1. 在单独的 `schemas/` 目录定义所有 schema
398
+ 2. 项目启动时加载,转换一次
399
+ 3. 路由中直接使用,不再转换
400
+ 4. 适合生产环境和高并发场景
401
+
402
+ **✅ 性能优势**:
403
+ - 避免每次请求都重复转换
404
+ - schema 复用,内存占用更小
405
+ - 响应时间更稳定
406
+
407
+ **✅ 代码优势**:
408
+ - 集中管理所有验证规则
409
+ - 易于维护和修改
410
+ - 类型安全(TypeScript)
411
+
412
+ ---
413
+
414
+ ## 对应示例文件
415
+
416
+ **示例入口**: [best-practices-project-structure.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/best-practices-project-structure.ts)
417
+ **说明**: 用一个最小的 `userSchemas` 对象模拟集中定义 / 路由复用结构,直接验证注册与登录两条路径。