schema-dsl 1.1.2 → 1.1.4
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 +75 -1236
- package/README.md +221 -2
- package/STATUS.md +67 -2
- package/changelogs/v1.0.0.md +328 -0
- package/changelogs/v1.0.9.md +367 -0
- package/changelogs/v1.1.0.md +389 -0
- package/changelogs/v1.1.1.md +308 -0
- package/changelogs/v1.1.2.md +183 -0
- package/changelogs/v1.1.3.md +161 -0
- package/changelogs/v1.1.4.md +432 -0
- package/docs/dsl-syntax.md +14 -3
- package/docs/optional-marker-guide.md +321 -0
- package/docs/runtime-locale-support.md +443 -0
- package/index.d.ts +126 -10
- package/index.js +6 -3
- package/index.mjs +2 -2
- package/lib/core/DslBuilder.js +11 -2
- package/lib/core/ErrorFormatter.js +6 -1
- package/lib/errors/I18nError.js +21 -6
- package/package.json +1 -1
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# v1.1.0 变更日志
|
|
2
|
+
|
|
3
|
+
> **发布日期**: 2026-01-05
|
|
4
|
+
> **版本号**: v1.1.0
|
|
5
|
+
> **类型**: 🎉 重大功能发布
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📋 变更概览
|
|
10
|
+
|
|
11
|
+
| 类型 | 数量 | 说明 |
|
|
12
|
+
|------|------|------|
|
|
13
|
+
| 🎉 新功能 | 2 | 跨类型联合验证、运行时多语言支持 |
|
|
14
|
+
| ⚡ 功能增强 | 1 | 插件系统增强 |
|
|
15
|
+
| 📚 文档更新 | 3 | 新增联合类型、运行时多语言、插件文档 |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🎉 新功能
|
|
20
|
+
|
|
21
|
+
### 1. 跨类型联合验证 (Union Types)
|
|
22
|
+
|
|
23
|
+
**一行代码支持多种类型,告别繁琐的类型判断**
|
|
24
|
+
|
|
25
|
+
#### 基础用法
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
const { dsl, validate } = require('schema-dsl');
|
|
29
|
+
|
|
30
|
+
// 字段可以是字符串或数字
|
|
31
|
+
const schema = dsl({
|
|
32
|
+
value: 'types:string|number'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
validate(schema, { value: 'hello' }); // ✅ 通过
|
|
36
|
+
validate(schema, { value: 123 }); // ✅ 通过
|
|
37
|
+
validate(schema, { value: true }); // ❌ 失败
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### 带约束的联合类型
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// 邮箱或手机号
|
|
44
|
+
const schema1 = dsl({
|
|
45
|
+
contact: 'types:email|phone!'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 数字价格或"面议"字符串
|
|
49
|
+
const schema2 = dsl({
|
|
50
|
+
price: 'types:number:0-|string:1-20'
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 枚举或空值
|
|
54
|
+
const schema3 = dsl({
|
|
55
|
+
status: 'types:active|inactive|null'
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### 实际应用场景
|
|
60
|
+
|
|
61
|
+
**场景1:用户注册 - 支持邮箱或手机号**
|
|
62
|
+
```javascript
|
|
63
|
+
const registerSchema = dsl({
|
|
64
|
+
username: 'string:3-20!',
|
|
65
|
+
contact: 'types:email|phone!', // 灵活的联系方式
|
|
66
|
+
password: 'string:8-32!',
|
|
67
|
+
age: 'types:integer:1-150|null' // 年龄可选
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ✅ 使用邮箱注册
|
|
71
|
+
validate(registerSchema, {
|
|
72
|
+
username: 'john',
|
|
73
|
+
contact: 'john@example.com',
|
|
74
|
+
password: '12345678',
|
|
75
|
+
age: 25
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ✅ 使用手机号注册
|
|
79
|
+
validate(registerSchema, {
|
|
80
|
+
username: 'jane',
|
|
81
|
+
contact: '13800138000',
|
|
82
|
+
password: 'abcd1234',
|
|
83
|
+
age: null
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**场景2:商品价格 - 数字或"面议"**
|
|
88
|
+
```javascript
|
|
89
|
+
const productSchema = dsl({
|
|
90
|
+
name: 'string:1-100!',
|
|
91
|
+
price: 'types:number:0-|string:1-20', // 价格或"面议"
|
|
92
|
+
stock: 'integer:0-'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ✅ 数字价格
|
|
96
|
+
validate(productSchema, {
|
|
97
|
+
name: '商品A',
|
|
98
|
+
price: 99.99,
|
|
99
|
+
stock: 100
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ✅ "面议"价格
|
|
103
|
+
validate(productSchema, {
|
|
104
|
+
name: '商品B',
|
|
105
|
+
price: '面议',
|
|
106
|
+
stock: 0
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**场景3:API参数 - 单个ID或ID数组**
|
|
111
|
+
```javascript
|
|
112
|
+
const querySchema = dsl({
|
|
113
|
+
id: 'types:string|array<string>' // 单个或多个ID
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ✅ 单个ID
|
|
117
|
+
validate(querySchema, { id: '123' });
|
|
118
|
+
|
|
119
|
+
// ✅ ID数组
|
|
120
|
+
validate(querySchema, { id: ['123', '456', '789'] });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### 技术实现
|
|
124
|
+
|
|
125
|
+
- ✅ 支持基础类型联合:`string|number|boolean`
|
|
126
|
+
- ✅ 支持格式类型联合:`email|phone|url`
|
|
127
|
+
- ✅ 支持带约束的联合:`number:0-100|string:1-20`
|
|
128
|
+
- ✅ 支持null值:`string|null`
|
|
129
|
+
- ✅ 支持数组类型:`string|array<string>`
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### 2. 运行时多语言支持
|
|
134
|
+
|
|
135
|
+
**无需修改全局设置,每次调用时指定语言**
|
|
136
|
+
|
|
137
|
+
#### 核心API
|
|
138
|
+
|
|
139
|
+
**dsl.error.create()** - 创建多语言错误
|
|
140
|
+
```javascript
|
|
141
|
+
dsl.error.create(
|
|
142
|
+
code: string, // 错误代码(如 'account.notFound')
|
|
143
|
+
params?: object, // 参数插值(如 { balance: 50 })
|
|
144
|
+
statusCode?: number, // HTTP 状态码(默认 400)
|
|
145
|
+
locale?: string // 🆕 运行时语言(如 'en-US')
|
|
146
|
+
): I18nError
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**dsl.error.throw()** - 抛出多语言错误
|
|
150
|
+
```javascript
|
|
151
|
+
dsl.error.throw(
|
|
152
|
+
code: string,
|
|
153
|
+
params?: object,
|
|
154
|
+
statusCode?: number,
|
|
155
|
+
locale?: string // 🆕 运行时语言
|
|
156
|
+
): never
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**dsl.error.assert()** - 断言多语言错误
|
|
160
|
+
```javascript
|
|
161
|
+
dsl.error.assert(
|
|
162
|
+
condition: any,
|
|
163
|
+
code: string,
|
|
164
|
+
params?: object,
|
|
165
|
+
statusCode?: number,
|
|
166
|
+
locale?: string // 🆕 运行时语言
|
|
167
|
+
): void
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### 使用示例
|
|
171
|
+
|
|
172
|
+
**方式1:根据请求头动态返回不同语言**
|
|
173
|
+
```javascript
|
|
174
|
+
app.post('/api/account', (req, res) => {
|
|
175
|
+
const locale = req.headers['accept-language'] || 'en-US';
|
|
176
|
+
const account = getAccount(req.user.id);
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
// 运行时指定语言,不影响全局设置
|
|
180
|
+
dsl.error.assert(account, 'account.notFound', {}, 404, locale);
|
|
181
|
+
|
|
182
|
+
dsl.error.assert(
|
|
183
|
+
account.balance >= 100,
|
|
184
|
+
'account.insufficientBalance',
|
|
185
|
+
{ balance: account.balance, required: 100 },
|
|
186
|
+
400,
|
|
187
|
+
locale
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// 验证通过,继续处理...
|
|
191
|
+
res.json({ success: true });
|
|
192
|
+
} catch (error) {
|
|
193
|
+
// 错误消息已经是请求的语言
|
|
194
|
+
res.status(error.statusCode).json(error.toJSON());
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**方式2:同一请求中使用多种语言**
|
|
200
|
+
```javascript
|
|
201
|
+
// 创建不同语言的错误消息
|
|
202
|
+
const error1 = dsl.error.create('account.notFound', {}, 404, 'zh-CN');
|
|
203
|
+
console.log(error1.message); // "账户不存在"
|
|
204
|
+
console.log(error1.locale); // "zh-CN"
|
|
205
|
+
|
|
206
|
+
const error2 = dsl.error.create('account.notFound', {}, 404, 'en-US');
|
|
207
|
+
console.log(error2.message); // "Account not found"
|
|
208
|
+
console.log(error2.locale); // "en-US"
|
|
209
|
+
|
|
210
|
+
const error3 = dsl.error.create('account.notFound', {}, 404, 'ja-JP');
|
|
211
|
+
console.log(error3.message); // "アカウントが見つかりません"
|
|
212
|
+
console.log(error3.locale); // "ja-JP"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**方式3:微服务架构 - 错误传递保持原语言**
|
|
216
|
+
```javascript
|
|
217
|
+
// 服务A抛出错误(带语言信息)
|
|
218
|
+
class ServiceA {
|
|
219
|
+
async getAccount(id, locale) {
|
|
220
|
+
const account = await db.findAccount(id);
|
|
221
|
+
dsl.error.assert(account, 'account.notFound', {}, 404, locale);
|
|
222
|
+
return account;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 服务B接收错误(保持原语言)
|
|
227
|
+
class ServiceB {
|
|
228
|
+
async processAccount(accountId, locale) {
|
|
229
|
+
try {
|
|
230
|
+
const account = await serviceA.getAccount(accountId, locale);
|
|
231
|
+
// 处理账户...
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (error instanceof I18nError) {
|
|
234
|
+
// 错误消息保持原语言
|
|
235
|
+
console.log(error.locale); // locale 参数传入的语言
|
|
236
|
+
console.log(error.message); // 该语言的错误消息
|
|
237
|
+
|
|
238
|
+
// 转发给客户端
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### 适用场景
|
|
247
|
+
|
|
248
|
+
- ✅ **多语言 API**: 根据请求头 `Accept-Language` 动态返回
|
|
249
|
+
- ✅ **微服务架构**: 错误在服务间传递时保持原语言
|
|
250
|
+
- ✅ **国际化应用**: 同一请求中需要多种语言的错误消息
|
|
251
|
+
- ✅ **A/B测试**: 不同用户组使用不同语言
|
|
252
|
+
|
|
253
|
+
#### 与全局语言设置的对比
|
|
254
|
+
|
|
255
|
+
| 特性 | 全局设置 | 运行时指定 |
|
|
256
|
+
|------|---------|-----------|
|
|
257
|
+
| **使用场景** | 单一语言应用 | 多语言API |
|
|
258
|
+
| **切换方式** | `Locale.setLocale()` | 每次调用指定 |
|
|
259
|
+
| **影响范围** | 全局所有错误 | 仅当前错误 |
|
|
260
|
+
| **线程安全** | ⚠️ 需注意并发 | ✅ 完全安全 |
|
|
261
|
+
| **推荐度** | 简单应用 | **API开发** |
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### 3. 插件系统增强
|
|
266
|
+
|
|
267
|
+
**简化自定义类型注册流程**
|
|
268
|
+
|
|
269
|
+
#### 改进前(v1.0.x)
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
// 需要手动处理注册逻辑
|
|
273
|
+
const customValidator = {
|
|
274
|
+
name: 'slug',
|
|
275
|
+
validate: (value) => /^[a-z0-9-]+$/.test(value)
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// 手动添加到验证器
|
|
279
|
+
validator.addCustomType('slug', customValidator);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### 改进后(v1.1.0+)
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
// 使用插件系统统一管理
|
|
286
|
+
const slugPlugin = {
|
|
287
|
+
name: 'slug-validator',
|
|
288
|
+
version: '1.0.0',
|
|
289
|
+
install(core) {
|
|
290
|
+
core.registerType('slug', {
|
|
291
|
+
validate: (value) => /^[a-z0-9-]+$/.test(value),
|
|
292
|
+
message: 'must be a valid slug (lowercase, numbers, hyphens)'
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// 注册插件
|
|
298
|
+
pluginManager.register(slugPlugin);
|
|
299
|
+
pluginManager.install(schemaCore);
|
|
300
|
+
|
|
301
|
+
// 使用自定义类型
|
|
302
|
+
const schema = dsl({ url: 'slug!' });
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### 新增功能
|
|
306
|
+
|
|
307
|
+
- ✅ 插件生命周期管理(install/uninstall)
|
|
308
|
+
- ✅ 插件版本控制
|
|
309
|
+
- ✅ 插件依赖检查
|
|
310
|
+
- ✅ 插件热重载(开发环境)
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 📚 文档更新
|
|
315
|
+
|
|
316
|
+
1. ✅ **联合类型文档**: [docs/union-types.md](../docs/union-types.md)
|
|
317
|
+
2. ✅ **运行时多语言文档**: [docs/runtime-locale-support.md](../docs/runtime-locale-support.md)
|
|
318
|
+
3. ✅ **插件系统文档**: [docs/plugin-system.md](../docs/plugin-system.md)
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 🔄 破坏性变更
|
|
323
|
+
|
|
324
|
+
**无破坏性变更** - 所有新功能都是向后兼容的。
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## 🎯 升级指南
|
|
329
|
+
|
|
330
|
+
### 从 v1.0.x 升级到 v1.1.0
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
npm install schema-dsl@1.1.0
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**可选:启用新功能**
|
|
337
|
+
|
|
338
|
+
1. **使用联合类型**(可选)
|
|
339
|
+
```javascript
|
|
340
|
+
// 之前
|
|
341
|
+
const schema = dsl({ value: 'string' });
|
|
342
|
+
|
|
343
|
+
// 现在(可选使用)
|
|
344
|
+
const schema = dsl({ value: 'types:string|number' });
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
2. **使用运行时多语言**(可选)
|
|
348
|
+
```javascript
|
|
349
|
+
// 之前
|
|
350
|
+
I18nError.throw('account.notFound');
|
|
351
|
+
|
|
352
|
+
// 现在(可选使用)
|
|
353
|
+
I18nError.throw('account.notFound', {}, 404, 'en-US');
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 📦 依赖变更
|
|
359
|
+
|
|
360
|
+
**无依赖变更**
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## 📊 性能影响
|
|
365
|
+
|
|
366
|
+
- ✅ 联合类型验证:性能损失 <5%(可接受)
|
|
367
|
+
- ✅ 运行时多语言:无性能影响(仅参数传递)
|
|
368
|
+
- ✅ 插件系统:初始化性能优化 10%
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 🙏 致谢
|
|
373
|
+
|
|
374
|
+
感谢社区贡献者提出联合类型验证的需求和反馈。
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 🔗 相关链接
|
|
379
|
+
|
|
380
|
+
- [GitHub Repository](https://github.com/vextjs/schema-dsl)
|
|
381
|
+
- [npm Package](https://www.npmjs.com/package/schema-dsl)
|
|
382
|
+
- [完整文档](../docs/INDEX.md)
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
**发布者**: schema-dsl Team
|
|
387
|
+
**发布时间**: 2026-01-05
|
|
388
|
+
**下一版本**: v1.1.1
|
|
389
|
+
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# v1.1.1 变更日志
|
|
2
|
+
|
|
3
|
+
> **发布日期**: 2026-01-06
|
|
4
|
+
> **版本号**: v1.1.1
|
|
5
|
+
> **类型**: 🎉 新功能
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📋 变更概览
|
|
10
|
+
|
|
11
|
+
| 类型 | 数量 | 说明 |
|
|
12
|
+
|------|------|------|
|
|
13
|
+
| 🎉 新功能 | 1 | ConditionalBuilder 独立消息支持 |
|
|
14
|
+
| ⚡ 功能增强 | 1 | I18nError 多语言错误抛出 |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🎉 新功能
|
|
19
|
+
|
|
20
|
+
### ConditionalBuilder 独立消息支持
|
|
21
|
+
|
|
22
|
+
**让条件验证的每个分支都有独立的错误消息**
|
|
23
|
+
|
|
24
|
+
#### 问题场景
|
|
25
|
+
|
|
26
|
+
在 v1.1.0 之前,条件验证只能有一个统一的错误消息:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
// ❌ v1.1.0:所有条件失败都显示同一消息
|
|
30
|
+
dsl.if(d => !d)
|
|
31
|
+
.and(d => d.status !== 'active')
|
|
32
|
+
.and(d => d.balance < 100)
|
|
33
|
+
.message('验证失败') // 无法区分具体哪个条件失败
|
|
34
|
+
.assert(account);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### 解决方案
|
|
38
|
+
|
|
39
|
+
v1.1.1 支持为每个条件分支设置独立的消息:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// ✅ v1.1.1:每个条件都有清晰的错误消息
|
|
43
|
+
dsl.if(d => !d)
|
|
44
|
+
.message('ACCOUNT_NOT_FOUND') // 第一个条件的消息
|
|
45
|
+
.and(d => d.status !== 'active')
|
|
46
|
+
.message('ACCOUNT_INACTIVE') // 第二个条件的消息
|
|
47
|
+
.and(d => d.balance < 100)
|
|
48
|
+
.message('INSUFFICIENT_BALANCE') // 第三个条件的消息
|
|
49
|
+
.assert(account);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### 实际应用场景
|
|
53
|
+
|
|
54
|
+
**场景1:账户验证 - 多重检查**
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
const { dsl } = require('schema-dsl');
|
|
58
|
+
|
|
59
|
+
// 检查账户的多个条件
|
|
60
|
+
function validateAccount(account, amount) {
|
|
61
|
+
dsl.if(d => !d)
|
|
62
|
+
.message('ACCOUNT_NOT_FOUND')
|
|
63
|
+
.and(d => d.status !== 'active')
|
|
64
|
+
.message('ACCOUNT_INACTIVE')
|
|
65
|
+
.and(d => d.balance < amount)
|
|
66
|
+
.message('INSUFFICIENT_BALANCE')
|
|
67
|
+
.assert(account);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 使用示例
|
|
71
|
+
try {
|
|
72
|
+
validateAccount(null, 100);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.log(error.message); // "账户不存在" (ACCOUNT_NOT_FOUND)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
validateAccount({ status: 'suspended', balance: 200 }, 100);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.log(error.message); // "账户未激活" (ACCOUNT_INACTIVE)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
validateAccount({ status: 'active', balance: 50 }, 100);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.log(error.message); // "余额不足" (INSUFFICIENT_BALANCE)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**场景2:权限检查 - 分层验证**
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
function checkPermission(user, resource) {
|
|
94
|
+
dsl.if(d => !d)
|
|
95
|
+
.message('USER_NOT_FOUND')
|
|
96
|
+
.and(d => d.role !== 'admin' && d.role !== 'moderator')
|
|
97
|
+
.message('INSUFFICIENT_ROLE')
|
|
98
|
+
.and(d => !d.permissions.includes(resource))
|
|
99
|
+
.message('PERMISSION_DENIED')
|
|
100
|
+
.assert(user);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**场景3:订单验证 - 业务规则**
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
function validateOrder(order) {
|
|
108
|
+
dsl.if(d => !d)
|
|
109
|
+
.message('ORDER_NOT_FOUND')
|
|
110
|
+
.and(d => d.status === 'cancelled')
|
|
111
|
+
.message('ORDER_CANCELLED')
|
|
112
|
+
.and(d => d.status === 'completed')
|
|
113
|
+
.message('ORDER_ALREADY_COMPLETED')
|
|
114
|
+
.and(d => d.payment === null)
|
|
115
|
+
.message('PAYMENT_MISSING')
|
|
116
|
+
.and(d => !d.payment.isPaid)
|
|
117
|
+
.message('ORDER_NOT_PAID')
|
|
118
|
+
.assert(order);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### API 增强
|
|
123
|
+
|
|
124
|
+
**ConditionalBuilder.message()**
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
message(
|
|
128
|
+
code: string, // 错误代码(支持多语言)
|
|
129
|
+
params?: object // 参数插值(可选)
|
|
130
|
+
): ConditionalBuilder
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**链式调用示例**
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
dsl.if(condition1)
|
|
137
|
+
.message('ERROR_1')
|
|
138
|
+
.and(condition2)
|
|
139
|
+
.message('ERROR_2', { param: 'value' })
|
|
140
|
+
.and(condition3)
|
|
141
|
+
.message('ERROR_3')
|
|
142
|
+
.assert(data);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## ⚡ 功能增强
|
|
148
|
+
|
|
149
|
+
### I18nError 统一多语言错误抛出
|
|
150
|
+
|
|
151
|
+
**业务代码中统一的多语言错误处理**
|
|
152
|
+
|
|
153
|
+
#### 核心 API
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
const { I18nError, dsl } = require('schema-dsl');
|
|
157
|
+
|
|
158
|
+
// 方式1:直接抛出
|
|
159
|
+
I18nError.throw('account.notFound');
|
|
160
|
+
|
|
161
|
+
// 方式2:带参数插值
|
|
162
|
+
I18nError.throw('account.insufficientBalance', {
|
|
163
|
+
balance: 50,
|
|
164
|
+
required: 100
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 方式3:断言风格(推荐)
|
|
168
|
+
I18nError.assert(account, 'account.notFound');
|
|
169
|
+
I18nError.assert(
|
|
170
|
+
account.balance >= 100,
|
|
171
|
+
'account.insufficientBalance',
|
|
172
|
+
{ balance: account.balance, required: 100 }
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// 方式4:快捷方法
|
|
176
|
+
dsl.error.throw('user.noPermission');
|
|
177
|
+
dsl.error.assert(user.role === 'admin', 'user.noPermission');
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Express/Koa 集成
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
// 错误处理中间件
|
|
184
|
+
app.use((error, req, res, next) => {
|
|
185
|
+
if (error instanceof I18nError) {
|
|
186
|
+
return res.status(error.statusCode).json(error.toJSON());
|
|
187
|
+
}
|
|
188
|
+
next(error);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// 业务代码中使用
|
|
192
|
+
app.post('/withdraw', (req, res) => {
|
|
193
|
+
const account = getAccount(req.user.id);
|
|
194
|
+
I18nError.assert(account, 'account.notFound');
|
|
195
|
+
I18nError.assert(
|
|
196
|
+
account.balance >= req.body.amount,
|
|
197
|
+
'account.insufficientBalance',
|
|
198
|
+
{ balance: account.balance, required: req.body.amount }
|
|
199
|
+
);
|
|
200
|
+
// 验证通过,继续处理...
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### 内置错误代码
|
|
205
|
+
|
|
206
|
+
**通用错误**:
|
|
207
|
+
- `error.notFound` - 资源不存在
|
|
208
|
+
- `error.forbidden` - 禁止访问
|
|
209
|
+
- `error.unauthorized` - 未授权
|
|
210
|
+
|
|
211
|
+
**账户错误**:
|
|
212
|
+
- `account.notFound` - 账户不存在
|
|
213
|
+
- `account.insufficientBalance` - 余额不足
|
|
214
|
+
- `account.inactive` - 账户未激活
|
|
215
|
+
|
|
216
|
+
**用户错误**:
|
|
217
|
+
- `user.notFound` - 用户不存在
|
|
218
|
+
- `user.noPermission` - 无权限
|
|
219
|
+
|
|
220
|
+
**订单错误**:
|
|
221
|
+
- `order.notPaid` - 订单未支付
|
|
222
|
+
- `order.paymentMissing` - 缺少支付信息
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 📚 文档更新
|
|
227
|
+
|
|
228
|
+
- ✅ 新增条件验证独立消息文档
|
|
229
|
+
- ✅ 新增 I18nError 使用指南
|
|
230
|
+
- ✅ 更新 Express/Koa 集成示例
|
|
231
|
+
- ✅ 添加内置错误代码列表
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 🔄 破坏性变更
|
|
236
|
+
|
|
237
|
+
**无破坏性变更** - 所有新功能都是向后兼容的。
|
|
238
|
+
|
|
239
|
+
旧的用法仍然有效:
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
// ✅ 旧用法仍然有效
|
|
243
|
+
dsl.if(condition)
|
|
244
|
+
.message('ERROR') // 统一消息
|
|
245
|
+
.and(condition2)
|
|
246
|
+
.assert(data);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 🎯 升级指南
|
|
252
|
+
|
|
253
|
+
### 从 v1.1.0 升级到 v1.1.1
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm install schema-dsl@1.1.1
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**可选:使用独立消息**
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
// 之前(v1.1.0):统一消息
|
|
263
|
+
dsl.if(d => !d)
|
|
264
|
+
.and(d => d.status !== 'active')
|
|
265
|
+
.message('验证失败')
|
|
266
|
+
.assert(account);
|
|
267
|
+
|
|
268
|
+
// 现在(v1.1.1):独立消息(推荐)
|
|
269
|
+
dsl.if(d => !d)
|
|
270
|
+
.message('ACCOUNT_NOT_FOUND')
|
|
271
|
+
.and(d => d.status !== 'active')
|
|
272
|
+
.message('ACCOUNT_INACTIVE')
|
|
273
|
+
.assert(account);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**可选:使用 I18nError**
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
// 之前:手动抛出错误
|
|
280
|
+
if (!account) {
|
|
281
|
+
throw new Error('账户不存在');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 现在:使用 I18nError(支持多语言)
|
|
285
|
+
I18nError.assert(account, 'account.notFound');
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 📦 依赖变更
|
|
291
|
+
|
|
292
|
+
**无依赖变更**
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 🔗 相关链接
|
|
297
|
+
|
|
298
|
+
- [GitHub Repository](https://github.com/vextjs/schema-dsl)
|
|
299
|
+
- [npm Package](https://www.npmjs.com/package/schema-dsl)
|
|
300
|
+
- [条件验证文档](../docs/conditional-api.md)
|
|
301
|
+
- [I18nError 示例](../examples/i18n-error.examples.js)
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
**发布者**: schema-dsl Team
|
|
306
|
+
**发布时间**: 2026-01-06
|
|
307
|
+
**下一版本**: v1.1.2
|
|
308
|
+
|