schema-dsl 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +205 -2
  2. package/README.md +257 -0
  3. package/docs/FEATURE-INDEX.md +1 -1
  4. package/docs/best-practices.md +3 -3
  5. package/docs/cache-manager.md +1 -1
  6. package/docs/conditional-api.md +1032 -0
  7. package/docs/dsl-syntax.md +1 -1
  8. package/docs/dynamic-locale.md +2 -2
  9. package/docs/error-handling.md +2 -2
  10. package/docs/export-guide.md +2 -2
  11. package/docs/export-limitations.md +3 -3
  12. package/docs/faq.md +6 -6
  13. package/docs/frontend-i18n-guide.md +1 -1
  14. package/docs/mongodb-exporter.md +3 -3
  15. package/docs/multi-type-support.md +12 -2
  16. package/docs/mysql-exporter.md +1 -1
  17. package/docs/plugin-system.md +4 -4
  18. package/docs/postgresql-exporter.md +1 -1
  19. package/docs/quick-start.md +4 -4
  20. package/docs/troubleshooting.md +2 -2
  21. package/docs/type-reference.md +5 -5
  22. package/docs/typescript-guide.md +5 -6
  23. package/docs/union-type-guide.md +147 -0
  24. package/docs/union-types.md +277 -0
  25. package/docs/validate-async.md +1 -1
  26. package/examples/array-dsl-example.js +1 -1
  27. package/examples/conditional-example.js +288 -0
  28. package/examples/conditional-non-object.js +129 -0
  29. package/examples/conditional-validate-example.js +321 -0
  30. package/examples/union-type-example.js +127 -0
  31. package/examples/union-types-example.js +77 -0
  32. package/index.d.ts +332 -7
  33. package/index.js +19 -1
  34. package/lib/adapters/DslAdapter.js +14 -5
  35. package/lib/core/ConditionalBuilder.js +401 -0
  36. package/lib/core/DslBuilder.js +113 -0
  37. package/lib/core/Locale.js +13 -8
  38. package/lib/core/Validator.js +246 -2
  39. package/lib/locales/en-US.js +14 -0
  40. package/lib/locales/es-ES.js +4 -0
  41. package/lib/locales/fr-FR.js +4 -0
  42. package/lib/locales/ja-JP.js +9 -0
  43. package/lib/locales/zh-CN.js +14 -0
  44. package/package.json +3 -1
@@ -0,0 +1,277 @@
1
+ # 跨类型联合验证 - types: 语法
2
+
3
+ > **版本**: v1.1.0+
4
+ > **状态**: ✅ 稳定
5
+
6
+ ---
7
+
8
+ ## 概述
9
+
10
+ `types:` 语法允许您定义跨类型联合验证,支持字段匹配多种不同的数据类型。
11
+
12
+ ### 特性
13
+
14
+ ✅ **简洁语法** - `'types:string|number'` 一行搞定
15
+ ✅ **带约束** - `'types:string:3-10|number:0-100'`
16
+ ✅ **插件扩展** - 支持自定义类型注册
17
+ ✅ **多语言** - 完整的i18n支持
18
+
19
+ ---
20
+
21
+ ## 快速开始
22
+
23
+ ### 基础用法
24
+
25
+ ```javascript
26
+ const { dsl, validate } = require('schema-dsl');
27
+
28
+ // 定义联合类型
29
+ const schema = dsl({
30
+ value: 'types:string|number'
31
+ });
32
+
33
+ // 验证
34
+ validate(schema, { value: 'hello' }); // ✅ 通过
35
+ validate(schema, { value: 123 }); // ✅ 通过
36
+ validate(schema, { value: true }); // ❌ 失败
37
+ ```
38
+
39
+ ### 带约束
40
+
41
+ ```javascript
42
+ const schema = dsl({
43
+ value: 'types:string:3-10|number:0-100!'
44
+ });
45
+
46
+ validate(schema, { value: 'abc' }); // ✅ 通过
47
+ validate(schema, { value: 50 }); // ✅ 通过
48
+ validate(schema, { value: 'ab' }); // ❌ 太短
49
+ validate(schema, { value: 101 }); // ❌ 超范围
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 语法说明
55
+
56
+ ### 基本格式
57
+
58
+ ```
59
+ types:type1|type2|type3[!]
60
+ ```
61
+
62
+ - `types:` - 固定前缀
63
+ - `type1|type2` - 类型列表,用 `|` 分隔
64
+ - `!` - 可选的必填标记
65
+
66
+ ### 带约束格式
67
+
68
+ ```
69
+ types:type1:constraint1|type2:constraint2
70
+ ```
71
+
72
+ ---
73
+
74
+ ## 支持的类型
75
+
76
+ ### 内置类型
77
+
78
+ 所有内置类型都可以在 `types:` 中使用:
79
+
80
+ - **基本类型**: `string`, `number`, `integer`, `boolean`, `null`, `any`
81
+ - **格式类型**: `email`, `url`, `uuid`, `date`, `datetime`, `time`
82
+ - **特殊类型**: `phone`, `idCard`, `objectId`, `hexColor` 等
83
+
84
+ ### 插件自定义类型
85
+
86
+ 通过插件注册的自定义类型也可以使用:
87
+
88
+ ```javascript
89
+ const { DslBuilder, PluginManager } = require('schema-dsl');
90
+
91
+ // 注册自定义类型
92
+ DslBuilder.registerType('order-id', {
93
+ type: 'string',
94
+ pattern: /^ORD[0-9]{12}$/.source,
95
+ minLength: 15,
96
+ maxLength: 15
97
+ });
98
+
99
+ // 在types:中使用
100
+ const schema = dsl({
101
+ identifier: 'types:uuid|order-id'
102
+ });
103
+ ```
104
+
105
+ ---
106
+
107
+ ## 实际应用场景
108
+
109
+ ### 场景1:用户注册(邮箱或手机号)
110
+
111
+ ```javascript
112
+ const registerSchema = dsl({
113
+ username: 'string:3-20!',
114
+ password: 'string:8-20!',
115
+ contact: 'types:email|phone!' // 邮箱或手机号
116
+ });
117
+ ```
118
+
119
+ ### 场景2:灵活的价格输入
120
+
121
+ ```javascript
122
+ const productSchema = dsl({
123
+ price: 'types:number:0-|string:1-20' // 数字价格或"面议"
124
+ });
125
+
126
+ validate(productSchema, { price: 99.99 }); // ✅ 数字
127
+ validate(productSchema, { price: '面议' }); // ✅ 字符串
128
+ ```
129
+
130
+ ### 场景3:订单查询(订单号或SKU)
131
+
132
+ ```javascript
133
+ // 先注册自定义类型
134
+ DslBuilder.registerType('order-id', { ... });
135
+ DslBuilder.registerType('sku', { ... });
136
+
137
+ const querySchema = dsl({
138
+ identifier: 'types:order-id|sku!'
139
+ });
140
+ ```
141
+
142
+ ---
143
+
144
+ ## 插件开发指南
145
+
146
+ ### 注册自定义类型
147
+
148
+ ```javascript
149
+ // 在插件的install方法中
150
+ install(schemaDsl, options, context) {
151
+ const { DslBuilder } = schemaDsl;
152
+
153
+ // 注册DSL类型
154
+ DslBuilder.registerType('custom-type', {
155
+ type: 'string',
156
+ pattern: /^CUSTOM-\d+$/.source,
157
+ minLength: 8,
158
+ maxLength: 20
159
+ });
160
+
161
+ // 同时注册ajv format(可选)
162
+ const validator = schemaDsl.getDefaultValidator();
163
+ const ajv = validator.getAjv();
164
+ ajv.addFormat('custom-type', {
165
+ validate: /^CUSTOM-\d+$/
166
+ });
167
+ }
168
+ ```
169
+
170
+ ### DslBuilder API
171
+
172
+ #### `DslBuilder.registerType(name, schema)`
173
+
174
+ 注册自定义类型。
175
+
176
+ **参数**:
177
+ - `name` (string) - 类型名称
178
+ - `schema` (Object|Function) - JSON Schema对象或生成函数
179
+
180
+ #### `DslBuilder.hasType(type)`
181
+
182
+ 检查类型是否已注册。
183
+
184
+ #### `DslBuilder.getCustomTypes()`
185
+
186
+ 获取所有已注册的自定义类型。
187
+
188
+ #### `DslBuilder.clearCustomTypes()`
189
+
190
+ 清除所有自定义类型(主要用于测试)。
191
+
192
+ ---
193
+
194
+ ## 多语言支持
195
+
196
+ ### 中文
197
+
198
+ ```javascript
199
+ validate(schema, { value: true }, { locale: 'zh-CN' });
200
+ // 错误: "必须匹配以下类型之一"
201
+ ```
202
+
203
+ ### 英文
204
+
205
+ ```javascript
206
+ validate(schema, { value: true }, { locale: 'en-US' });
207
+ // Error: "Must match one of the following types"
208
+ ```
209
+
210
+ 支持的语言:`zh-CN`, `en-US`, `es-ES`, `fr-FR`, `ja-JP`
211
+
212
+ ---
213
+
214
+ ## 最佳实践
215
+
216
+ ### 1. 优先使用内置类型
217
+
218
+ ```javascript
219
+ // ✅ 推荐
220
+ 'types:email|phone'
221
+
222
+ // ⚠️ 不推荐(性能较差)
223
+ 'types:string:custom-email-pattern|string:custom-phone-pattern'
224
+ ```
225
+
226
+ ### 2. 合理使用约束
227
+
228
+ ```javascript
229
+ // ✅ 明确约束
230
+ 'types:string:3-32|number:0-100'
231
+
232
+ // ❌ 过于宽松
233
+ 'types:string|number' // 没有约束
234
+ ```
235
+
236
+ ### 3. 插件类型命名规范
237
+
238
+ ```javascript
239
+ // ✅ 使用kebab-case
240
+ DslBuilder.registerType('order-id', { ... });
241
+ DslBuilder.registerType('phone-cn', { ... });
242
+
243
+ // ❌ 不推荐
244
+ DslBuilder.registerType('OrderID', { ... });
245
+ DslBuilder.registerType('phone_cn', { ... });
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 注意事项
251
+
252
+ ### oneOf语义
253
+
254
+ `types:` 语法内部使用JSON Schema的 `oneOf`,表示"恰好匹配其中一种类型"。
255
+
256
+ ### 性能考虑
257
+
258
+ 联合类型会依次验证每个类型,直到匹配成功。类型越多,性能开销越大。
259
+
260
+ **建议**:
261
+ - 联合类型数量控制在5个以内
262
+ - 将最常用的类型放在前面
263
+
264
+ ---
265
+
266
+ ## 相关文档
267
+
268
+ - [插件系统](./plugin-system.md)
269
+ - [自定义类型注册](./plugin-type-registration.md)
270
+ - [代码规范](../specs/rules/代码规范.md)
271
+
272
+ ---
273
+
274
+ ## 版本历史
275
+
276
+ - **v1.1.0** - 首次发布跨类型联合验证功能
277
+
@@ -476,5 +476,5 @@ new ValidationError(errors, data)
476
476
 
477
477
  **版本**: v1.0.4
478
478
  **更新日期**: 2025-12-29
479
- **作者**: SchemaIO Team
479
+ **作者**: SchemaI-DSL Team
480
480
 
@@ -104,7 +104,7 @@ const withAge = dsl({
104
104
  // ✨ 新特性:merge方法
105
105
  const extendedSchema = SchemaUtils.extend(baseUser, withAge);
106
106
 
107
- console.log('合并后字段数:', Object.keys(mergedSchema.properties).length);
107
+ console.log('合并后字段数:', Object.keys(extendedSchema.properties).length);
108
108
  console.log('');
109
109
 
110
110
  // ========== 7. Schema pick/omit ==========
@@ -0,0 +1,288 @@
1
+ /**
2
+ * ConditionalBuilder 完整示例
3
+ *
4
+ * 展示链式条件构建器的各种用法
5
+ */
6
+
7
+ const { dsl, validate } = require('../index');
8
+
9
+ console.log('========================================');
10
+ console.log('ConditionalBuilder 示例');
11
+ console.log('========================================\n');
12
+
13
+ // ============================================
14
+ // 示例1:简单条件 + 错误消息
15
+ // ============================================
16
+ console.log('【示例1】简单条件 + 错误消息');
17
+ console.log('----------------------------');
18
+
19
+ const schema1 = dsl({
20
+ age: 'number!',
21
+ status: dsl.if((data) => data.age >= 18)
22
+ .message('未成年用户不能注册')
23
+ });
24
+
25
+ const testData1a = { age: 20, status: 'active' };
26
+ const result1a = validate(schema1, testData1a);
27
+ console.log('✅ 成年用户:', result1a.valid ? '验证通过' : '验证失败');
28
+
29
+ const testData1b = { age: 16, status: 'active' };
30
+ const result1b = validate(schema1, testData1b);
31
+ console.log('❌ 未成年用户:', result1b.valid ? '验证通过' : '验证失败');
32
+ if (!result1b.valid) {
33
+ console.log(' 错误消息:', result1b.errors[0].message);
34
+ }
35
+
36
+ // ============================================
37
+ // 示例2:条件 + then/else(动态Schema)
38
+ // ============================================
39
+ console.log('\n【示例2】条件 + then/else(动态Schema)');
40
+ console.log('----------------------------');
41
+
42
+ const schema2 = dsl({
43
+ userType: 'string!',
44
+ email: dsl.if((data) => data.userType === 'admin')
45
+ .then('email!') // 管理员必填
46
+ .else('email') // 普通用户可选
47
+ });
48
+
49
+ const testData2a = { userType: 'admin', email: 'admin@example.com' };
50
+ const result2a = validate(schema2, testData2a);
51
+ console.log('✅ 管理员有邮箱:', result2a.valid ? '验证通过' : '验证失败');
52
+
53
+ const testData2b = { userType: 'admin', email: '' };
54
+ const result2b = validate(schema2, testData2b);
55
+ console.log('❌ 管理员无邮箱:', result2b.valid ? '验证通过' : '验证失败');
56
+
57
+ const testData2c = { userType: 'user', email: '' };
58
+ const result2c = validate(schema2, testData2c);
59
+ console.log('✅ 普通用户无邮箱:', result2c.valid ? '验证通过' : '验证失败');
60
+
61
+ // ============================================
62
+ // 示例3:else 可选(不写 else 就不验证)
63
+ // ============================================
64
+ console.log('\n【示例3】else 可选');
65
+ console.log('----------------------------');
66
+
67
+ const schema3 = dsl({
68
+ userType: 'string!',
69
+ vipLevel: dsl.if((data) => data.userType === 'vip')
70
+ .then('enum:gold|silver|bronze!')
71
+ // 不写 else,非 vip 用户不验证 vipLevel
72
+ });
73
+
74
+ const testData3a = { userType: 'vip', vipLevel: 'gold' };
75
+ const result3a = validate(schema3, testData3a);
76
+ console.log('✅ VIP用户有等级:', result3a.valid ? '验证通过' : '验证失败');
77
+
78
+ const testData3b = { userType: 'user' };
79
+ const result3b = validate(schema3, testData3b);
80
+ console.log('✅ 普通用户无等级:', result3b.valid ? '验证通过' : '验证失败');
81
+
82
+ const testData3c = { userType: 'user', vipLevel: 'invalid_level' };
83
+ const result3c = validate(schema3, testData3c);
84
+ console.log('✅ 普通用户有无效等级:', result3c.valid ? '验证通过(不验证)' : '验证失败');
85
+
86
+ // ============================================
87
+ // 示例4:多条件 AND
88
+ // ============================================
89
+ console.log('\n【示例4】多条件 AND');
90
+ console.log('----------------------------');
91
+
92
+ const schema4 = dsl({
93
+ age: 'number!',
94
+ userType: 'string!',
95
+ email: dsl.if((data) => data.age >= 18)
96
+ .and((data) => data.userType === 'admin')
97
+ .then('email!')
98
+ .else('email')
99
+ });
100
+
101
+ const testData4a = { age: 20, userType: 'admin', email: 'admin@example.com' };
102
+ const result4a = validate(schema4, testData4a);
103
+ console.log('✅ 成年管理员有邮箱:', result4a.valid ? '验证通过' : '验证失败');
104
+
105
+ const testData4b = { age: 20, userType: 'user', email: '' };
106
+ const result4b = validate(schema4, testData4b);
107
+ console.log('✅ 成年普通用户无邮箱:', result4b.valid ? '验证通过' : '验证失败');
108
+
109
+ const testData4c = { age: 16, userType: 'admin', email: '' };
110
+ const result4c = validate(schema4, testData4c);
111
+ console.log('✅ 未成年管理员无邮箱:', result4c.valid ? '验证通过' : '验证失败');
112
+
113
+ // ============================================
114
+ // 示例5:多条件 OR
115
+ // ============================================
116
+ console.log('\n【示例5】多条件 OR');
117
+ console.log('----------------------------');
118
+
119
+ const schema5 = dsl({
120
+ age: 'number!',
121
+ status: 'string!',
122
+ reason: dsl.if((data) => data.age < 18)
123
+ .or((data) => data.status === 'blocked')
124
+ .message('不允许注册')
125
+ });
126
+
127
+ const testData5a = { age: 16, status: 'active', reason: 'test' };
128
+ const result5a = validate(schema5, testData5a);
129
+ console.log('❌ 未成年用户:', result5a.valid ? '验证通过' : `验证失败(${result5a.errors[0].message})`);
130
+
131
+ const testData5b = { age: 20, status: 'blocked', reason: 'test' };
132
+ const result5b = validate(schema5, testData5b);
133
+ console.log('❌ 被封禁用户:', result5b.valid ? '验证通过' : `验证失败(${result5b.errors[0].message})`);
134
+
135
+ const testData5c = { age: 20, status: 'active', reason: 'test' };
136
+ const result5c = validate(schema5, testData5c);
137
+ console.log('✅ 正常用户:', result5c.valid ? '验证通过' : '验证失败');
138
+
139
+ // ============================================
140
+ // 示例6:elseIf 多分支
141
+ // ============================================
142
+ console.log('\n【示例6】elseIf 多分支');
143
+ console.log('----------------------------');
144
+
145
+ const schema6 = dsl({
146
+ userType: 'string!',
147
+ permissions: dsl.if((data) => data.userType === 'admin')
148
+ .then('array<string>!')
149
+ .elseIf((data) => data.userType === 'vip')
150
+ .then('array<string>')
151
+ .else(null)
152
+ });
153
+
154
+ const testData6a = { userType: 'admin', permissions: ['read', 'write'] };
155
+ const result6a = validate(schema6, testData6a);
156
+ console.log('✅ 管理员有权限:', result6a.valid ? '验证通过' : '验证失败');
157
+
158
+ const testData6b = { userType: 'vip' };
159
+ const result6b = validate(schema6, testData6b);
160
+ console.log('✅ VIP无权限:', result6b.valid ? '验证通过' : '验证失败');
161
+
162
+ const testData6c = { userType: 'guest' };
163
+ const result6c = validate(schema6, testData6c);
164
+ console.log('✅ 游客无权限:', result6c.valid ? '验证通过' : '验证失败');
165
+
166
+ // ============================================
167
+ // 示例7:复杂场景 - 用户注册
168
+ // ============================================
169
+ console.log('\n【示例7】复杂场景 - 用户注册');
170
+ console.log('----------------------------');
171
+
172
+ const userRegistrationSchema = dsl({
173
+ username: 'string:3-32!',
174
+ age: 'number:1-120!',
175
+ userType: 'enum:admin|vip|user!',
176
+
177
+ // 未成年禁止注册
178
+ ageCheck: dsl.if((data) => data.age < 18)
179
+ .message('未成年用户不能注册'),
180
+
181
+ // 管理员必须有邮箱
182
+ email: dsl.if((data) => data.userType === 'admin')
183
+ .then('email!')
184
+ .else('email'),
185
+
186
+ // VIP用户必须有手机号
187
+ phone: dsl.if((data) => data.userType === 'vip')
188
+ .then('string:11!')
189
+ .else(null),
190
+
191
+ // 管理员和VIP可以设置昵称
192
+ nickname: dsl.if((data) => data.userType === 'admin')
193
+ .or((data) => data.userType === 'vip')
194
+ .then('string:2-20')
195
+ .else(null)
196
+ });
197
+
198
+ const testData7a = {
199
+ username: 'admin1',
200
+ age: 25,
201
+ userType: 'admin',
202
+ email: 'admin@example.com',
203
+ nickname: 'Super Admin'
204
+ };
205
+ const result7a = validate(userRegistrationSchema, testData7a);
206
+ console.log('✅ 成年管理员:', result7a.valid ? '注册成功' : '注册失败');
207
+
208
+ const testData7b = {
209
+ username: 'vip1',
210
+ age: 30,
211
+ userType: 'vip',
212
+ phone: '13800138000',
213
+ nickname: 'VIP User'
214
+ };
215
+ const result7b = validate(userRegistrationSchema, testData7b);
216
+ console.log('✅ VIP用户:', result7b.valid ? '注册成功' : '注册失败');
217
+
218
+ const testData7c = {
219
+ username: 'kid',
220
+ age: 15,
221
+ userType: 'user'
222
+ };
223
+ const result7c = validate(userRegistrationSchema, testData7c);
224
+ console.log('❌ 未成年用户:', result7c.valid ? '注册成功' : `注册失败(${result7c.errors[0].message})`);
225
+
226
+ // ============================================
227
+ // 示例8:复杂场景 - 商品发布
228
+ // ============================================
229
+ console.log('\n【示例8】复杂场景 - 商品发布');
230
+ console.log('----------------------------');
231
+
232
+ const productSchema = dsl({
233
+ title: 'string:1-100!',
234
+ price: 'number:0-!',
235
+ type: 'enum:physical|digital|service!',
236
+
237
+ // 实体商品需要重量和尺寸
238
+ weight: dsl.if((data) => data.type === 'physical')
239
+ .then('number:0-!')
240
+ .else(null),
241
+
242
+ dimensions: dsl.if((data) => data.type === 'physical')
243
+ .then('string!')
244
+ .else(null),
245
+
246
+ // 数字商品需要下载链接
247
+ downloadUrl: dsl.if((data) => data.type === 'digital')
248
+ .then('url!')
249
+ .else(null),
250
+
251
+ // 服务类需要服务时长
252
+ duration: dsl.if((data) => data.type === 'service')
253
+ .then('number:1-!')
254
+ .else(null)
255
+ });
256
+
257
+ const testData8a = {
258
+ title: '笔记本电脑',
259
+ price: 5999,
260
+ type: 'physical',
261
+ weight: 1.5,
262
+ dimensions: '30x20x2cm'
263
+ };
264
+ const result8a = validate(productSchema, testData8a);
265
+ console.log('✅ 实体商品:', result8a.valid ? '发布成功' : '发布失败');
266
+
267
+ const testData8b = {
268
+ title: '电子书',
269
+ price: 29.9,
270
+ type: 'digital',
271
+ downloadUrl: 'https://example.com/download'
272
+ };
273
+ const result8b = validate(productSchema, testData8b);
274
+ console.log('✅ 数字商品:', result8b.valid ? '发布成功' : '发布失败');
275
+
276
+ const testData8c = {
277
+ title: '咨询服务',
278
+ price: 200,
279
+ type: 'service',
280
+ duration: 60
281
+ };
282
+ const result8c = validate(productSchema, testData8c);
283
+ console.log('✅ 服务类:', result8c.valid ? '发布成功' : '发布失败');
284
+
285
+ console.log('\n========================================');
286
+ console.log('示例运行完成!');
287
+ console.log('========================================');
288
+