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
@@ -0,0 +1,1278 @@
1
+ # 链式条件 API - ConditionalBuilder
2
+
3
+ > **版本**: v1.1.1
4
+ > **更新日期**: 2026-01-06
5
+ > **状态**: ✅ 稳定
6
+
7
+ ---
8
+
9
+ ## 📋 目录
10
+
11
+ - [概述](#概述)
12
+ - [🆕 v1.1.1 新功能](#-v110-新功能)
13
+ - [快速开始](#快速开始)
14
+ - [API 参考](#api-参考)
15
+ - [使用场景](#使用场景)
16
+ - [最佳实践](#最佳实践)
17
+ - [常见问题](#常见问题)
18
+
19
+ ---
20
+
21
+ ## 概述
22
+
23
+ `ConditionalBuilder` 提供流畅的链式条件判断 API,类似 JavaScript 的 if-else 语句,用于在验证时根据实际数据动态调整验证规则。
24
+
25
+ ### 核心特性
26
+
27
+ - ✅ **链式调用** - 流畅的 API,类似 JavaScript if-else
28
+ - ✅ **运行时执行** - 在验证时根据实际数据判断
29
+ - ✅ **多条件组合** - 支持 and/or 逻辑组合
30
+ - ✅ **🆕 独立消息** - v1.1.1+ 每个 .and()/.or() 可有独立错误消息
31
+ - ✅ **else 可选** - 不写 else 就不验证
32
+ - ✅ **简化设计** - message 自动抛错,无需 throwError()
33
+ - ✅ **完全兼容** - 不影响现有 API
34
+
35
+ ---
36
+
37
+ ## 🆕 v1.1.1 新功能
38
+
39
+ ### 独立消息支持 - `.and()/.or()` 后可调用 `.message()`
40
+
41
+ **每个条件都可以有自己的错误消息**
42
+
43
+ v1.1.1 开始,支持在 `.and()` 和 `.or()` 后调用 `.message()` 设置独立的错误消息,让错误提示更精确。
44
+
45
+ #### 基础用法
46
+
47
+ ```javascript
48
+ const { dsl } = require('schema-dsl');
49
+
50
+ // ✅ v1.1.1+ 新功能:每个条件独立消息
51
+ dsl.if(d => !d)
52
+ .message('ACCOUNT_NOT_FOUND')
53
+ .and(d => d.tradable_credits < amount)
54
+ .message('INSUFFICIENT_TRADABLE_CREDITS')
55
+ .assert(account);
56
+
57
+ // 工作原理:
58
+ // - 第一个条件为 true → 返回 'ACCOUNT_NOT_FOUND'
59
+ // - 第二个条件为 true → 返回 'INSUFFICIENT_TRADABLE_CREDITS'
60
+ // - 所有条件为 false → 验证成功
61
+ ```
62
+
63
+ #### 多个 .and() 条件
64
+
65
+ ```javascript
66
+ // 多层验证,每层都有清晰的错误消息
67
+ dsl.if(d => !d)
68
+ .message('ACCOUNT_NOT_FOUND')
69
+ .and(d => d.status !== 'active')
70
+ .message('ACCOUNT_INACTIVE')
71
+ .and(d => d.tradable_credits < amount)
72
+ .message('INSUFFICIENT_TRADABLE_CREDITS')
73
+ .assert(account);
74
+
75
+ // 依次检查,第一个为 true 的条件返回其消息
76
+ ```
77
+
78
+ #### .or() 条件独立消息
79
+
80
+ ```javascript
81
+ // OR 条件也支持独立消息
82
+ dsl.if(d => d.age < 18)
83
+ .message('未成年用户不能注册')
84
+ .or(d => d.isBlocked)
85
+ .message('账户已被封禁')
86
+ .assert(data);
87
+
88
+ // 任一条件为 true 就失败,返回对应消息
89
+ ```
90
+
91
+ #### 链式检查模式
92
+
93
+ v1.1.1 引入了**链式检查模式**,当满足以下条件时自动启用:
94
+
95
+ 1. 使用 `.message()` 模式(不是 `.then()`/`.else()`)
96
+ 2. root 条件有 `.message()`
97
+ 3. 有 `.and()` 条件
98
+ 4. 没有 `.or()` 条件
99
+
100
+ **链式检查模式特点**:
101
+ - 依次检查每个条件
102
+ - 第一个为 `true` 的条件失败,返回其消息
103
+ - 所有条件为 `false` 时验证通过
104
+
105
+ **示例对比**:
106
+
107
+ ```javascript
108
+ // ✅ 启用链式检查(纯 AND 场景)
109
+ dsl.if(d => !d).message('A').and(d => d < 100).message('B')
110
+
111
+ // ❌ 不启用(有 .or(),使用传统 AND/OR 逻辑)
112
+ dsl.if(d => !d).message('A').and(d => d < 100).or(d => d > 200).message('B')
113
+
114
+ // ❌ 不启用(使用 .then()/.else(),不是 message 模式)
115
+ dsl.if(d => d.age >= 18).and(d => d.role === 'admin').then('email!')
116
+ ```
117
+
118
+ #### 向后兼容性
119
+
120
+ **100% 向后兼容**,不影响现有代码:
121
+
122
+ ```javascript
123
+ // ✅ 原有用法继续工作
124
+ dsl.if(d => d.age >= 18).and(d => d.role === 'admin').message('不符合条件')
125
+
126
+ // ✅ .and() 后不调用 .message() 也可以
127
+ dsl.if(d => !d).message('整体错误').and(d => d < 100).assert(50)
128
+ // → 使用整体消息 '整体错误'
129
+ ```
130
+
131
+ #### 实际应用场景
132
+
133
+ **场景1:账户验证**
134
+ ```javascript
135
+ function validateAccount(account, amount) {
136
+ dsl.if(d => !d)
137
+ .message('ACCOUNT_NOT_FOUND')
138
+ .and(d => d.status !== 'active')
139
+ .message('ACCOUNT_INACTIVE')
140
+ .and(d => d.balance < amount)
141
+ .message('INSUFFICIENT_BALANCE')
142
+ .assert(account);
143
+ }
144
+
145
+ // 每个失败点都有清晰的错误消息
146
+ ```
147
+
148
+ **场景2:用户权限验证**
149
+ ```javascript
150
+ function validateUserPermission(user) {
151
+ dsl.if(d => d.role !== 'admin')
152
+ .message('NO_ADMIN_PERMISSION')
153
+ .and(d => !d.isVerified)
154
+ .message('USER_NOT_VERIFIED')
155
+ .and(d => d.isBanned)
156
+ .message('USER_BANNED')
157
+ .assert(user);
158
+ }
159
+ ```
160
+
161
+ **场景3:订单状态检查**
162
+ ```javascript
163
+ function validateOrder(order) {
164
+ dsl.if(d => d.status !== 'paid')
165
+ .message('ORDER_NOT_PAID')
166
+ .and(d => !d.payment)
167
+ .message('PAYMENT_INFO_MISSING')
168
+ .and(d => !d.shippingAddress)
169
+ .message('SHIPPING_ADDRESS_MISSING')
170
+ .assert(order);
171
+ }
172
+ ```
173
+
174
+ ---
175
+
176
+ ## 与现有方法的区别
177
+
178
+ `dsl.if()` 提供两种使用方式,根据参数类型自动选择:
179
+
180
+ | 方式 | 参数类型 | 执行时机 | 用途 | 示例 |
181
+ |------|---------|---------|------|------|
182
+ | **方式一** | 字符串 | Schema 定义时 | 静态布尔条件 | `dsl.if('isVip', thenSchema, elseSchema)` |
183
+ | **方式二** | 函数 | 验证时 | 动态条件判断 | `dsl.if((data) => data.age >= 18).then(...)` |
184
+
185
+ **方式一**(字段条件):基于字段值的静态判断
186
+ ```javascript
187
+ // 示例:根据 isVip 字段值选择不同的验证规则
188
+ dsl.if('isVip', 'number:0-50', 'number:0-10')
189
+ ```
190
+
191
+ **方式二**(函数条件):基于完整数据的动态判断
192
+ ```javascript
193
+ // 示例:根据多个字段的组合逻辑动态选择
194
+ dsl.if((data) => data.age >= 18 && data.role === 'admin')
195
+ .then('email!')
196
+ .else('email')
197
+ ```
198
+
199
+ 此外,`dsl.match()` 适用于多值映射场景:
200
+ ```javascript
201
+ // 示例:根据 type 字段值映射不同验证规则
202
+ dsl.match('type', {
203
+ email: 'email!',
204
+ phone: 'string:11!',
205
+ _default: 'string'
206
+ })
207
+ ```
208
+
209
+ ---
210
+
211
+ ## 快速开始
212
+
213
+ ### 基础用法
214
+
215
+ ```javascript
216
+ const { dsl, validate } = require('schema-dsl');
217
+
218
+ // 方式1:传统方式(需要 validate 函数)
219
+ const schema1 = dsl({
220
+ age: 'number!',
221
+ status: dsl.if((data) => data.age >= 18)
222
+ .message('未成年用户不能注册')
223
+ });
224
+
225
+ validate(schema1, { age: 16, status: 'active' });
226
+ // => { valid: false, errors: [{ message: '未成年用户不能注册' }] }
227
+
228
+ // ✅ 方式2:快捷方式(一行代码验证)
229
+ const result = dsl.if((data) => data.age >= 18)
230
+ .message('未成年用户不能注册')
231
+ .validate({ age: 16 });
232
+ // => { valid: false, errors: [{ message: '未成年用户不能注册' }] }
233
+
234
+ // ✅ 方式3:.check() 快速判断
235
+ const isValid = dsl.if((data) => data.age >= 18)
236
+ .message('未成年用户不能注册')
237
+ .check({ age: 16 });
238
+ // => false
239
+
240
+ // 2. 条件 + then/else(动态Schema)
241
+ const result = dsl.if((data) => data.userType === 'admin')
242
+ .then('email!') // 管理员必填
243
+ .else('email') // 普通用户可选
244
+ .validate({ userType: 'admin', email: 'admin@example.com' });
245
+
246
+ // 3. else 可选
247
+ const result = dsl.if((data) => data.userType === 'vip')
248
+ .then('enum:gold|silver|bronze!')
249
+ // 不写 else,非 vip 用户不验证
250
+ .validate({ userType: 'user' });
251
+
252
+ // 4. 复用验证器
253
+ const ageValidator = dsl.if(d => d.age < 18).message('未成年用户不能注册');
254
+ const r1 = ageValidator.validate({ age: 16 }); // 失败
255
+ const r2 = ageValidator.validate({ age: 20 }); // 通过
256
+ ```
257
+
258
+ ### 多条件组合
259
+
260
+ ```javascript
261
+ // 1. AND 条件
262
+ const result = dsl.if((data) => data.age >= 18)
263
+ .and((data) => data.userType === 'admin')
264
+ .message('只有成年管理员可以操作')
265
+ .validate({ age: 20, userType: 'user' });
266
+
267
+ // 2. OR 条件
268
+ const result = dsl.if((data) => data.age < 18)
269
+ .or((data) => data.status === 'blocked')
270
+ .message('不允许注册')
271
+ .validate({ age: 16, status: 'active' });
272
+
273
+ // 3. 复杂组合
274
+ const result = dsl.if((data) => data.age >= 18)
275
+ .and((data) => data.userType === 'admin')
276
+ .or((data) => data.status === 'vip')
277
+ .then('email!')
278
+ .else('email')
279
+ .validate(data);
280
+ ```
281
+
282
+ ### elseIf 分支
283
+
284
+ ```javascript
285
+ const validator = dsl.if((data) => data.userType === 'admin')
286
+ .then('array<string>!')
287
+ .elseIf((data) => data.userType === 'vip')
288
+ .then('array<string>')
289
+ .elseIf((data) => data.userType === 'user')
290
+ .then('array')
291
+ .else(null); // 游客不验证
292
+
293
+ const r1 = validator.validate({ userType: 'admin', permissions: ['read', 'write'] });
294
+ const r2 = validator.validate({ userType: 'vip' });
295
+ const r3 = validator.validate({ userType: 'guest' });
296
+ ```
297
+
298
+ ---
299
+
300
+ ## API 参考
301
+
302
+ ### dsl.if(condition)
303
+
304
+ 创建链式条件构建器。
305
+
306
+ **参数**:
307
+ - `condition` {Function} - 条件函数,接收完整数据对象
308
+ - 参数: `(data: any) => boolean`
309
+ - 返回: `boolean` - true 表示条件满足
310
+
311
+ **返回**: `ConditionalBuilder` - 构建器实例
312
+
313
+ **示例**:
314
+ ```javascript
315
+ dsl.if((data) => data.age >= 18)
316
+ dsl.if((data) => data.userType === 'admin')
317
+ dsl.if((data) => data.status === 'active' && data.verified)
318
+ ```
319
+
320
+ ---
321
+
322
+ ### .and(condition)
323
+
324
+ 添加 AND 条件(与前一个条件组合)。
325
+
326
+ > **v1.1.1+** 支持在 `.and()` 后调用 `.message()` 设置独立的错误消息
327
+
328
+ **参数**:
329
+ - `condition` {Function} - 条件函数
330
+
331
+ **返回**: `this` - 支持链式调用
332
+
333
+ **基础示例**:
334
+ ```javascript
335
+ // 传统用法:所有条件共享一个消息
336
+ dsl.if((data) => data.age >= 18)
337
+ .and((data) => data.userType === 'admin')
338
+ .message('不符合条件')
339
+ ```
340
+
341
+ **v1.1.1+ 独立消息**:
342
+ ```javascript
343
+ // ✅ 每个条件都有自己的错误消息
344
+ dsl.if((data) => !data)
345
+ .message('账户不存在')
346
+ .and((data) => data.balance < 100)
347
+ .message('余额不足')
348
+ .assert(account);
349
+
350
+ // 工作原理:
351
+ // - 第一个条件为 true → 返回 '账户不存在'
352
+ // - 第二个条件为 true → 返回 '余额不足'
353
+ // - 所有条件为 false → 验证成功
354
+ ```
355
+
356
+ **多个 .and() 条件**:
357
+ ```javascript
358
+ // 支持多个 .and() 条件,每个都有独立消息
359
+ dsl.if(d => !d)
360
+ .message('NOT_FOUND')
361
+ .and(d => d.status !== 'active')
362
+ .message('INACTIVE')
363
+ .and(d => d.balance < 100)
364
+ .message('INSUFFICIENT')
365
+ .assert(account);
366
+
367
+ // 依次检查,第一个为 true 的条件返回其消息
368
+ ```
369
+
370
+ **逻辑**:
371
+ - 传统模式:`(condition1 AND condition2)` - 所有条件为 true 才失败
372
+ - 链式检查模式 (v1.1.1+):依次检查,第一个为 true 的失败
373
+
374
+ **链式检查模式触发条件**:
375
+ 1. 使用 `.message()` 模式
376
+ 2. root 条件有 `.message()`
377
+ 3. 有 `.and()` 条件
378
+ 4. 没有 `.or()` 条件
379
+
380
+ ---
381
+
382
+ ### .or(condition)
383
+
384
+ 添加 OR 条件(与前一个条件组合)。
385
+
386
+ > **v1.1.1+** 支持在 `.or()` 后调用 `.message()` 设置独立的错误消息
387
+
388
+ **参数**:
389
+ - `condition` {Function} - 条件函数
390
+
391
+ **返回**: `this` - 支持链式调用
392
+
393
+ **基础示例**:
394
+ ```javascript
395
+ // 传统用法:所有条件共享一个消息
396
+ dsl.if((data) => data.age < 18)
397
+ .or((data) => data.status === 'blocked')
398
+ .message('不允许注册')
399
+ ```
400
+
401
+ **v1.1.1+ 独立消息**:
402
+ ```javascript
403
+ // ✅ 每个 OR 条件都有自己的错误消息
404
+ dsl.if(d => d.age < 18)
405
+ .message('未成年用户不能注册')
406
+ .or(d => d.isBlocked)
407
+ .message('账户已被封禁')
408
+ .assert(data);
409
+
410
+ // 工作原理:
411
+ // - 第一个条件为 true → 返回 '未成年用户不能注册'
412
+ // - 第二个条件为 true → 返回 '账户已被封禁'
413
+ // - 所有条件为 false → 验证成功
414
+ ```
415
+
416
+ **逻辑**: `(condition1 OR condition2)` - 任一条件为 true 就失败
417
+
418
+ **注意**:
419
+ - 如果有 `.or()` 条件,不会启用链式检查模式
420
+ - 使用传统 AND/OR 组合逻辑
421
+
422
+ ---
423
+
424
+ ### .elseIf(condition)
425
+
426
+ 添加 else-if 分支。
427
+
428
+ **参数**:
429
+ - `condition` {Function} - 条件函数
430
+
431
+ **返回**: `this` - 支持链式调用
432
+
433
+ **示例**:
434
+ ```javascript
435
+ dsl.if((data) => data.userType === 'admin')
436
+ .then('email!')
437
+ .elseIf((data) => data.userType === 'vip')
438
+ .then('email')
439
+ .else(null)
440
+ ```
441
+
442
+ **注意**: 必须在 `.if()` 之后调用
443
+
444
+ ---
445
+
446
+ ### .message(msg)
447
+
448
+ 设置错误消息(支持多语言 key)。
449
+
450
+ > **v1.1.1+** 支持为 `.and()` 和 `.or()` 条件设置独立消息
451
+
452
+ **参数**:
453
+ - `msg` {string} - 错误消息或多语言 key
454
+
455
+ **返回**: `this` - 支持链式调用
456
+
457
+ **行为**: 条件为 true 时自动抛出此错误(无需 `.throwError()`)
458
+
459
+ **基础示例**:
460
+ ```javascript
461
+ dsl.if((data) => data.age >= 18)
462
+ .message('未成年用户不能注册')
463
+
464
+ // 支持多语言 key
465
+ dsl.if((data) => data.age >= 18)
466
+ .message('error.underage')
467
+ ```
468
+
469
+ **v1.1.1+ 为 .and() 设置独立消息**:
470
+ ```javascript
471
+ // ✅ 每个条件都有自己的错误消息
472
+ dsl.if((data) => !data)
473
+ .message('账户不存在')
474
+ .and((data) => data.balance < 100)
475
+ .message('余额不足')
476
+ .assert(account);
477
+ ```
478
+
479
+ **v1.1.1+ 为 .or() 设置独立消息**:
480
+ ```javascript
481
+ // ✅ OR 条件也支持独立消息
482
+ dsl.if(d => d.age < 18)
483
+ .message('未成年')
484
+ .or(d => d.isBlocked)
485
+ .message('已封禁')
486
+ .assert(data);
487
+ ```
488
+
489
+ **链式检查模式说明** (v1.1.1+):
490
+
491
+ 当满足以下条件时,自动启用链式检查模式:
492
+ 1. 使用 `.message()` 模式(不是 `.then()`/`.else()`)
493
+ 2. root 条件有 `.message()`
494
+ 3. 有 `.and()` 条件
495
+ 4. 没有 `.or()` 条件
496
+
497
+ ```javascript
498
+ // ✅ 启用链式检查(纯 AND 场景)
499
+ dsl.if(d => !d).message('A').and(d => d < 100).message('B')
500
+
501
+ // ❌ 不启用(有 .or())
502
+ dsl.if(d => !d).message('A').and(d => d < 100).or(d => d > 200).message('B')
503
+
504
+ // ❌ 不启用(使用 .then()/.else())
505
+ dsl.if(d => d.age >= 18).and(d => d.role === 'admin').then('email!')
506
+ ```
507
+
508
+ ---
509
+
510
+ ### .then(schema)
511
+
512
+ 设置满足条件时的 Schema。
513
+
514
+ **参数**:
515
+ - `schema` {string|DslBuilder|JSONSchema} - DSL 字符串或 Schema 对象
516
+
517
+ **返回**: `this` - 支持链式调用
518
+
519
+ **示例**:
520
+ ```javascript
521
+ // DSL 字符串
522
+ dsl.if((data) => data.userType === 'admin')
523
+ .then('email!')
524
+
525
+ // DslBuilder 实例
526
+ dsl.if((data) => data.userType === 'admin')
527
+ .then(dsl('email!').label('管理员邮箱'))
528
+
529
+ // JSON Schema 对象
530
+ dsl.if((data) => data.userType === 'admin')
531
+ .then({ type: 'string', format: 'email' })
532
+ ```
533
+
534
+ ---
535
+
536
+ ### .else(schema)
537
+
538
+ 设置默认 Schema(所有条件都不满足时)。
539
+
540
+ **参数**:
541
+ - `schema` {string|DslBuilder|JSONSchema|null} - DSL 字符串、Schema 对象或 null
542
+
543
+ **返回**: `this` - 支持链式调用
544
+
545
+ **特性**: 可选,不写 else 就不验证
546
+
547
+ **示例**:
548
+ ```javascript
549
+ // 显式指定 else
550
+ dsl.if((data) => data.userType === 'admin')
551
+ .then('email!')
552
+ .else('email')
553
+
554
+ // else 为 null(显式跳过验证)
555
+ dsl.if((data) => data.userType === 'admin')
556
+ .then('email!')
557
+ .else(null)
558
+
559
+ // 不写 else(隐式跳过验证)
560
+ dsl.if((data) => data.userType === 'admin')
561
+ .then('email!')
562
+ ```
563
+
564
+ ---
565
+
566
+ ### .validate(data, options)
567
+
568
+ 快捷验证方法 - 返回完整验证结果。
569
+
570
+ **参数**:
571
+ - `data` {*} - 待验证的数据(任意类型)
572
+ - `options` {Object} - 验证选项(可选)
573
+ - `locale` {string} - 语言环境(如 'zh-CN', 'en-US')
574
+ - `messages` {Object} - 自定义错误消息
575
+
576
+ **返回**: `Object` - 验证结果 `{ valid, errors, data }`
577
+
578
+ **特性**: 一行代码完成验证,无需外部 `validate()` 函数
579
+
580
+ **示例**:
581
+ ```javascript
582
+ // 一行代码验证
583
+ const result = dsl.if(d => d.age < 18)
584
+ .message('未成年用户不能注册')
585
+ .validate({ age: 16 });
586
+ // => { valid: false, errors: [...], data }
587
+
588
+ // 复用验证器
589
+ const ageValidator = dsl.if(d => d.age < 18).message('未成年');
590
+ const r1 = ageValidator.validate({ age: 16 }); // false
591
+ const r2 = ageValidator.validate({ age: 20 }); // true
592
+
593
+ // 支持验证选项
594
+ const result = dsl.if(d => d.age < 18)
595
+ .message('conditional.underAge')
596
+ .validate({ age: 16 }, { locale: 'zh-CN' });
597
+
598
+ // 验证非对象类型
599
+ const result = dsl.if(d => d.includes('@'))
600
+ .then('email!')
601
+ .validate('test@example.com');
602
+ ```
603
+
604
+ ---
605
+
606
+ ### .validateAsync(data, options)
607
+
608
+ 异步验证方法 - 失败自动抛出异常。
609
+
610
+ **参数**:
611
+ - `data` {*} - 待验证的数据
612
+ - `options` {Object} - 验证选项(可选)
613
+
614
+ **返回**: `Promise<*>` - 验证通过返回数据,失败抛出异常
615
+
616
+ **抛出**: `ValidationError` - 验证失败抛出异常
617
+
618
+ **特性**: 适合 async/await 场景,失败自动抛错
619
+
620
+ **示例**:
621
+ ```javascript
622
+ // 异步验证,失败自动抛错
623
+ try {
624
+ const data = await dsl.if(d => d.age < 18)
625
+ .message('未成年用户不能注册')
626
+ .validateAsync({ age: 16 });
627
+ } catch (error) {
628
+ console.log(error.message); // "未成年用户不能注册"
629
+ console.log(error.errors); // 详细错误信息
630
+ }
631
+
632
+ // Express 中间件
633
+ app.post('/register', async (req, res, next) => {
634
+ try {
635
+ await dsl.if(d => d.age < 18)
636
+ .message('未成年用户不能注册')
637
+ .validateAsync(req.body);
638
+
639
+ // 验证通过,继续处理...
640
+ const user = await createUser(req.body);
641
+ res.json(user);
642
+ } catch (error) {
643
+ next(error);
644
+ }
645
+ });
646
+
647
+ // 复用验证器
648
+ const ageValidator = dsl.if(d => d.age < 18).message('未成年');
649
+
650
+ try {
651
+ await ageValidator.validateAsync({ age: 16 });
652
+ } catch (error) {
653
+ // 处理错误
654
+ }
655
+ ```
656
+
657
+ ---
658
+
659
+ ### .assert(data, options)
660
+
661
+ 断言方法 - 同步验证,失败直接抛错。
662
+
663
+ **参数**:
664
+ - `data` {*} - 待验证的数据
665
+ - `options` {Object} - 验证选项(可选)
666
+
667
+ **返回**: `*` - 验证通过返回数据
668
+
669
+ **抛出**: `Error` - 验证失败抛出错误(name: 'ValidationError')
670
+
671
+ **特性**: 同步版本的断言验证,适合快速失败场景
672
+
673
+ **示例**:
674
+ ```javascript
675
+ // 断言验证,失败直接抛错
676
+ try {
677
+ dsl.if(d => d.age < 18)
678
+ .message('未成年用户不能注册')
679
+ .assert({ age: 16 });
680
+ } catch (error) {
681
+ console.log(error.message); // "未成年用户不能注册"
682
+ }
683
+
684
+ // 函数中快速断言
685
+ function registerUser(userData) {
686
+ // 断言验证
687
+ dsl.if(d => d.age < 18)
688
+ .message('未成年用户不能注册')
689
+ .assert(userData);
690
+
691
+ dsl.if(d => !d.email)
692
+ .message('邮箱不能为空')
693
+ .assert(userData);
694
+
695
+ // 验证通过,继续处理...
696
+ return createUser(userData);
697
+ }
698
+
699
+ // 链式断言
700
+ function validateAndCreate(data) {
701
+ dsl.if(d => d.age < 18).message('未成年').assert(data);
702
+ dsl.if(d => !d.email).message('邮箱必填').assert(data);
703
+ dsl.if(d => !d.username).message('用户名必填').assert(data);
704
+
705
+ return createUser(data);
706
+ }
707
+ ```
708
+
709
+ ---
710
+
711
+ ### .check(data)
712
+
713
+ 快捷检查方法 - 只返回 boolean。
714
+
715
+ **参数**:
716
+ - `data` {*} - 待验证的数据
717
+
718
+ **返回**: `boolean` - 验证是否通过
719
+
720
+ **特性**: 比 `.validate()` 更简洁,适合只需要判断真假的场景
721
+
722
+ **示例**:
723
+ ```javascript
724
+ // 快速判断
725
+ const isValid = dsl.if(d => d.age < 18)
726
+ .message('未成年')
727
+ .check({ age: 16 });
728
+ // => false
729
+
730
+ // 断言场景
731
+ if (!validator.check(userData)) {
732
+ console.log('验证失败');
733
+ }
734
+
735
+ // 循环验证
736
+ const users = [{ age: 16 }, { age: 20 }, { age: 17 }];
737
+ const adults = users.filter(u =>
738
+ !dsl.if(d => d.age < 18).message('未成年').check(u)
739
+ );
740
+ ```
741
+
742
+ ---
743
+
744
+ ## 使用场景
745
+
746
+ ### 场景1:用户注册 - 快捷验证
747
+
748
+ 使用 `.validate()` 方法快速验证用户注册数据。
749
+
750
+ ```javascript
751
+ // 创建可复用的验证器
752
+ const validators = {
753
+ age: dsl.if(d => d.age < 18).message('未成年用户不能注册'),
754
+ email: dsl.if(d => d.userType === 'admin')
755
+ .message('管理员必须提供邮箱')
756
+ };
757
+
758
+ // 快速验证(一行代码)
759
+ function registerUser(userData) {
760
+ // 验证年龄
761
+ const ageResult = validators.age.validate(userData);
762
+ if (!ageResult.valid) {
763
+ return { error: ageResult.errors[0].message };
764
+ }
765
+
766
+ // 验证邮箱
767
+ const emailResult = validators.email.validate(userData);
768
+ if (!emailResult.valid) {
769
+ return { error: emailResult.errors[0].message };
770
+ }
771
+
772
+ return { success: true };
773
+ }
774
+
775
+ // 使用
776
+ registerUser({ username: 'test', age: 16 });
777
+ // => { error: '未成年用户不能注册' }
778
+ ```
779
+
780
+ ### 场景2:批量数据验证 - 使用 .check()
781
+
782
+ 使用 `.check()` 方法快速过滤符合条件的数据。
783
+
784
+ ```javascript
785
+ const users = [
786
+ { name: '张三', age: 16 },
787
+ { name: '李四', age: 20 },
788
+ { name: '王五', age: 17 },
789
+ { name: '赵六', age: 25 }
790
+ ];
791
+
792
+ // 创建验证器
793
+ const canRegister = dsl.if(d => d.age < 18)
794
+ .message('未成年');
795
+
796
+ // ✅ 使用 .check() 过滤
797
+ const validUsers = users.filter(u => !canRegister.check(u));
798
+ // => [{ name: '李四', age: 20 }, { name: '赵六', age: 25 }]
799
+
800
+ // ✅ 使用 .check() 统计
801
+ const minorCount = users.filter(u => canRegister.check(u)).length;
802
+ console.log(`未成年用户: ${minorCount} 人`);
803
+ // => "未成年用户: 2 人"
804
+ ```
805
+
806
+ ### 场景3:表单实时验证
807
+
808
+ ```javascript
809
+ // 前端表单验证
810
+ const formValidators = {
811
+ username: dsl.if(d => d.length < 3)
812
+ .message('用户名至少3个字符'),
813
+
814
+ password: dsl.if(d => d.length < 8)
815
+ .message('密码至少8个字符')
816
+ };
817
+
818
+ // 实时验证(输入时)
819
+ function onUsernameChange(value) {
820
+ const isValid = formValidators.username.check(value);
821
+ if (!isValid) {
822
+ showError('用户名至少3个字符');
823
+ } else {
824
+ clearError();
825
+ }
826
+ }
827
+
828
+ // 提交验证
829
+ function onSubmit(formData) {
830
+ const usernameResult = formValidators.username.validate(formData.username);
831
+ const passwordResult = formValidators.password.validate(formData.password);
832
+
833
+ if (!usernameResult.valid) {
834
+ return alert(usernameResult.errors[0].message);
835
+ }
836
+
837
+ if (!passwordResult.valid) {
838
+ return alert(passwordResult.errors[0].message);
839
+ }
840
+
841
+ // 提交表单...
842
+ }
843
+ ```
844
+
845
+ ### 场景4:用户权限检查
846
+
847
+ ```javascript
848
+ // 权限验证器
849
+ const hasPermission = dsl.if(d => d.role === 'admin')
850
+ .or(d => d.role === 'moderator')
851
+ .message('权限不足');
852
+
853
+ // 中间件
854
+ function checkPermission(req, res, next) {
855
+ if (!hasPermission.check(req.user)) {
856
+ return res.status(403).json({ error: '权限不足' });
857
+ }
858
+ next();
859
+ }
860
+
861
+ // 路由
862
+ app.delete('/users/:id', checkPermission, deleteUser);
863
+ ```
864
+
865
+ ### 场景5:根据年龄和用户类型验证不同字段(传统方式对比)
866
+
867
+ ```javascript
868
+ // 传统方式(需要 validate 函数)
869
+ const schema = dsl({
870
+ username: 'string:3-32!',
871
+ age: 'number:1-120!',
872
+ userType: 'enum:admin|vip|user!',
873
+
874
+ // 未成年禁止注册
875
+ ageCheck: dsl.if((data) => data.age < 18)
876
+ .message('未成年用户不能注册'),
877
+
878
+ // 管理员必须有邮箱
879
+ email: dsl.if((data) => data.userType === 'admin')
880
+ .then('email!')
881
+ .else('email'),
882
+
883
+ // VIP用户必须有手机号
884
+ phone: dsl.if((data) => data.userType === 'vip')
885
+ .then('string:11!')
886
+ .else(null),
887
+
888
+ // 管理员和VIP可以设置昵称
889
+ nickname: dsl.if((data) => data.userType === 'admin')
890
+ .or((data) => data.userType === 'vip')
891
+ .then('string:2-20')
892
+ .else(null)
893
+ });
894
+
895
+ // 测试
896
+ validate(schema, {
897
+ username: 'admin1',
898
+ age: 25,
899
+ userType: 'admin',
900
+ email: 'admin@example.com'
901
+ });
902
+ // => { valid: true }
903
+ ```
904
+
905
+ ### 场景2:商品发布
906
+
907
+ 根据商品类型验证不同字段。
908
+
909
+ ```javascript
910
+ const schema = dsl({
911
+ title: 'string:1-100!',
912
+ price: 'number:0-!',
913
+ type: 'enum:physical|digital|service!',
914
+
915
+ // 实体商品需要重量和尺寸
916
+ weight: dsl.if((data) => data.type === 'physical')
917
+ .then('number:0-!')
918
+ .else(null),
919
+
920
+ dimensions: dsl.if((data) => data.type === 'physical')
921
+ .then('string!')
922
+ .else(null),
923
+
924
+ // 数字商品需要下载链接
925
+ downloadUrl: dsl.if((data) => data.type === 'digital')
926
+ .then('url!')
927
+ .else(null),
928
+
929
+ // 服务类需要服务时长
930
+ duration: dsl.if((data) => data.type === 'service')
931
+ .then('number:1-!')
932
+ .else(null)
933
+ });
934
+
935
+ // 实体商品
936
+ validate(schema, {
937
+ title: '笔记本电脑',
938
+ price: 5999,
939
+ type: 'physical',
940
+ weight: 1.5,
941
+ dimensions: '30x20x2cm'
942
+ });
943
+ // => { valid: true }
944
+
945
+ // 数字商品
946
+ validate(schema, {
947
+ title: '电子书',
948
+ price: 29.9,
949
+ type: 'digital',
950
+ downloadUrl: 'https://example.com/download'
951
+ });
952
+ // => { valid: true }
953
+ ```
954
+
955
+ ### 场景3:权限控制
956
+
957
+ 根据用户角色和状态控制访问。
958
+
959
+ ```javascript
960
+ const schema = dsl({
961
+ userId: 'string!',
962
+ role: 'enum:admin|moderator|user!',
963
+ status: 'enum:active|suspended|banned!',
964
+
965
+ // 被封禁用户禁止操作
966
+ accessCheck: dsl.if((data) => data.status === 'banned')
967
+ .message('您的账号已被封禁'),
968
+
969
+ // 暂停用户只能查看
970
+ operationType: dsl.if((data) => data.status === 'suspended')
971
+ .then('enum:view!')
972
+ .else('enum:view|edit|delete!'),
973
+
974
+ // 管理员可以访问所有资源
975
+ resourceIds: dsl.if((data) => data.role === 'admin')
976
+ .then('array<string>') // 可选
977
+ .else('array<string>!') // 必填
978
+ });
979
+ ```
980
+
981
+ ---
982
+
983
+ ## 最佳实践
984
+
985
+ ### 1. 条件函数保持简单
986
+
987
+ ❌ **不推荐**:
988
+ ```javascript
989
+ dsl.if((data) => {
990
+ const user = getUserFromDB(data.userId); // 同步数据库查询
991
+ return user.level > 5;
992
+ })
993
+ ```
994
+
995
+ ✅ **推荐**:
996
+ ```javascript
997
+ dsl.if((data) => data.userLevel > 5)
998
+ ```
999
+
1000
+ **原因**: 条件函数应该只读取数据对象,不应该有副作用或执行耗时操作。
1001
+
1002
+ ---
1003
+
1004
+ ### 2. 使用有意义的字段名
1005
+
1006
+ ❌ **不推荐**:
1007
+ ```javascript
1008
+ const schema = dsl({
1009
+ field1: 'string!',
1010
+ check1: dsl.if((data) => data.field1 === 'admin')
1011
+ .message('Error')
1012
+ });
1013
+ ```
1014
+
1015
+ ✅ **推荐**:
1016
+ ```javascript
1017
+ const schema = dsl({
1018
+ userType: 'string!',
1019
+ ageVerification: dsl.if((data) => data.age < 18)
1020
+ .message('未成年用户不能注册')
1021
+ });
1022
+ ```
1023
+
1024
+ ---
1025
+
1026
+ ### 3. 合理使用 else
1027
+
1028
+ 当条件不满足时需要不同的验证规则,使用 `.else()`:
1029
+
1030
+ ```javascript
1031
+ dsl.if((data) => data.userType === 'admin')
1032
+ .then('email!')
1033
+ .else('email') // 不同的验证规则
1034
+ ```
1035
+
1036
+ 当条件不满足时不需要验证,省略 `.else()`:
1037
+
1038
+ ```javascript
1039
+ dsl.if((data) => data.userType === 'vip')
1040
+ .then('string:6!')
1041
+ // 不写 else,非 vip 用户不验证
1042
+ ```
1043
+
1044
+ ---
1045
+
1046
+ ### 4. 多条件组合优先使用函数内部逻辑
1047
+
1048
+ 简单条件可以直接在函数内部组合:
1049
+
1050
+ ```javascript
1051
+ // ✅ 推荐(简洁)
1052
+ dsl.if((data) => data.age >= 18 && data.userType === 'admin')
1053
+ .then('email!')
1054
+
1055
+ // ⚠️ 可用但稍繁琐
1056
+ dsl.if((data) => data.age >= 18)
1057
+ .and((data) => data.userType === 'admin')
1058
+ .then('email!')
1059
+ ```
1060
+
1061
+ 复杂逻辑或需要可维护性时使用 `.and()` / `.or()`:
1062
+
1063
+ ```javascript
1064
+ // ✅ 推荐(可读性强)
1065
+ dsl.if((data) => data.age >= 18)
1066
+ .and((data) => data.userType === 'admin')
1067
+ .and((data) => data.verified)
1068
+ .or((data) => data.isSuperUser)
1069
+ .then('email!')
1070
+ ```
1071
+
1072
+ ---
1073
+
1074
+ ### 5. 错误消息清晰明确
1075
+
1076
+ ❌ **不推荐**:
1077
+ ```javascript
1078
+ dsl.if((data) => data.age < 18)
1079
+ .message('Error')
1080
+ ```
1081
+
1082
+ ✅ **推荐**:
1083
+ ```javascript
1084
+ dsl.if((data) => data.age < 18)
1085
+ .message('未成年用户不能注册')
1086
+ ```
1087
+
1088
+ ✅ **更好**(支持多语言):
1089
+ ```javascript
1090
+ dsl.if((data) => data.age < 18)
1091
+ .message('error.user.underage')
1092
+ ```
1093
+
1094
+ ---
1095
+
1096
+ ## 常见问题
1097
+
1098
+ ### Q1: 条件函数什么时候执行?
1099
+
1100
+ **A**: 在调用 `validate()` 时执行,不是在定义 Schema 时。
1101
+
1102
+ ```javascript
1103
+ const schema = dsl({
1104
+ email: dsl.if((data) => data.userType === 'admin')
1105
+ .then('email!') // ← 这里不会执行
1106
+ });
1107
+
1108
+ validate(schema, data); // ← 条件函数在这里执行
1109
+ ```
1110
+
1111
+ ---
1112
+
1113
+ ### Q2: 条件函数可以访问哪些数据?
1114
+
1115
+ **A**: 可以访问完整的数据对象。
1116
+
1117
+ ```javascript
1118
+ const schema = dsl({
1119
+ age: 'number!',
1120
+ userType: 'string!',
1121
+ status: 'string!',
1122
+ email: dsl.if((data) => {
1123
+ // 可以访问所有字段
1124
+ return data.age >= 18 && data.userType === 'admin' && data.status === 'active';
1125
+ }).then('email!')
1126
+ });
1127
+ ```
1128
+
1129
+ ---
1130
+
1131
+ ### Q3: 如何处理条件函数抛错?
1132
+
1133
+ **A**: 条件函数抛错会被捕获,视为条件不满足。
1134
+
1135
+ ```javascript
1136
+ const schema = dsl({
1137
+ obj: 'object!',
1138
+ result: dsl.if((data) => data.obj.nested.value > 10)
1139
+ .then('string!')
1140
+ .else(null)
1141
+ });
1142
+
1143
+ // data.obj.nested 不存在,访问会抛错
1144
+ validate(schema, { obj: {} });
1145
+ // => { valid: true } 条件不满足,执行 else(null)
1146
+ ```
1147
+
1148
+ **建议**: 在条件函数中做好防御性检查:
1149
+
1150
+ ```javascript
1151
+ dsl.if((data) => data.obj?.nested?.value > 10)
1152
+ .then('string!')
1153
+ ```
1154
+
1155
+ ---
1156
+
1157
+ ### Q4: 可以嵌套 dsl.if() 吗?
1158
+
1159
+ **A**: 可以,支持嵌套。
1160
+
1161
+ ```javascript
1162
+ const schema = dsl({
1163
+ userType: 'string!',
1164
+ age: 'number!',
1165
+ email: dsl.if((data) => data.userType === 'admin')
1166
+ .then(
1167
+ dsl.if((data) => data.age >= 18)
1168
+ .then('email!')
1169
+ .else('email')
1170
+ )
1171
+ .else('email')
1172
+ });
1173
+ ```
1174
+
1175
+ ---
1176
+
1177
+ ### Q5: 如何与现有的 dsl.match() 方法配合使用?
1178
+
1179
+ **A**: 可以混用,选择最适合的方法。
1180
+
1181
+ ```javascript
1182
+ const schema = dsl({
1183
+ // 静态值映射 - 使用 match
1184
+ userType: 'enum:admin|vip|user!',
1185
+ level: dsl.match('userType', {
1186
+ admin: 'enum:high!',
1187
+ vip: 'enum:medium!',
1188
+ user: 'enum:low!'
1189
+ }),
1190
+
1191
+ // 动态条件判断 - 使用 if
1192
+ email: dsl.if((data) => data.userType === 'admin' && data.level === 'high')
1193
+ .then('email!')
1194
+ .else('email')
1195
+ });
1196
+ ```
1197
+
1198
+ **选择建议**:
1199
+ - **简单值映射** → 使用 `dsl.match()`
1200
+ - **复杂条件逻辑** → 使用 `dsl.if()`
1201
+
1202
+ ---
1203
+
1204
+ ### Q6: 是否支持非对象类型(字符串、数组、数字等)?
1205
+
1206
+ **A**: 完全支持!可以直接验证任何类型的值。
1207
+
1208
+ ```javascript
1209
+ // 示例1:直接验证字符串
1210
+ const stringSchema = dsl.if((data) => typeof data === 'string' && data.includes('@'))
1211
+ .then('email!')
1212
+ .else('string:1-50');
1213
+
1214
+ validate(stringSchema, 'test@example.com'); // ✅ 作为邮箱验证
1215
+ validate(stringSchema, 'just a text'); // ✅ 作为普通字符串验证
1216
+
1217
+ // 示例2:直接验证数组
1218
+ const arraySchema = dsl.if((data) => Array.isArray(data) && data.length > 5)
1219
+ .message('数组最多5个元素');
1220
+
1221
+ validate(arraySchema, [1, 2, 3]); // ✅ 通过
1222
+ validate(arraySchema, [1, 2, 3, 4, 5, 6]); // ❌ 失败
1223
+
1224
+ // 示例3:直接验证数字
1225
+ const numberSchema = dsl.if((data) => typeof data === 'number' && data < 0)
1226
+ .message('不允许负数');
1227
+
1228
+ validate(numberSchema, 10); // ✅ 通过
1229
+ validate(numberSchema, -5); // ❌ 失败
1230
+
1231
+ // 示例4:自动识别类型(邮箱或手机号)
1232
+ const contactSchema = dsl.if((data) => typeof data === 'string' && data.includes('@'))
1233
+ .then('email!')
1234
+ .else('string:11!');
1235
+
1236
+ validate(contactSchema, 'user@example.com'); // ✅ 作为邮箱验证
1237
+ validate(contactSchema, '13800138000'); // ✅ 作为手机号验证
1238
+ ```
1239
+
1240
+ **完整示例**: 参见 `examples/conditional-non-object.js`
1241
+
1242
+ ---
1243
+
1244
+ ### Q7: 性能如何?
1245
+
1246
+ **A**: 性能优秀,条件函数执行非常快。
1247
+
1248
+ - 条件函数是纯 JavaScript 函数,执行速度快
1249
+ - 只遍历条件链一次,找到第一个匹配的条件就停止
1250
+ - 支持缓存优化(WeakMap)
1251
+
1252
+ **性能提示**:
1253
+ - 避免在条件函数中执行耗时操作(数据库查询、API 调用)
1254
+ - 将最常见的条件放在前面(if 而不是 elseIf)
1255
+
1256
+ ---
1257
+
1258
+ ## 更新日志
1259
+
1260
+ ### v1.1.1 (2026-01-05)
1261
+
1262
+ - ✅ 新增 `ConditionalBuilder` 类
1263
+ - ✅ 新增 `dsl.if()` 链式条件 API
1264
+ - ✅ 支持 and/or 多条件组合
1265
+ - ✅ 支持 elseIf 多分支
1266
+ - ✅ message 自动抛错(无需 throwError)
1267
+ - ✅ else 可选(不写就不验证)
1268
+ - ❌ 移除无效的旧条件方法类型定义
1269
+
1270
+ ---
1271
+
1272
+ ## 相关文档
1273
+
1274
+ - [快速开始](./quick-start.md)
1275
+ - [验证指南](./validation-guide.md)
1276
+ - [API 参考](./api-reference.md)
1277
+ - [最佳实践](./best-practices.md)
1278
+