schema-dsl 1.0.8 → 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.
- package/CHANGELOG.md +338 -3
- package/README.md +296 -17
- package/STATUS.md +74 -3
- package/docs/FEATURE-INDEX.md +1 -1
- package/docs/add-custom-locale.md +395 -0
- package/docs/best-practices.md +3 -3
- package/docs/cache-manager.md +1 -1
- package/docs/conditional-api.md +1032 -0
- package/docs/dsl-syntax.md +1 -1
- package/docs/dynamic-locale.md +76 -30
- package/docs/error-handling.md +2 -2
- package/docs/export-guide.md +2 -2
- package/docs/export-limitations.md +3 -3
- package/docs/faq.md +6 -6
- package/docs/frontend-i18n-guide.md +19 -16
- package/docs/i18n-user-guide.md +7 -9
- package/docs/i18n.md +65 -2
- package/docs/mongodb-exporter.md +3 -3
- package/docs/multi-type-support.md +12 -2
- package/docs/mysql-exporter.md +1 -1
- package/docs/plugin-system.md +4 -4
- package/docs/postgresql-exporter.md +1 -1
- package/docs/quick-start.md +4 -4
- package/docs/troubleshooting.md +2 -2
- package/docs/type-reference.md +5 -5
- package/docs/typescript-guide.md +5 -6
- package/docs/union-type-guide.md +147 -0
- package/docs/union-types.md +277 -0
- package/docs/validate-async.md +1 -1
- package/examples/array-dsl-example.js +1 -1
- package/examples/conditional-example.js +288 -0
- package/examples/conditional-non-object.js +129 -0
- package/examples/conditional-validate-example.js +321 -0
- package/examples/union-type-example.js +127 -0
- package/examples/union-types-example.js +77 -0
- package/index.d.ts +395 -12
- package/index.js +31 -4
- package/lib/adapters/DslAdapter.js +14 -5
- package/lib/core/ConditionalBuilder.js +401 -0
- package/lib/core/DslBuilder.js +113 -0
- package/lib/core/ErrorFormatter.js +81 -33
- package/lib/core/Locale.js +13 -8
- package/lib/core/Validator.js +252 -16
- package/lib/locales/en-US.js +14 -0
- package/lib/locales/es-ES.js +4 -0
- package/lib/locales/fr-FR.js +4 -0
- package/lib/locales/ja-JP.js +9 -0
- package/lib/locales/zh-CN.js +14 -0
- package/package.json +5 -2
package/docs/typescript-guide.md
CHANGED
|
@@ -479,12 +479,11 @@ const schema = dsl({
|
|
|
479
479
|
const schema = dsl({
|
|
480
480
|
userType: dsl('string!').label('用户类型'),
|
|
481
481
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
})
|
|
482
|
+
// 使用 dsl.match() 根据 userType 字段动态验证
|
|
483
|
+
companyName: dsl.match('userType', {
|
|
484
|
+
'company': 'string!', // 企业用户必填
|
|
485
|
+
'_default': 'string' // 个人用户可选
|
|
486
|
+
})
|
|
488
487
|
});
|
|
489
488
|
```
|
|
490
489
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# 一个字段支持多种类型
|
|
2
|
+
|
|
3
|
+
> 使用 `.pattern()` 方法匹配多种格式
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 基本用法
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const { dsl, validate } = require('schema-dsl');
|
|
11
|
+
|
|
12
|
+
// 邮箱 或 手机号
|
|
13
|
+
const schema = dsl({
|
|
14
|
+
contact: dsl('string!')
|
|
15
|
+
.pattern(/^([^\s@]+@[^\s@]+\.[^\s@]+|1[3-9]\d{9})$/)
|
|
16
|
+
.messages({ pattern: '必须是邮箱或手机号' })
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
validate(schema, { contact: 'test@example.com' }); // ✅
|
|
20
|
+
validate(schema, { contact: '13800138000' }); // ✅
|
|
21
|
+
validate(schema, { contact: 'invalid' }); // ❌
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**说明**:
|
|
25
|
+
- 正则中使用 `|` 表示"或",括号 `()` 分组
|
|
26
|
+
- 使用 `.messages()` 设置错误消息,支持多语言
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 常用示例
|
|
31
|
+
|
|
32
|
+
### 用户登录(用户名或邮箱)
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
const loginSchema = dsl({
|
|
36
|
+
username: dsl('string:3-32!')
|
|
37
|
+
.pattern(/^([^\s@]+@[^\s@]+\.[^\s@]+|[a-zA-Z0-9_]+)$/)
|
|
38
|
+
.messages({ pattern: '必须是邮箱或用户名' }),
|
|
39
|
+
password: 'string:8-32!'
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 联系方式(邮箱或手机号)
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
const schema = dsl({
|
|
47
|
+
contact: dsl('string!')
|
|
48
|
+
.pattern(/^([^\s@]+@[^\s@]+\.[^\s@]+|1[3-9]\d{9})$/)
|
|
49
|
+
.messages({ pattern: '必须是邮箱或手机号' })
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### URL(http 或 https)
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
const schema = dsl({
|
|
57
|
+
website: dsl('string!')
|
|
58
|
+
.pattern(/^https?:\/\/.+$/)
|
|
59
|
+
.messages({ pattern: '必须是 http 或 https 开头的 URL' })
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 支持多语言
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
// 使用多语言 key
|
|
69
|
+
const schema = dsl({
|
|
70
|
+
contact: dsl('string!')
|
|
71
|
+
.pattern(/^([^\s@]+@[^\s@]+\.[^\s@]+|1[3-9]\d{9})$/)
|
|
72
|
+
.messages({ pattern: 'pattern.emailOrPhone' }) // 多语言 key
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 验证时指定语言
|
|
76
|
+
validate(schema, { contact: 'invalid' }, { locale: 'zh-CN' }); // 中文:必须是邮箱或手机号
|
|
77
|
+
validate(schema, { contact: 'invalid' }, { locale: 'en-US' }); // 英文:Must be an email or phone number
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**内置多语言 key**:
|
|
81
|
+
- `pattern.emailOrPhone` - 邮箱或手机号
|
|
82
|
+
- `pattern.usernameOrEmail` - 用户名或邮箱
|
|
83
|
+
- `pattern.httpOrHttps` - http 或 https URL
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 正则表达式速查
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
// 邮箱 或 手机号
|
|
91
|
+
/^([^\s@]+@[^\s@]+\.[^\s@]+|1[3-9]\d{9})$/
|
|
92
|
+
|
|
93
|
+
// http 或 https
|
|
94
|
+
/^https?:\/\/.+$/
|
|
95
|
+
|
|
96
|
+
// 用户名 或 邮箱
|
|
97
|
+
/^([^\s@]+@[^\s@]+\.[^\s@]+|[a-zA-Z0-9_]{3,32})$/
|
|
98
|
+
|
|
99
|
+
// 数字ID 或 UUID
|
|
100
|
+
/^(\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i
|
|
101
|
+
|
|
102
|
+
// 多个邮箱域名
|
|
103
|
+
/^[^\s@]+@(gmail\.com|qq\.com|163\.com)$/
|
|
104
|
+
|
|
105
|
+
// 中国或美国手机号
|
|
106
|
+
/^(1[3-9]\d{9}|\+1\d{10})$/
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## 完整示例
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
const { dsl, validate } = require('schema-dsl');
|
|
116
|
+
|
|
117
|
+
const registerSchema = dsl({
|
|
118
|
+
name: 'string:1-50!',
|
|
119
|
+
contact: dsl('string!')
|
|
120
|
+
.pattern(/^([^\s@]+@[^\s@]+\.[^\s@]+|1[3-9]\d{9})$/)
|
|
121
|
+
.messages({ pattern: '必须是邮箱或手机号' })
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const testData = [
|
|
125
|
+
{ name: '张三', contact: 'zhangsan@example.com' },
|
|
126
|
+
{ name: '李四', contact: '13800138000' },
|
|
127
|
+
{ name: '王五', contact: 'invalid' }
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
testData.forEach((data, index) => {
|
|
131
|
+
const result = validate(registerSchema, data);
|
|
132
|
+
console.log(`测试${index + 1}:`, result.valid ? '✅' : '❌');
|
|
133
|
+
if (!result.valid) {
|
|
134
|
+
console.log(' 错误:', result.errors[0].message);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**输出**:
|
|
140
|
+
```
|
|
141
|
+
测试1: ✅
|
|
142
|
+
测试2: ✅
|
|
143
|
+
测试3: ❌
|
|
144
|
+
错误: 必须是邮箱或手机号
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
|
|
@@ -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
|
+
|
package/docs/validate-async.md
CHANGED
|
@@ -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(
|
|
107
|
+
console.log('合并后字段数:', Object.keys(extendedSchema.properties).length);
|
|
108
108
|
console.log('');
|
|
109
109
|
|
|
110
110
|
// ========== 7. Schema pick/omit ==========
|