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/CHANGELOG.md
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
| 版本 | 日期 | 变更摘要 | 详细 |
|
|
13
13
|
|------------------|------|---------|------|
|
|
14
|
-
| [v1.0
|
|
15
|
-
| [v1.0.
|
|
16
|
-
| [v1.0.
|
|
14
|
+
| [v1.1.0](#v110) | 2026-01-05 | 🎉 重大功能:跨类型联合验证 + 插件系统增强 | [查看详情](#v110) |
|
|
15
|
+
| [v1.0.9](#v109) | 2026-01-04 | 🎉 重大改进:多语言支持完善 + TypeScript 类型完整 | [查看详情](#v109) |
|
|
16
|
+
| [v1.0.8](#v108) | 2026-01-04 | 优化:错误消息过滤增强 | [查看详情](#v108) |
|
|
17
17
|
| [v1.0.7](#v107) | 2026-01-04 | 修复:dsl.match/dsl.if 嵌套支持 dsl() 包裹 | [查看详情](#v107) |
|
|
18
18
|
| [v1.0.6](#v106) | 2026-01-04 | 🚨 紧急修复:TypeScript 类型污染 | [查看详情](#v106) |
|
|
19
19
|
| [v1.0.5](#v105) | 2026-01-04 | 测试覆盖率提升至 97% | [查看详情](#v105) |
|
|
@@ -25,6 +25,341 @@
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
+
## [v1.1.0] - 2026-01-05
|
|
29
|
+
|
|
30
|
+
### 🎉 新功能
|
|
31
|
+
|
|
32
|
+
#### 1. 跨类型联合验证 - `types:` 语法
|
|
33
|
+
|
|
34
|
+
**一个字段支持多种类型**
|
|
35
|
+
|
|
36
|
+
现在可以使用 `types:` 前缀定义跨类型联合验证,支持字段匹配多种不同的数据类型。
|
|
37
|
+
|
|
38
|
+
**基础用法**:
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
const { dsl, validate } = require('schema-dsl');
|
|
42
|
+
|
|
43
|
+
// 字段可以是字符串或数字
|
|
44
|
+
const schema = dsl({
|
|
45
|
+
value: 'types:string|number'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
validate(schema, { value: 'hello' }); // ✅ 通过
|
|
49
|
+
validate(schema, { value: 123 }); // ✅ 通过
|
|
50
|
+
validate(schema, { value: true }); // ❌ 失败
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**带约束的联合类型**:
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
const schema = dsl({
|
|
57
|
+
contact: 'types:email|phone!', // 邮箱或手机号
|
|
58
|
+
price: 'types:number:0-|string:1-20', // 数字价格或"面议"
|
|
59
|
+
rating: 'types:integer:1-5|string:9' // 整数1-5或字符串"excellent"
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**实际应用场景**:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
// 用户注册:灵活的联系方式
|
|
67
|
+
const registerSchema = dsl({
|
|
68
|
+
username: 'string:3-20!',
|
|
69
|
+
contact: 'types:email|phone!', // 支持邮箱或手机号注册
|
|
70
|
+
age: 'types:integer:1-150|null' // 年龄可选(null表示不填)
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**支持的类型**:
|
|
75
|
+
- ✅ 所有内置类型:`string`, `number`, `email`, `url`, `uuid` 等
|
|
76
|
+
- ✅ 插件注册的自定义类型(见下文)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
#### 2. 插件系统增强 - DSL类型注册
|
|
81
|
+
|
|
82
|
+
**插件现在可以注册自定义DSL类型**
|
|
83
|
+
|
|
84
|
+
插件不再局限于注册ajv format/keyword,现在可以注册完整的DSL类型,让自定义类型在DSL语法中直接可用。
|
|
85
|
+
|
|
86
|
+
**改造前** (v1.0.x):
|
|
87
|
+
```javascript
|
|
88
|
+
// ❌ 只能注册ajv format(仅验证阶段生效)
|
|
89
|
+
install(schemaDsl, options) {
|
|
90
|
+
const ajv = validator.getAjv();
|
|
91
|
+
ajv.addFormat('phone-cn', { validate: /^1[3-9]\d{9}$/ });
|
|
92
|
+
|
|
93
|
+
// ❌ 无法在DSL语法中使用
|
|
94
|
+
// dsl({ phone: 'phone-cn!' }) // 报错:未知类型
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**改造后** (v1.1.0):
|
|
99
|
+
```javascript
|
|
100
|
+
// ✅ 同时注册DSL类型和ajv format
|
|
101
|
+
install(schemaDsl, options) {
|
|
102
|
+
const { DslBuilder } = schemaDsl;
|
|
103
|
+
|
|
104
|
+
// ✅ 注册到DSL解析器(解析阶段)
|
|
105
|
+
DslBuilder.registerType('phone-cn', {
|
|
106
|
+
type: 'string',
|
|
107
|
+
pattern: /^1[3-9]\d{9}$/.source,
|
|
108
|
+
minLength: 11,
|
|
109
|
+
maxLength: 11
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ✅ 注册到ajv(验证阶段)
|
|
113
|
+
const ajv = validator.getAjv();
|
|
114
|
+
ajv.addFormat('phone-cn', { validate: /^1[3-9]\d{9}$/ });
|
|
115
|
+
|
|
116
|
+
// ✅ 现在可以在DSL中直接使用
|
|
117
|
+
// dsl({ phone: 'phone-cn!' }) // ✅ 成功
|
|
118
|
+
// dsl({ contact: 'types:email|phone-cn' }) // ✅ 在联合类型中也能用
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**新增 API**:
|
|
123
|
+
|
|
124
|
+
| API | 说明 | 用途 |
|
|
125
|
+
|-----|------|------|
|
|
126
|
+
| `DslBuilder.registerType(name, schema)` | 注册自定义类型 | 插件注册新类型 |
|
|
127
|
+
| `DslBuilder.hasType(type)` | 检查类型是否存在 | 插件验证类型 |
|
|
128
|
+
| `DslBuilder.getCustomTypes()` | 获取所有自定义类型 | 调试和测试 |
|
|
129
|
+
| `DslBuilder.clearCustomTypes()` | 清除自定义类型 | 测试用 |
|
|
130
|
+
|
|
131
|
+
**插件示例**:
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
// plugins/custom-format.js (v2.0.0)
|
|
135
|
+
module.exports = {
|
|
136
|
+
name: 'custom-format',
|
|
137
|
+
version: '2.0.0',
|
|
138
|
+
|
|
139
|
+
install(schemaDsl, options, context) {
|
|
140
|
+
const { DslBuilder } = schemaDsl;
|
|
141
|
+
const ajv = validator.getAjv();
|
|
142
|
+
|
|
143
|
+
// 定义自定义类型
|
|
144
|
+
const types = {
|
|
145
|
+
'phone-cn': {
|
|
146
|
+
pattern: /^1[3-9]\d{9}$/,
|
|
147
|
+
schema: { type: 'string', pattern: /^1[3-9]\d{9}$/.source, minLength: 11, maxLength: 11 }
|
|
148
|
+
},
|
|
149
|
+
'qq': {
|
|
150
|
+
pattern: /^[1-9][0-9]{4,10}$/,
|
|
151
|
+
schema: { type: 'string', pattern: /^[1-9][0-9]{4,10}$/.source, minLength: 5, maxLength: 11 }
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// 注册到DSL和ajv
|
|
156
|
+
Object.keys(types).forEach(name => {
|
|
157
|
+
const config = types[name];
|
|
158
|
+
DslBuilder.registerType(name, config.schema); // DSL解析
|
|
159
|
+
ajv.addFormat(name, { validate: config.pattern }); // ajv验证
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
#### 3. ConditionalBuilder 快捷验证方法
|
|
168
|
+
|
|
169
|
+
**一行代码完成条件验证**
|
|
170
|
+
|
|
171
|
+
新增4个快捷验证方法,让条件判断更简洁:
|
|
172
|
+
|
|
173
|
+
| 方法 | 返回值 | 抛错 | 异步 | 适用场景 |
|
|
174
|
+
|------|--------|------|------|---------|
|
|
175
|
+
| `.validate()` | `{ valid, errors, data }` | ❌ | ❌ | 需要错误详情 |
|
|
176
|
+
| `.validateAsync()` | `Promise<data>` | ✅ | ✅ | async/await、中间件 |
|
|
177
|
+
| `.assert()` | `data` | ✅ | ❌ | 快速失败、断言 |
|
|
178
|
+
| `.check()` | `boolean` | ❌ | ❌ | 只需判断真假 |
|
|
179
|
+
|
|
180
|
+
**使用示例**:
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
// 同步验证 - 返回详细结果
|
|
184
|
+
const result = dsl.if(d => d.age < 18)
|
|
185
|
+
.message('未成年用户不能注册')
|
|
186
|
+
.validate({ age: 16 });
|
|
187
|
+
|
|
188
|
+
// 异步验证 - 失败自动抛错
|
|
189
|
+
await dsl.if(d => d.age < 18)
|
|
190
|
+
.message('未成年用户不能注册')
|
|
191
|
+
.validateAsync({ age: 16 });
|
|
192
|
+
|
|
193
|
+
// 断言验证 - 同步抛错
|
|
194
|
+
dsl.if(d => d.age < 18).message('未成年').assert(userData);
|
|
195
|
+
|
|
196
|
+
// 快速判断 - 返回 boolean
|
|
197
|
+
const isValid = dsl.if(d => d.age < 18).message('未成年').check(data);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**优势**:
|
|
201
|
+
- ✅ 从2行代码减少到1行
|
|
202
|
+
- ✅ 支持 async/await
|
|
203
|
+
- ✅ 快速失败模式
|
|
204
|
+
- ✅ 完全向后兼容
|
|
205
|
+
|
|
206
|
+
**详细文档**:[ConditionalBuilder API](./docs/conditional-api.md)
|
|
207
|
+
|
|
208
|
+
### Changed
|
|
209
|
+
|
|
210
|
+
- 📖 优化 README.md,新增"条件验证 - 一行代码搞定"章节
|
|
211
|
+
- 📖 完善 API 文档和使用示例
|
|
212
|
+
- 📖 新增4个方法的 TypeScript 类型定义
|
|
213
|
+
|
|
214
|
+
### Tests
|
|
215
|
+
|
|
216
|
+
- ✅ 新增 45个测试用例(总计 807个测试)
|
|
217
|
+
- `.validate()` 方法: 9个
|
|
218
|
+
- `.validateAsync()` 方法: 6个
|
|
219
|
+
- `.assert()` 方法: 6个
|
|
220
|
+
- `.check()` 方法: 5个
|
|
221
|
+
- 边界情况: 12个
|
|
222
|
+
- 特殊场景: 7个
|
|
223
|
+
- ✅ 测试覆盖率 100%(807/807)
|
|
224
|
+
- ✅ 覆盖 null、undefined、空值、NaN、异常处理等所有边界情况
|
|
225
|
+
|
|
226
|
+
### 向后兼容
|
|
227
|
+
|
|
228
|
+
✅ **完全向后兼容**,所有现有代码无需修改
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## [v1.0.9] - 2026-01-04
|
|
233
|
+
|
|
234
|
+
### 🎉 重大改进
|
|
235
|
+
|
|
236
|
+
#### 1. 多语言文档完善 ⭐⭐⭐
|
|
237
|
+
|
|
238
|
+
**优化内容**:
|
|
239
|
+
|
|
240
|
+
1. ✅ **统一文档描述为"首次加载,运行时切换"**
|
|
241
|
+
- 修复 `i18n-user-guide.md` 配置示例错误
|
|
242
|
+
- 更新 `add-custom-locale.md`、`dynamic-locale.md`、`frontend-i18n-guide.md`
|
|
243
|
+
- 添加完整的配置和使用示例
|
|
244
|
+
- 添加错误示例对比(运行时单个加载 vs 首次加载)
|
|
245
|
+
|
|
246
|
+
2. ✅ **添加文档导航**
|
|
247
|
+
- 在 `i18n.md` 添加多语言文档导航
|
|
248
|
+
- 帮助用户快速找到所需文档
|
|
249
|
+
|
|
250
|
+
3. ✅ **性能数据更新**
|
|
251
|
+
- 更新性能对比数据:**2,879,606 ops/s**(提升3.19倍)
|
|
252
|
+
- 比 Zod 快 1.58倍
|
|
253
|
+
- 比 Joi 快 9.61倍
|
|
254
|
+
- 比 Yup 快 27.07倍
|
|
255
|
+
|
|
256
|
+
#### 2. 多语言支持完善(v1.0.9 早期)⭐⭐⭐
|
|
257
|
+
|
|
258
|
+
**问题描述**:
|
|
259
|
+
- TypeScript 类型定义缺少 `options` 参数
|
|
260
|
+
- 多语言切换需要修改全局状态,并发场景不安全
|
|
261
|
+
- 自定义错误消息查找逻辑有问题
|
|
262
|
+
|
|
263
|
+
**修复内容**:
|
|
264
|
+
|
|
265
|
+
1. ✅ **TypeScript 类型定义完善**
|
|
266
|
+
```typescript
|
|
267
|
+
// 新增 ValidateOptions 接口
|
|
268
|
+
interface ValidateOptions {
|
|
269
|
+
format?: boolean; // 是否格式化错误
|
|
270
|
+
locale?: string; // 动态指定语言(如 'zh-CN', 'en-US')
|
|
271
|
+
messages?: ErrorMessages; // 自定义错误消息
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 更新函数签名
|
|
275
|
+
function validate(
|
|
276
|
+
schema: JSONSchema | DslBuilder,
|
|
277
|
+
data: any,
|
|
278
|
+
options?: ValidateOptions // ← 新增
|
|
279
|
+
): ValidationResult;
|
|
280
|
+
|
|
281
|
+
// 更新 Validator.validate 方法签名
|
|
282
|
+
validate(
|
|
283
|
+
schema: JSONSchema | DslBuilder | Function,
|
|
284
|
+
data: any,
|
|
285
|
+
options?: ValidateOptions // ← 新增
|
|
286
|
+
): ValidationResult;
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
2. ✅ **参数化语言切换(无需全局状态)**
|
|
290
|
+
```javascript
|
|
291
|
+
// 旧方式(需要修改全局状态)
|
|
292
|
+
Locale.setLocale('zh-CN');
|
|
293
|
+
const result = validate(schema, data);
|
|
294
|
+
|
|
295
|
+
// 新方式(参数化,支持并发)
|
|
296
|
+
const result = validate(schema, data, { locale: 'zh-CN' });
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
3. ✅ **修复自定义错误消息查找逻辑**
|
|
300
|
+
- **Bug 1**: 优先使用通用模板而非 schema 自定义消息
|
|
301
|
+
- **Bug 2**: 无法正确处理三种自定义消息格式:
|
|
302
|
+
- 键引用:`_customMessages.pattern = "pattern.objectId"`
|
|
303
|
+
- 模板字符串:`_customMessages.min = "{{#label}}必须大于{{#limit}}"`
|
|
304
|
+
- 最终消息:`_customMessages.pattern = "手机号格式不正确"`
|
|
305
|
+
|
|
306
|
+
**修复效果**:
|
|
307
|
+
|
|
308
|
+
```javascript
|
|
309
|
+
// 1. 并发调用不同语言
|
|
310
|
+
const [resultZh, resultEn] = await Promise.all([
|
|
311
|
+
validate(schema, data, { locale: 'zh-CN' }),
|
|
312
|
+
validate(schema, data, { locale: 'en-US' })
|
|
313
|
+
]);
|
|
314
|
+
// ✅ 两次调用互不影响
|
|
315
|
+
|
|
316
|
+
// 2. 自定义消息(键引用)
|
|
317
|
+
const schema = dsl('objectId!');
|
|
318
|
+
const result = validate(schema, 'invalid', { locale: 'zh-CN' });
|
|
319
|
+
// message: "无效的 ObjectId" ✅
|
|
320
|
+
|
|
321
|
+
// 3. 自定义消息(模板字符串)
|
|
322
|
+
const schema = dsl({
|
|
323
|
+
age: 'number:18-!'.label('年龄')
|
|
324
|
+
.messages({ 'min': '{{#label}}必须大于{{#limit}}' })
|
|
325
|
+
});
|
|
326
|
+
const result = validate(schema, { age: 10 });
|
|
327
|
+
// message: "年龄必须大于18" ✅
|
|
328
|
+
|
|
329
|
+
// 4. 自定义消息(最终消息)
|
|
330
|
+
const schema = dsl({
|
|
331
|
+
phone: 'string!'.pattern(/^1[3-9]\d{9}$/)
|
|
332
|
+
.messages({ 'pattern': '手机号格式不正确' })
|
|
333
|
+
});
|
|
334
|
+
const result = validate(schema, { phone: 'invalid' });
|
|
335
|
+
// message: "手机号格式不正确" ✅
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**技术细节**:
|
|
339
|
+
- 修改 `index.js` 中的 `validate()` 函数,传递 `options` 参数
|
|
340
|
+
- 修改 `Validator.js` 中的 `validate()` 方法,直接传递 `locale` 到 ErrorFormatter
|
|
341
|
+
- 修改 `ErrorFormatter.js`:
|
|
342
|
+
- `format()` 方法支持对象参数 `{ locale, messages }`
|
|
343
|
+
- `formatDetailed()` 方法新增 `customMessages` 参数
|
|
344
|
+
- 优化自定义消息查找逻辑:优先检查 schema 中的 `_customMessages`
|
|
345
|
+
|
|
346
|
+
**测试结果**:
|
|
347
|
+
- ✅ 725 个测试全部通过(从 51 个失败修复到 0 个失败)
|
|
348
|
+
- ✅ 完全向后兼容,不破坏现有 API
|
|
349
|
+
|
|
350
|
+
### Changed (变更)
|
|
351
|
+
|
|
352
|
+
- 📝 **API**: `validate()` 和 `Validator.validate()` 新增 `options` 参数(向后兼容)
|
|
353
|
+
- 🔧 **内部**: `ErrorFormatter.formatDetailed()` 新增 `customMessages` 参数
|
|
354
|
+
|
|
355
|
+
### Fixed (修复)
|
|
356
|
+
|
|
357
|
+
- 🐛 修复 TypeScript 类型定义缺少 `options` 参数
|
|
358
|
+
- 🐛 修复自定义错误消息查找逻辑(优先级问题)
|
|
359
|
+
- 🐛 修复自定义消息无法区分键引用/模板字符串/最终消息
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
28
363
|
## [v1.0.8] - 2026-01-04
|
|
29
364
|
|
|
30
365
|
### Improved (优化)
|