schema-dsl 1.0.9 → 1.1.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 (47) hide show
  1. package/CHANGELOG.md +325 -2
  2. package/README.md +419 -189
  3. package/STATUS.md +65 -3
  4. package/docs/FEATURE-INDEX.md +1 -1
  5. package/docs/best-practices.md +3 -3
  6. package/docs/cache-manager.md +1 -1
  7. package/docs/conditional-api.md +1278 -0
  8. package/docs/dsl-syntax.md +1 -1
  9. package/docs/dynamic-locale.md +2 -2
  10. package/docs/error-handling.md +2 -2
  11. package/docs/export-guide.md +2 -2
  12. package/docs/export-limitations.md +3 -3
  13. package/docs/faq.md +6 -6
  14. package/docs/frontend-i18n-guide.md +1 -1
  15. package/docs/mongodb-exporter.md +3 -3
  16. package/docs/multi-type-support.md +12 -2
  17. package/docs/mysql-exporter.md +1 -1
  18. package/docs/plugin-system.md +4 -4
  19. package/docs/postgresql-exporter.md +1 -1
  20. package/docs/quick-start.md +4 -4
  21. package/docs/troubleshooting.md +2 -2
  22. package/docs/type-reference.md +5 -5
  23. package/docs/typescript-guide.md +5 -6
  24. package/docs/union-type-guide.md +147 -0
  25. package/docs/union-types.md +277 -0
  26. package/docs/validate-async.md +1 -1
  27. package/examples/array-dsl-example.js +1 -1
  28. package/examples/conditional-example.js +288 -0
  29. package/examples/conditional-non-object.js +129 -0
  30. package/examples/conditional-validate-example.js +321 -0
  31. package/examples/i18n-error.examples.js +181 -0
  32. package/examples/union-type-example.js +127 -0
  33. package/examples/union-types-example.js +77 -0
  34. package/index.d.ts +655 -7
  35. package/index.js +54 -3
  36. package/lib/adapters/DslAdapter.js +14 -5
  37. package/lib/core/ConditionalBuilder.js +503 -0
  38. package/lib/core/DslBuilder.js +113 -0
  39. package/lib/core/Locale.js +13 -8
  40. package/lib/core/Validator.js +250 -2
  41. package/lib/errors/I18nError.js +222 -0
  42. package/lib/locales/en-US.js +39 -0
  43. package/lib/locales/es-ES.js +4 -0
  44. package/lib/locales/fr-FR.js +4 -0
  45. package/lib/locales/ja-JP.js +9 -0
  46. package/lib/locales/zh-CN.js +39 -0
  47. package/package.json +3 -1
package/README.md CHANGED
@@ -181,12 +181,50 @@ if (result.valid) {
181
181
  |------|-----------|------|
182
182
  | **基本验证** | ✅ | string、number、boolean、date、email、url... |
183
183
  | **高级验证** | ✅ | 正则、自定义、条件、嵌套、数组... |
184
+ | **🆕 跨类型联合** | ✅ | `types:string|number` 一个字段支持多种类型 (v1.1.1) |
184
185
  | **错误格式化** | ✅ | 自动多语言翻译 |
186
+ | **🆕 多语言错误** | ✅ | `I18nError` 统一的多语言错误抛出 (v1.1.1) |
185
187
  | **数据库导出** | ✅ | MongoDB、MySQL、PostgreSQL |
186
188
  | **TypeScript** | ✅ | 完整类型定义 |
187
189
  | **性能优化** | ✅ | WeakMap 缓存、智能编译 |
190
+ | **插件系统** | ✅ | 支持自定义类型注册 (v1.1.1) |
188
191
  | **文档生成** | ✅ | Markdown、HTML |
189
192
 
193
+ ### 🆕 v1.1.0 新特性:跨类型联合验证
194
+
195
+ **一行代码支持多种类型**
196
+
197
+ ```javascript
198
+ const { dsl, validate } = require('schema-dsl');
199
+
200
+ // 字段可以是字符串或数字
201
+ const schema = dsl({
202
+ value: 'types:string|number'
203
+ });
204
+
205
+ validate(schema, { value: 'hello' }); // ✅ 通过
206
+ validate(schema, { value: 123 }); // ✅ 通过
207
+ validate(schema, { value: true }); // ❌ 失败
208
+
209
+ // 带约束的联合类型
210
+ const advancedSchema = dsl({
211
+ contact: 'types:email|phone!', // 邮箱或手机号
212
+ price: 'types:number:0-|string:1-20' // 数字价格或"面议"
213
+ });
214
+ ```
215
+
216
+ **实际场景示例**:
217
+ ```javascript
218
+ // 用户注册:支持邮箱或手机号
219
+ const registerSchema = dsl({
220
+ username: 'string:3-20!',
221
+ contact: 'types:email|phone!', // 灵活的联系方式
222
+ age: 'types:integer:1-150|null' // 年龄可选
223
+ });
224
+ ```
225
+
226
+ 📖 [完整文档](./docs/union-types.md) | [插件开发指南](./docs/plugin-type-registration.md)
227
+
190
228
  ---
191
229
 
192
230
  ## 📦 安装
@@ -377,6 +415,146 @@ const registerSchema = SchemaUtils
377
415
  // partial - 变为可选(用于更新接口)
378
416
  ```
379
417
 
418
+ ### 条件验证 - 一行代码搞定
419
+
420
+ **问题场景**:不同情况需要不同的验证规则
421
+
422
+ ```javascript
423
+ const { dsl } = require('schema-dsl');
424
+
425
+ // 场景1:年龄限制 - 未成年不能注册
426
+ // ❌ 传统做法:先验证,再判断,写两次
427
+ const result = validate(schema, userData);
428
+ if (!result.valid) return;
429
+ if (userData.age < 18) {
430
+ throw new Error('未成年用户不能注册');
431
+ }
432
+
433
+ // ✅ 新做法:一行代码搞定
434
+ dsl.if(d => d.age < 18)
435
+ .message('未成年用户不能注册')
436
+ .assert(userData); // 失败自动抛错
437
+
438
+ // 场景2:权限检查 - 快速判断
439
+ // ❌ 传统做法:写 if 判断
440
+ if (user.role !== 'admin' && user.role !== 'moderator') {
441
+ return res.status(403).json({ error: '权限不足' });
442
+ }
443
+
444
+ // ✅ 新做法:一行搞定
445
+ if (!dsl.if(d => d.role === 'admin' || d.role === 'moderator')
446
+ .message('权限不足')
447
+ .check(user)) {
448
+ return res.status(403).json({ error: '权限不足' });
449
+ }
450
+
451
+ // 场景3:批量过滤 - 筛选符合条件的数据
452
+ // ❌ 传统做法:写 filter 函数
453
+ const adults = users.filter(u => u.age >= 18);
454
+
455
+ // ✅ 新做法:语义更清晰
456
+ const adults = users.filter(u =>
457
+ !dsl.if(d => d.age < 18).message('未成年').check(u)
458
+ );
459
+ ```
460
+
461
+ #### 四种方法,满足不同场景
462
+
463
+ | 方法 | 什么时候用 | 返回什么 | 示例 |
464
+ |------|-----------|---------|------|
465
+ | **`.validate()`** | 需要知道错误详情 | `{ valid, errors, data }` | 表单验证 |
466
+ | **`.validateAsync()`** | async/await 场景 | Promise(失败抛错) | Express 中间件 |
467
+ | **`.assert()`** | 快速失败,不想写 if | 失败直接抛错 | 函数入口检查 |
468
+ | **`.check()`** | 只需要判断真假 | `true/false` | 数据过滤 |
469
+
470
+ #### 实际例子
471
+
472
+ **表单验证 - 需要显示错误**
473
+
474
+ ```javascript
475
+ // 使用 .validate() 获取错误详情
476
+ const result = dsl.if(d => d.age < 18)
477
+ .message('未成年用户不能注册')
478
+ .validate(formData);
479
+
480
+ if (!result.valid) {
481
+ showError(result.errors[0].message); // 显示给用户
482
+ }
483
+ ```
484
+
485
+ **Express 中间件 - 异步验证**
486
+
487
+ ```javascript
488
+ // 使用 .validateAsync() 失败自动抛错
489
+ app.post('/register', async (req, res, next) => {
490
+ try {
491
+ await dsl.if(d => d.age < 18)
492
+ .message('未成年用户不能注册')
493
+ .validateAsync(req.body);
494
+
495
+ // 验证通过,继续处理
496
+ const user = await createUser(req.body);
497
+ res.json(user);
498
+ } catch (error) {
499
+ next(error); // 自动传递给错误处理中间件
500
+ }
501
+ });
502
+ ```
503
+
504
+ **函数参数检查 - 快速断言**
505
+
506
+ ```javascript
507
+ // 使用 .assert() 不满足直接抛错
508
+ function registerUser(userData) {
509
+ // 入口检查,不满足直接抛错,代码更清晰
510
+ dsl.if(d => d.age < 18).message('未成年不能注册').assert(userData);
511
+ dsl.if(d => !d.email).message('邮箱必填').assert(userData);
512
+ dsl.if(d => !d.phone).message('手机号必填').assert(userData);
513
+
514
+ // 检查通过,继续业务逻辑
515
+ return createUser(userData);
516
+ }
517
+ ```
518
+
519
+ **批量数据处理 - 快速过滤**
520
+
521
+ ```javascript
522
+ // 使用 .check() 只返回 true/false
523
+ const canRegister = dsl.if(d => d.age < 18)
524
+ .or(d => d.status === 'blocked')
525
+ .message('不允许注册');
526
+
527
+ // 过滤出可以注册的用户
528
+ const validUsers = users.filter(u => !canRegister.check(u));
529
+
530
+ // 统计未成年用户数量
531
+ const minorCount = users.filter(u =>
532
+ dsl.if(d => d.age < 18).message('未成年').check(u)
533
+ ).length;
534
+ ```
535
+
536
+ **复用验证器**
537
+
538
+ ```javascript
539
+ // 创建一次,到处使用
540
+ const ageValidator = dsl.if(d => d.age < 18)
541
+ .message('未成年用户不能注册');
542
+
543
+ // 不同场景使用不同方法
544
+ const r1 = ageValidator.validate({ age: 16 }); // 同步,返回详情
545
+ const r2 = await ageValidator.validateAsync(data); // 异步,失败抛错
546
+ const r3 = ageValidator.check({ age: 20 }); // 快速判断
547
+ ```
548
+
549
+ #### 💡 选择建议
550
+
551
+ - 🎯 **表单验证**:用 `.validate()` - 需要显示错误给用户
552
+ - 🚀 **API 接口**:用 `.validateAsync()` - 配合 try/catch
553
+ - ⚡ **函数入口**:用 `.assert()` - 快速失败,代码简洁
554
+ - 🔍 **数据过滤**:用 `.check()` - 只需要判断真假
555
+
556
+ **完整文档**: [ConditionalBuilder API](./docs/conditional-api.md)
557
+
380
558
  ---
381
559
 
382
560
  ## 📖 DSL 语法速查
@@ -922,241 +1100,293 @@ try {
922
1100
 
923
1101
  ---
924
1102
 
925
- ## 🎯 适用场景
926
-
927
- ### ✅ 特别适合
1103
+ ## 常见问题 FAQ
928
1104
 
929
- - 🚀 **快速开发** - API 开发、表单验证,追求开发效率
930
- - 🌍 **国际化项目** - 需要完整的多语言错误消息支持
931
- - 🗄️ **全栈开发** - 需要从 Schema 自动生成数据库表结构
932
- - 📋 **配置驱动** - 验证规则需要从配置文件或数据库动态读取
933
- - 🏢 **中小型项目** - Node.js + Express/Koa/Egg.js 后端项目
1105
+ ### Q1: 如何判断数据不能为空?(类似 `if(!data)`)
934
1106
 
935
- ### 💡 使用场景示例
1107
+ **方案1:使用必填标记**(推荐)
1108
+ ```javascript
1109
+ const schema = dsl({
1110
+ username: 'string!', // 必填,不能为空
1111
+ email: 'email!'
1112
+ });
1113
+ ```
936
1114
 
937
- **RESTful API 开发**
1115
+ **方案2:使用条件验证 + 抛错**
938
1116
  ```javascript
939
- // 统一的验证中间件
940
- const validateMiddleware = (schema) => {
941
- return async (req, res, next) => {
942
- try {
943
- req.body = await validateAsync(schema, req.body);
944
- next();
945
- } catch (error) {
946
- next(error);
947
- }
948
- };
949
- };
1117
+ // 验证失败自动抛错
1118
+ dsl.if(d => !d)
1119
+ .message('数据不能为空')
1120
+ .assert(data);
1121
+ ```
950
1122
 
951
- app.post('/api/users',
952
- validateMiddleware(createUserSchema),
953
- userController.create
954
- );
1123
+ **方案3:异步验证**
1124
+ ```javascript
1125
+ // Express/Koa 推荐
1126
+ await dsl.if(d => !d)
1127
+ .message('数据不能为空')
1128
+ .validateAsync(data);
955
1129
  ```
956
1130
 
957
- **表单验证**
1131
+ ---
1132
+
1133
+ ### Q2: 如何判断数据是否是对象?(类似 `typeof data === 'object'`)
1134
+
1135
+ **方案1:使用内置 object 类型**(推荐)
958
1136
  ```javascript
959
- // 前端也可以使用(支持浏览器)
960
- const formSchema = dsl({
961
- username: 'string:3-32!',
962
- email: 'email!',
963
- password: 'string:8-32!',
964
- confirmPassword: 'string!'
1137
+ const schema = dsl({
1138
+ data: 'object!' // 必须是对象(排除 null 和 array)
965
1139
  });
966
1140
 
967
- const result = validate(formSchema, formData);
968
- if (!result.valid) {
969
- // 显示错误消息
970
- showErrors(result.errors);
971
- }
1141
+ validate(schema, { data: { name: 'John' } }); // ✅ 通过
1142
+ validate(schema, { data: 'string' }); // ❌ 失败
1143
+ validate(schema, { data: [] }); // ❌ 失败
972
1144
  ```
973
1145
 
974
- **动态配置验证**
1146
+ **方案2:条件验证 + 抛错**
975
1147
  ```javascript
976
- // 从数据库读取验证规则
977
- const rules = await db.validationRules.find({ formId: 'user-register' });
978
-
979
- // 动态构建 Schema
980
- const dynamicSchema = dsl(
981
- rules.reduce((schema, rule) => {
982
- schema[rule.field] = rule.dsl;
983
- return schema;
984
- }, {})
985
- );
1148
+ dsl.if(d => typeof d !== 'object' || d === null || Array.isArray(d))
1149
+ .message('data 必须是一个对象')
1150
+ .assert(data);
1151
+ ```
1152
+
1153
+ **方案3:带结构验证**
1154
+ ```javascript
1155
+ const schema = dsl({
1156
+ data: {
1157
+ name: 'string!',
1158
+ age: 'integer!',
1159
+ email: 'email'
1160
+ }
1161
+ });
1162
+
1163
+ await validateAsync(schema, input); // 验证对象结构
986
1164
  ```
987
1165
 
988
1166
  ---
989
1167
 
990
- ## 性能对比
1168
+ ### Q3: 如何验证嵌套对象?
1169
+
1170
+ ```javascript
1171
+ const schema = dsl({
1172
+ user: {
1173
+ profile: 'object!', // profile 必须是对象
1174
+ settings: {
1175
+ theme: 'string',
1176
+ notifications: 'object!' // 嵌套对象验证
1177
+ }
1178
+ }
1179
+ });
1180
+ ```
991
1181
 
992
- **测试环境**: Node.js 18, 10,000 次验证
1182
+ ---
993
1183
 
994
- | 库名 | 速度 (ops/sec) | 相对速度 |
995
- |------|---------------|---------|
996
- | Ajv | 2,000,000 | 🥇 最快 |
997
- | Zod | 526,316 | 🥈 很快 |
998
- | **schema-dsl** | **277,778** | 🥉 **快** |
999
- | Joi | 97,087 | 中等 |
1000
- | Yup | 60,241 | 较慢 |
1184
+ ### Q4: 如何在 Express/Koa 中使用?
1001
1185
 
1002
- **结论**:
1003
- - Joi **2.86倍**
1004
- - ✅ 比 Yup 快 **4.61倍**
1005
- - 对 99% 的应用场景足够快(27万+次/秒)
1006
- - ⚠️ 如果需要极致性能(100万+次/秒),推荐使用 Ajv
1186
+ ```javascript
1187
+ app.post('/api/user', async (req, res) => {
1188
+ try {
1189
+ // 1. 验证请求体是对象
1190
+ await dsl.if(d => typeof d !== 'object' || d === null)
1191
+ .message('请求体必须是对象')
1192
+ .validateAsync(req.body);
1193
+
1194
+ // 2. 验证字段
1195
+ const schema = dsl({
1196
+ username: 'string:3-32!',
1197
+ email: 'email!',
1198
+ password: 'string:8-!'
1199
+ });
1200
+
1201
+ const validData = await validateAsync(schema, req.body);
1202
+
1203
+ // 继续处理...
1204
+ res.json({ success: true, data: validData });
1205
+ } catch (error) {
1206
+ res.status(400).json({ error: error.message });
1207
+ }
1208
+ });
1209
+ ```
1007
1210
 
1008
1211
  ---
1009
1212
 
1010
- ## 🆚 与其他库对比
1213
+ ### Q5: 如何自定义错误消息?
1011
1214
 
1012
- ### 选择建议
1215
+ ```javascript
1216
+ const schema = dsl({
1217
+ username: dsl('string:3-32!')
1218
+ .label('用户名')
1219
+ .messages({
1220
+ minLength: '用户名至少需要 {{#limit}} 个字符',
1221
+ required: '用户名不能为空'
1222
+ }),
1223
+
1224
+ email: dsl('email!')
1225
+ .label('邮箱地址')
1226
+ .messages({
1227
+ format: '请输入有效的邮箱地址',
1228
+ required: '邮箱不能为空'
1229
+ })
1230
+ });
1231
+ ```
1013
1232
 
1014
- | 项目需求 | 推荐方案 | 原因 |
1015
- |---------|---------|------|
1016
- | 快速开发,减少代码量 | **schema-dsl** | 代码量最少,学习成本最低 |
1017
- | TypeScript 强类型推断 | Zod | 最佳的 TypeScript 支持 |
1018
- | 极致性能要求 | Ajv | 性能最强 |
1019
- | 企业级成熟方案 | Joi | 社区最大,经过大规模验证 |
1020
- | 多语言 + 数据库导出 | **schema-dsl** | 独家功能 |
1233
+ ---
1021
1234
 
1022
- ### 详细对比
1235
+ ### Q6: 类型对照表
1023
1236
 
1024
- <table>
1025
- <tr>
1026
- <th>特性</th>
1027
- <th>schema-dsl</th>
1028
- <th>Joi</th>
1029
- <th>Yup</th>
1030
- <th>Zod</th>
1031
- <th>Ajv</th>
1032
- </tr>
1033
- <tr>
1034
- <td><strong>语法简洁度</strong></td>
1035
- <td>⭐⭐⭐⭐⭐<br>一行代码</td>
1036
- <td>⭐⭐<br>链式调用冗长</td>
1037
- <td>⭐⭐<br>链式调用冗长</td>
1038
- <td>⭐⭐⭐<br>相对简洁</td>
1039
- <td>⭐⭐<br>JSON 配置繁琐</td>
1040
- </tr>
1041
- <tr>
1042
- <td><strong>学习成本</strong></td>
1043
- <td>⭐⭐⭐⭐⭐<br>5分钟</td>
1044
- <td>⭐⭐⭐<br>30分钟</td>
1045
- <td>⭐⭐⭐<br>30分钟</td>
1046
- <td>⭐⭐⭐⭐<br>15分钟</td>
1047
- <td>⭐⭐⭐<br>20分钟</td>
1048
- </tr>
1049
- <tr>
1050
- <td><strong>性能(简单验证)</strong></td>
1051
- <td>⭐⭐⭐⭐<br>55.6万/秒</td>
1052
- <td>⭐⭐⭐<br>23.3万/秒</td>
1053
- <td>⭐⭐<br>18.9万/秒</td>
1054
- <td>⭐⭐⭐⭐⭐<br>100万/秒</td>
1055
- <td>⭐⭐⭐⭐⭐<br>250万/秒</td>
1056
- </tr>
1057
- <tr>
1058
- <td><strong>性能(复杂验证)</strong></td>
1059
- <td>⭐⭐⭐⭐⭐<br>62.5万/秒</td>
1060
- <td>⭐⭐⭐<br>12.5万/秒</td>
1061
- <td>⭐⭐<br>5.5万/秒</td>
1062
- <td>⭐⭐⭐⭐<br>38.5万/秒</td>
1063
- <td>⭐⭐⭐⭐⭐<br>250万/秒</td>
1064
- </tr>
1065
- <tr>
1066
- <td><strong>TypeScript 支持</strong></td>
1067
- <td>⭐⭐⭐<br>.d.ts 类型定义</td>
1068
- <td>⭐⭐⭐<br>.d.ts 类型定义</td>
1069
- <td>⭐⭐⭐<br>.d.ts 类型定义</td>
1070
- <td>⭐⭐⭐⭐⭐<br>完美类型推断</td>
1071
- <td>⭐⭐<br>基础支持</td>
1072
- </tr>
1073
- <tr>
1074
- <td><strong>数据库导出</strong></td>
1075
- <td>✅ MongoDB<br>✅ MySQL<br>✅ PostgreSQL</td>
1076
- <td>❌</td>
1077
- <td>❌</td>
1078
- <td>❌</td>
1079
- <td>❌</td>
1080
- </tr>
1081
- <tr>
1082
- <td><strong>多语言支持</strong></td>
1083
- <td>✅ 完整支持<br>可自定义语言包</td>
1084
- <td>⚠️ 基础支持</td>
1085
- <td>⚠️ 基础支持</td>
1086
- <td>⚠️ 基础支持</td>
1087
- <td>⚠️ 基础支持</td>
1088
- </tr>
1089
- <tr>
1090
- <td><strong>文档生成</strong></td>
1091
- <td>✅ Markdown<br>✅ HTML</td>
1092
- <td>❌</td>
1093
- <td>❌</td>
1094
- <td>❌</td>
1095
- <td>❌</td>
1096
- </tr>
1097
- <tr>
1098
- <td><strong>社区规模</strong></td>
1099
- <td>⭐⭐⭐<br>成长中</td>
1100
- <td>⭐⭐⭐⭐⭐<br>最大</td>
1101
- <td>⭐⭐⭐⭐<br>很大</td>
1102
- <td>⭐⭐⭐⭐<br>快速增长</td>
1103
- <td>⭐⭐⭐⭐<br>成熟</td>
1104
- </tr>
1105
- </table>
1237
+ | JavaScript 条件 | schema-dsl 写法 |
1238
+ |----------------|----------------|
1239
+ | `if (!data)` | `'string!'` 或 `.assert(data)` |
1240
+ | `if (typeof data === 'object')` | `'object!'` |
1241
+ | `if (typeof data === 'string')` | `'string!'` |
1242
+ | `if (typeof data === 'number')` | `'number!'` |
1243
+ | `if (Array.isArray(data))` | `'array!'` |
1244
+ | `if (data === null)` | `'null!'` |
1245
+ | `if (data > 0)` | `'number:0-!'` |
1246
+ | `if (data.length >= 3)` | `'string:3-!'` |
1106
1247
 
1107
1248
  ---
1108
1249
 
1109
- ## 📚 完整文档
1250
+ ### Q7: 如何合并多个 dsl.if() 验证?
1110
1251
 
1111
- ### 核心文档
1112
- - [快速开始](./docs/quick-start.md) - 5分钟上手指南
1113
- - [DSL 语法完整参考](./docs/dsl-syntax.md) - 所有语法详解
1114
- - [API 文档](./docs/api-reference.md) - 完整 API 说明
1115
- - [**TypeScript 使用指南**](./docs/typescript-guide.md) - TypeScript 最佳实践 ⭐
1252
+ **原代码(多个独立验证)**:
1253
+ ```javascript
1254
+ dsl.if(d => !d)
1255
+ .message('ACCOUNT_NOT_FOUND')
1256
+ .assert(account);
1116
1257
 
1117
- ### 功能指南
1118
- - [String 扩展方法](./docs/string-extensions.md) - 链式调用详解
1119
- - [Schema 复用](./docs/schema-utils.md) - omit/pick/extend/partial
1120
- - [异步验证](./docs/validate-async.md) - validateAsync 使用指南
1121
- - [错误处理](./docs/error-handling.md) - ValidationError 详解
1122
- - [多语言支持](./docs/i18n.md) - 国际化配置指南
1123
- - [插件开发](./docs/plugin-system.md) - 自定义插件教程
1258
+ dsl.if(d => d.tradable_credits < amount)
1259
+ .message('INSUFFICIENT_TRADABLE_CREDITS')
1260
+ .assert(account.tradable_credits);
1261
+ ```
1124
1262
 
1125
- ### 导出功能
1126
- - [MongoDB 导出](./docs/mongodb-exporter.md) - MongoDB Schema 生成
1127
- - [MySQL 导出](./docs/mysql-exporter.md) - MySQL DDL 生成
1128
- - [PostgreSQL 导出](./docs/postgresql-exporter.md) - PostgreSQL DDL 生成
1129
- - [Markdown 导出](./docs/markdown-exporter.md) - API 文档生成
1263
+ **✅ 方案1:使用 .and() 链式合并(v1.1.1 推荐)**
1264
+ ```javascript
1265
+ // 每个条件都有独立的错误消息
1266
+ dsl.if(d => !d)
1267
+ .message('ACCOUNT_NOT_FOUND')
1268
+ .and(d => d.tradable_credits < amount)
1269
+ .message('INSUFFICIENT_TRADABLE_CREDITS')
1270
+ .assert(account);
1271
+
1272
+ // 工作原理:
1273
+ // - 第一个条件失败 → 返回 'ACCOUNT_NOT_FOUND'
1274
+ // - 第二个条件失败 → 返回 'INSUFFICIENT_TRADABLE_CREDITS'
1275
+ // - 所有条件通过 → 验证成功
1276
+ ```
1130
1277
 
1131
- ### 集成示例
1132
- - [Express 集成](./examples/express-integration.js)
1278
+ **✅ 方案2:使用 .elseIf() 分支验证**
1279
+ ```javascript
1280
+ // ✅ 按优先级检查,找到第一个失败的
1281
+ dsl.if(d => !d)
1282
+ .message('ACCOUNT_NOT_FOUND')
1283
+ .elseIf(d => d.tradable_credits < amount)
1284
+ .message('INSUFFICIENT_TRADABLE_CREDITS')
1285
+ .assert(account);
1286
+ ```
1287
+
1288
+ **✅ 方案3:保持独立验证**(最清晰)
1289
+ ```javascript
1290
+ // ✅ 两个独立的验证器
1291
+ dsl.if(d => !d).message('ACCOUNT_NOT_FOUND').assert(account);
1292
+ dsl.if(d => d.tradable_credits < amount)
1293
+ .message('INSUFFICIENT_TRADABLE_CREDITS')
1294
+ .assert(account.tradable_credits);
1295
+ ```
1296
+
1297
+ **⚠️ 注意事项**:
1298
+ - `.and()` 用于组合多个条件,每个条件可以有**独立的** `.message()` (v1.1.1)
1299
+ - 如果 `.and()` 后不调用 `.message()`,则使用前一个条件的消息
1300
+ - `.elseIf()` 按顺序检查,找到第一个失败的就停止(if-else-if 逻辑)
1301
+
1302
+ **何时使用**:
1303
+ - ✅ 使用 `.and()` - 多个条件,每个有不同错误消息(v1.1.1)
1304
+ - ✅ 使用 `.elseIf()` - 不同分支有不同验证规则
1305
+ - ✅ 独立验证 - 最清晰,最可靠
1306
+
1307
+ **实际应用示例**:
1308
+ ```javascript
1309
+ // 账户验证:检查存在性 + 余额 + 状态
1310
+ dsl.if(d => !d)
1311
+ .message('ACCOUNT_NOT_FOUND')
1312
+ .and(d => d.status !== 'active')
1313
+ .message('ACCOUNT_INACTIVE')
1314
+ .and(d => d.tradable_credits < amount)
1315
+ .message('INSUFFICIENT_TRADABLE_CREDITS')
1316
+ .assert(account);
1317
+
1318
+ // 每个失败条件都有清晰的错误消息!
1319
+ ```
1320
+
1321
+ 📖 更多示例请查看 [完整文档](./docs/INDEX.md)
1133
1322
 
1134
1323
  ---
1135
1324
 
1136
- ## 💻 示例代码
1325
+ ### Q8: 如何统一抛出多语言错误?(v1.1.1+)
1137
1326
 
1138
- 项目包含 30+ 完整示例,涵盖所有功能:
1327
+ **问题**: 业务代码中抛出的错误无法多语言,与 `.message()` 和 `.label()` 不一致
1139
1328
 
1140
- ```bash
1141
- # 安装依赖(首次运行)
1142
- npm install
1329
+ **✅ 解决方案:使用 `I18nError` 或 `dsl.error`**
1143
1330
 
1144
- # 查看所有示例
1145
- ls examples/
1331
+ ```javascript
1332
+ const { I18nError, dsl } = require('schema-dsl');
1146
1333
 
1147
- # 运行基础示例
1148
- node examples/simple-example.js
1334
+ // 方式1:直接抛出
1335
+ I18nError.throw('account.notFound');
1336
+ // 中文: "账户不存在"
1337
+ // 英文: "Account not found"
1149
1338
 
1150
- # 运行数据库导出示例
1151
- node examples/export-demo.js
1339
+ // 方式2:带参数插值
1340
+ I18nError.throw('account.insufficientBalance', {
1341
+ balance: 50,
1342
+ required: 100
1343
+ });
1344
+ // 输出: "余额不足,当前余额50,需要100"
1345
+
1346
+ // 方式3:断言风格(推荐)
1347
+ I18nError.assert(account, 'account.notFound');
1348
+ I18nError.assert(
1349
+ account.balance >= 100,
1350
+ 'account.insufficientBalance',
1351
+ { balance: account.balance, required: 100 }
1352
+ );
1152
1353
 
1153
- # 运行 Express 集成示例
1154
- node examples/express-integration.js
1354
+ // 方式4:快捷方法
1355
+ dsl.error.throw('user.noPermission');
1356
+ dsl.error.assert(user.role === 'admin', 'user.noPermission');
1357
+ ```
1358
+
1359
+ **Express/Koa 集成**:
1360
+ ```javascript
1361
+ // 错误处理中间件
1362
+ app.use((error, req, res, next) => {
1363
+ if (error instanceof I18nError) {
1364
+ return res.status(error.statusCode).json(error.toJSON());
1365
+ }
1366
+ next(error);
1367
+ });
1155
1368
 
1156
- # 🆕 v1.0.3 新增:运行 slug 类型示例
1157
- node examples/slug.examples.js
1369
+ // 业务代码中使用
1370
+ app.post('/withdraw', (req, res) => {
1371
+ const account = getAccount(req.user.id);
1372
+ I18nError.assert(account, 'account.notFound');
1373
+ I18nError.assert(
1374
+ account.balance >= req.body.amount,
1375
+ 'account.insufficientBalance',
1376
+ { balance: account.balance, required: req.body.amount }
1377
+ );
1378
+ // ...
1379
+ });
1158
1380
  ```
1159
1381
 
1382
+ **内置错误代码**:
1383
+ - 通用: `error.notFound`, `error.forbidden`, `error.unauthorized`
1384
+ - 账户: `account.notFound`, `account.insufficientBalance`
1385
+ - 用户: `user.notFound`, `user.noPermission`
1386
+ - 订单: `order.notPaid`, `order.paymentMissing`
1387
+
1388
+ 📖 完整文档请查看 [examples/i18n-error.examples.js](./examples/i18n-error.examples.js)
1389
+
1160
1390
  ---
1161
1391
 
1162
1392
  ## 🤝 贡献指南