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.
- package/CHANGELOG.md +325 -2
- package/README.md +419 -189
- package/STATUS.md +65 -3
- package/docs/FEATURE-INDEX.md +1 -1
- package/docs/best-practices.md +3 -3
- package/docs/cache-manager.md +1 -1
- package/docs/conditional-api.md +1278 -0
- package/docs/dsl-syntax.md +1 -1
- package/docs/dynamic-locale.md +2 -2
- 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 +1 -1
- 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/i18n-error.examples.js +181 -0
- package/examples/union-type-example.js +127 -0
- package/examples/union-types-example.js +77 -0
- package/index.d.ts +655 -7
- package/index.js +54 -3
- package/lib/adapters/DslAdapter.js +14 -5
- package/lib/core/ConditionalBuilder.js +503 -0
- package/lib/core/DslBuilder.js +113 -0
- package/lib/core/Locale.js +13 -8
- package/lib/core/Validator.js +250 -2
- package/lib/errors/I18nError.js +222 -0
- package/lib/locales/en-US.js +39 -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 +39 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
| 版本 | 日期 | 变更摘要 | 详细 |
|
|
13
13
|
|------------------|------|---------|------|
|
|
14
|
+
| [v1.1.1](#v111) | 2026-01-06 | 🎉 新功能:ConditionalBuilder 独立消息支持 | [查看详情](#v111) |
|
|
15
|
+
| [v1.1.0](#v110) | 2026-01-05 | 🎉 重大功能:跨类型联合验证 + 插件系统增强 | [查看详情](#v110) |
|
|
14
16
|
| [v1.0.9](#v109) | 2026-01-04 | 🎉 重大改进:多语言支持完善 + TypeScript 类型完整 | [查看详情](#v109) |
|
|
15
17
|
| [v1.0.8](#v108) | 2026-01-04 | 优化:错误消息过滤增强 | [查看详情](#v108) |
|
|
16
18
|
| [v1.0.7](#v107) | 2026-01-04 | 修复:dsl.match/dsl.if 嵌套支持 dsl() 包裹 | [查看详情](#v107) |
|
|
17
19
|
| [v1.0.6](#v106) | 2026-01-04 | 🚨 紧急修复:TypeScript 类型污染 | [查看详情](#v106) |
|
|
18
|
-
| [v1.0.7](#v107) | 2026-01-04 | 修复:dsl.match/dsl.if 嵌套支持 dsl() 包裹 | [查看详情](#v107) |
|
|
19
|
-
| [v1.0.6](#v106) | 2026-01-04 | 🚨 紧急修复:TypeScript 类型污染 | [查看详情](#v106) |
|
|
20
20
|
| [v1.0.5](#v105) | 2026-01-04 | 测试覆盖率提升至 97% | [查看详情](#v105) |
|
|
21
21
|
| [v1.0.4](#v104) | 2025-12-31 | TypeScript 完整支持、validateAsync、ValidationError | [查看详情](#v104) |
|
|
22
22
|
| [v1.0.3](#v103) | 2025-12-31 | ⚠️ 破坏性变更:单值语法修复 | [查看详情](#v103) |
|
|
@@ -26,6 +26,329 @@
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
+
## [v1.1.1] - 2026-01-06
|
|
30
|
+
|
|
31
|
+
### 🎉 新功能
|
|
32
|
+
|
|
33
|
+
#### ConditionalBuilder 独立消息支持 - `.and()/.or()` 后可调用 `.message()`
|
|
34
|
+
|
|
35
|
+
**每个条件都可以有自己的错误消息**
|
|
36
|
+
|
|
37
|
+
现在支持在 `.and()` 和 `.or()` 后调用 `.message()` 设置独立的错误消息,让错误提示更精确。
|
|
38
|
+
|
|
39
|
+
**基础用法**:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const { dsl } = require('schema-dsl');
|
|
43
|
+
|
|
44
|
+
// ✅ v1.1.1 新功能:每个条件独立消息
|
|
45
|
+
dsl.if(d => !d)
|
|
46
|
+
.message('ACCOUNT_NOT_FOUND')
|
|
47
|
+
.and(d => d.tradable_credits < amount)
|
|
48
|
+
.message('INSUFFICIENT_TRADABLE_CREDITS')
|
|
49
|
+
.assert(account);
|
|
50
|
+
|
|
51
|
+
// 工作原理:
|
|
52
|
+
// - 第一个条件失败 → 返回 'ACCOUNT_NOT_FOUND'
|
|
53
|
+
// - 第二个条件失败 → 返回 'INSUFFICIENT_TRADABLE_CREDITS'
|
|
54
|
+
// - 所有条件通过 → 验证成功
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**特性**:
|
|
58
|
+
- ✅ 支持多个 `.and()` 条件各有独立消息
|
|
59
|
+
- ✅ 支持 `.or()` 条件独立消息
|
|
60
|
+
- ✅ 自动检测启用链式检查模式
|
|
61
|
+
- ✅ 100% 向后兼容,不影响现有代码
|
|
62
|
+
- ✅ 完整的 TypeScript 类型支持
|
|
63
|
+
|
|
64
|
+
**实际应用示例**:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// 多层验证,每层都有清晰的错误消息
|
|
68
|
+
dsl.if(d => !d)
|
|
69
|
+
.message('ACCOUNT_NOT_FOUND')
|
|
70
|
+
.and(d => d.status !== 'active')
|
|
71
|
+
.message('ACCOUNT_INACTIVE')
|
|
72
|
+
.and(d => d.tradable_credits < amount)
|
|
73
|
+
.message('INSUFFICIENT_TRADABLE_CREDITS')
|
|
74
|
+
.assert(account);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**文档链接**:
|
|
78
|
+
- [条件 API 文档](./docs/conditional-api.md)
|
|
79
|
+
- [README FAQ Q7](./README.md#q7-如何合并多个-dslif-验证)
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
#### I18nError 多语言错误抛出机制
|
|
84
|
+
|
|
85
|
+
**统一的多语言错误抛出**
|
|
86
|
+
|
|
87
|
+
新增 `I18nError` 类和 `dsl.error` 快捷方法,提供统一的多语言错误抛出机制。
|
|
88
|
+
|
|
89
|
+
**基础用法**:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
const { I18nError, dsl } = require('schema-dsl');
|
|
93
|
+
|
|
94
|
+
// 方式1:直接抛出
|
|
95
|
+
I18nError.throw('account.notFound');
|
|
96
|
+
// 中文: "账户不存在"
|
|
97
|
+
// 英文: "Account not found"
|
|
98
|
+
|
|
99
|
+
// 方式2:带参数插值
|
|
100
|
+
I18nError.throw('account.insufficientBalance', {
|
|
101
|
+
balance: 50,
|
|
102
|
+
required: 100
|
|
103
|
+
});
|
|
104
|
+
// 输出: "余额不足,当前余额50,需要100"
|
|
105
|
+
|
|
106
|
+
// 方式3:断言风格(推荐)
|
|
107
|
+
I18nError.assert(account, 'account.notFound');
|
|
108
|
+
I18nError.assert(
|
|
109
|
+
account.balance >= 100,
|
|
110
|
+
'account.insufficientBalance',
|
|
111
|
+
{ balance: account.balance, required: 100 }
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// 方式4:快捷方法
|
|
115
|
+
dsl.error.throw('user.noPermission');
|
|
116
|
+
dsl.error.assert(user.role === 'admin', 'user.noPermission');
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**特性**:
|
|
120
|
+
- ✅ 统一的错误代码格式:`{模块}.{错误类型}`
|
|
121
|
+
- ✅ 自动多语言翻译(支持中英文)
|
|
122
|
+
- ✅ 参数插值支持
|
|
123
|
+
- ✅ Express/Koa 集成(toJSON 方法)
|
|
124
|
+
- ✅ 与 `.message()` 和 `.label()` 使用相同的多语言机制
|
|
125
|
+
|
|
126
|
+
**内置错误代码**:
|
|
127
|
+
- 通用: `error.notFound`, `error.forbidden`, `error.unauthorized`
|
|
128
|
+
- 账户: `account.notFound`, `account.insufficientBalance`, `account.insufficientCredits`
|
|
129
|
+
- 用户: `user.notFound`, `user.noPermission`, `user.notVerified`
|
|
130
|
+
- 订单: `order.notPaid`, `order.paymentMissing`, `order.addressMissing`
|
|
131
|
+
|
|
132
|
+
**Express/Koa 集成**:
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
app.use((error, req, res, next) => {
|
|
136
|
+
if (error instanceof I18nError) {
|
|
137
|
+
return res.status(error.statusCode).json(error.toJSON());
|
|
138
|
+
}
|
|
139
|
+
next(error);
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**文档链接**:
|
|
144
|
+
- [使用示例](./examples/i18n-error.examples.js)
|
|
145
|
+
- [README FAQ Q8](./README.md#q8-如何统一抛出多语言错误v111)
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### 📝 测试
|
|
150
|
+
|
|
151
|
+
- **新增**: 52 个测试用例(24个独立消息 + 28个 I18nError)
|
|
152
|
+
- **总计**: 921 个测试全部通过 (100%)
|
|
153
|
+
|
|
154
|
+
### 📖 文档
|
|
155
|
+
|
|
156
|
+
- **新增**: docs/conditional-api.md 新增 600+ 行功能说明
|
|
157
|
+
- **新增**: examples/i18n-error.examples.js I18nError 使用示例
|
|
158
|
+
- **更新**: README.md FAQ Q7/Q8 添加新功能说明
|
|
159
|
+
- **更新**: index.d.ts TypeScript 类型注释和示例(I18nError + dsl.error)
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## [v1.1.0] - 2026-01-05
|
|
164
|
+
|
|
165
|
+
### 🎉 新功能
|
|
166
|
+
|
|
167
|
+
#### 1. 跨类型联合验证 - `types:` 语法
|
|
168
|
+
|
|
169
|
+
**一个字段支持多种类型**
|
|
170
|
+
|
|
171
|
+
...existing content...
|
|
172
|
+
|
|
173
|
+
**带约束的联合类型**:
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const schema = dsl({
|
|
177
|
+
contact: 'types:email|phone!', // 邮箱或手机号
|
|
178
|
+
price: 'types:number:0-|string:1-20', // 数字价格或"面议"
|
|
179
|
+
rating: 'types:integer:1-5|string:9' // 整数1-5或字符串"excellent"
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**实际应用场景**:
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
// 用户注册:灵活的联系方式
|
|
187
|
+
const registerSchema = dsl({
|
|
188
|
+
username: 'string:3-20!',
|
|
189
|
+
contact: 'types:email|phone!', // 支持邮箱或手机号注册
|
|
190
|
+
age: 'types:integer:1-150|null' // 年龄可选(null表示不填)
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**支持的类型**:
|
|
195
|
+
- ✅ 所有内置类型:`string`, `number`, `email`, `url`, `uuid` 等
|
|
196
|
+
- ✅ 插件注册的自定义类型(见下文)
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
#### 2. 插件系统增强 - DSL类型注册
|
|
201
|
+
|
|
202
|
+
**插件现在可以注册自定义DSL类型**
|
|
203
|
+
|
|
204
|
+
插件不再局限于注册ajv format/keyword,现在可以注册完整的DSL类型,让自定义类型在DSL语法中直接可用。
|
|
205
|
+
|
|
206
|
+
**改造前** (v1.0.x):
|
|
207
|
+
```javascript
|
|
208
|
+
// ❌ 只能注册ajv format(仅验证阶段生效)
|
|
209
|
+
install(schemaDsl, options) {
|
|
210
|
+
const ajv = validator.getAjv();
|
|
211
|
+
ajv.addFormat('phone-cn', { validate: /^1[3-9]\d{9}$/ });
|
|
212
|
+
|
|
213
|
+
// ❌ 无法在DSL语法中使用
|
|
214
|
+
// dsl({ phone: 'phone-cn!' }) // 报错:未知类型
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**改造后** (v1.1.0):
|
|
219
|
+
```javascript
|
|
220
|
+
// ✅ 同时注册DSL类型和ajv format
|
|
221
|
+
install(schemaDsl, options) {
|
|
222
|
+
const { DslBuilder } = schemaDsl;
|
|
223
|
+
|
|
224
|
+
// ✅ 注册到DSL解析器(解析阶段)
|
|
225
|
+
DslBuilder.registerType('phone-cn', {
|
|
226
|
+
type: 'string',
|
|
227
|
+
pattern: /^1[3-9]\d{9}$/.source,
|
|
228
|
+
minLength: 11,
|
|
229
|
+
maxLength: 11
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// ✅ 注册到ajv(验证阶段)
|
|
233
|
+
const ajv = validator.getAjv();
|
|
234
|
+
ajv.addFormat('phone-cn', { validate: /^1[3-9]\d{9}$/ });
|
|
235
|
+
|
|
236
|
+
// ✅ 现在可以在DSL中直接使用
|
|
237
|
+
// dsl({ phone: 'phone-cn!' }) // ✅ 成功
|
|
238
|
+
// dsl({ contact: 'types:email|phone-cn' }) // ✅ 在联合类型中也能用
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**新增 API**:
|
|
243
|
+
|
|
244
|
+
| API | 说明 | 用途 |
|
|
245
|
+
|-----|------|------|
|
|
246
|
+
| `DslBuilder.registerType(name, schema)` | 注册自定义类型 | 插件注册新类型 |
|
|
247
|
+
| `DslBuilder.hasType(type)` | 检查类型是否存在 | 插件验证类型 |
|
|
248
|
+
| `DslBuilder.getCustomTypes()` | 获取所有自定义类型 | 调试和测试 |
|
|
249
|
+
| `DslBuilder.clearCustomTypes()` | 清除自定义类型 | 测试用 |
|
|
250
|
+
|
|
251
|
+
**插件示例**:
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
// plugins/custom-format.js (v2.0.0)
|
|
255
|
+
module.exports = {
|
|
256
|
+
name: 'custom-format',
|
|
257
|
+
version: '2.0.0',
|
|
258
|
+
|
|
259
|
+
install(schemaDsl, options, context) {
|
|
260
|
+
const { DslBuilder } = schemaDsl;
|
|
261
|
+
const ajv = validator.getAjv();
|
|
262
|
+
|
|
263
|
+
// 定义自定义类型
|
|
264
|
+
const types = {
|
|
265
|
+
'phone-cn': {
|
|
266
|
+
pattern: /^1[3-9]\d{9}$/,
|
|
267
|
+
schema: { type: 'string', pattern: /^1[3-9]\d{9}$/.source, minLength: 11, maxLength: 11 }
|
|
268
|
+
},
|
|
269
|
+
'qq': {
|
|
270
|
+
pattern: /^[1-9][0-9]{4,10}$/,
|
|
271
|
+
schema: { type: 'string', pattern: /^[1-9][0-9]{4,10}$/.source, minLength: 5, maxLength: 11 }
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// 注册到DSL和ajv
|
|
276
|
+
Object.keys(types).forEach(name => {
|
|
277
|
+
const config = types[name];
|
|
278
|
+
DslBuilder.registerType(name, config.schema); // DSL解析
|
|
279
|
+
ajv.addFormat(name, { validate: config.pattern }); // ajv验证
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
#### 3. ConditionalBuilder 快捷验证方法
|
|
288
|
+
|
|
289
|
+
**一行代码完成条件验证**
|
|
290
|
+
|
|
291
|
+
新增4个快捷验证方法,让条件判断更简洁:
|
|
292
|
+
|
|
293
|
+
| 方法 | 返回值 | 抛错 | 异步 | 适用场景 |
|
|
294
|
+
|------|--------|------|------|---------|
|
|
295
|
+
| `.validate()` | `{ valid, errors, data }` | ❌ | ❌ | 需要错误详情 |
|
|
296
|
+
| `.validateAsync()` | `Promise<data>` | ✅ | ✅ | async/await、中间件 |
|
|
297
|
+
| `.assert()` | `data` | ✅ | ❌ | 快速失败、断言 |
|
|
298
|
+
| `.check()` | `boolean` | ❌ | ❌ | 只需判断真假 |
|
|
299
|
+
|
|
300
|
+
**使用示例**:
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
// 同步验证 - 返回详细结果
|
|
304
|
+
const result = dsl.if(d => d.age < 18)
|
|
305
|
+
.message('未成年用户不能注册')
|
|
306
|
+
.validate({ age: 16 });
|
|
307
|
+
|
|
308
|
+
// 异步验证 - 失败自动抛错
|
|
309
|
+
await dsl.if(d => d.age < 18)
|
|
310
|
+
.message('未成年用户不能注册')
|
|
311
|
+
.validateAsync({ age: 16 });
|
|
312
|
+
|
|
313
|
+
// 断言验证 - 同步抛错
|
|
314
|
+
dsl.if(d => d.age < 18).message('未成年').assert(userData);
|
|
315
|
+
|
|
316
|
+
// 快速判断 - 返回 boolean
|
|
317
|
+
const isValid = dsl.if(d => d.age < 18).message('未成年').check(data);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**优势**:
|
|
321
|
+
- ✅ 从2行代码减少到1行
|
|
322
|
+
- ✅ 支持 async/await
|
|
323
|
+
- ✅ 快速失败模式
|
|
324
|
+
- ✅ 完全向后兼容
|
|
325
|
+
|
|
326
|
+
**详细文档**:[ConditionalBuilder API](./docs/conditional-api.md)
|
|
327
|
+
|
|
328
|
+
### Changed
|
|
329
|
+
|
|
330
|
+
- 📖 优化 README.md,新增"条件验证 - 一行代码搞定"章节
|
|
331
|
+
- 📖 完善 API 文档和使用示例
|
|
332
|
+
- 📖 新增4个方法的 TypeScript 类型定义
|
|
333
|
+
|
|
334
|
+
### Tests
|
|
335
|
+
|
|
336
|
+
- ✅ 新增 45个测试用例(总计 807个测试)
|
|
337
|
+
- `.validate()` 方法: 9个
|
|
338
|
+
- `.validateAsync()` 方法: 6个
|
|
339
|
+
- `.assert()` 方法: 6个
|
|
340
|
+
- `.check()` 方法: 5个
|
|
341
|
+
- 边界情况: 12个
|
|
342
|
+
- 特殊场景: 7个
|
|
343
|
+
- ✅ 测试覆盖率 100%(807/807)
|
|
344
|
+
- ✅ 覆盖 null、undefined、空值、NaN、异常处理等所有边界情况
|
|
345
|
+
|
|
346
|
+
### 向后兼容
|
|
347
|
+
|
|
348
|
+
✅ **完全向后兼容**,所有现有代码无需修改
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
29
352
|
## [v1.0.9] - 2026-01-04
|
|
30
353
|
|
|
31
354
|
### 🎉 重大改进
|