schema-dsl 2.0.0 → 2.0.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 +130 -113
- package/LICENSE +21 -21
- package/README.md +628 -628
- package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
- package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
- package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
- package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
- package/dist/index.cjs +75 -29
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +75 -29
- package/dist/plugins/custom-format.cjs +33 -17
- package/dist/plugins/custom-format.d.cts +1 -1
- package/dist/plugins/custom-format.d.ts +1 -1
- package/dist/plugins/custom-format.js +33 -17
- package/dist/plugins/custom-type-example.cjs +33 -17
- package/dist/plugins/custom-type-example.d.cts +1 -1
- package/dist/plugins/custom-type-example.d.ts +1 -1
- package/dist/plugins/custom-type-example.js +33 -17
- package/dist/plugins/custom-validator.cjs +0 -2
- package/dist/plugins/custom-validator.d.cts +1 -1
- package/dist/plugins/custom-validator.d.ts +1 -1
- package/dist/plugins/custom-validator.js +0 -2
- package/docs/FEATURE-INDEX.md +553 -553
- package/docs/add-custom-locale.md +496 -496
- package/docs/add-keyword.md +24 -24
- package/docs/api-reference.md +1047 -1047
- package/docs/api.md +13 -13
- package/docs/best-practices-project-structure.md +417 -417
- package/docs/best-practices.md +712 -712
- package/docs/cache-manager.md +344 -344
- package/docs/compile.md +45 -45
- package/docs/conditional-api.md +1307 -1307
- package/docs/custom-extensions-guide.md +339 -339
- package/docs/design-philosophy.md +606 -606
- package/docs/doc-index.md +324 -324
- package/docs/dsl-syntax.md +714 -714
- package/docs/dynamic-locale.md +608 -608
- package/docs/enum.md +482 -482
- package/docs/error-handling.md +1975 -1975
- package/docs/export-guide.md +501 -501
- package/docs/export-limitations.md +567 -567
- package/docs/faq.md +596 -596
- package/docs/frontend-i18n-guide.md +307 -307
- package/docs/i18n-user-guide.md +487 -487
- package/docs/i18n.md +476 -476
- package/docs/index.md +48 -48
- package/docs/json-schema-basics.md +40 -40
- package/docs/label-vs-description.md +271 -271
- package/docs/markdown-exporter.md +406 -406
- package/docs/mongodb-exporter.md +302 -302
- package/docs/multi-language.md +26 -26
- package/docs/multi-type-support.md +322 -322
- package/docs/mysql-exporter.md +280 -280
- package/docs/number-operators.md +449 -449
- package/docs/optional-marker-guide.md +326 -326
- package/docs/performance-guide.md +49 -49
- package/docs/plugin-system.md +381 -381
- package/docs/plugin-type-registration.md +34 -34
- package/docs/postgresql-exporter.md +311 -311
- package/docs/public/favicon.svg +4 -4
- package/docs/quick-start.md +435 -435
- package/docs/runtime-locale-support.md +532 -532
- package/docs/schema-helper.md +345 -345
- package/docs/schema-utils-advanced-issues.md +23 -23
- package/docs/schema-utils-best-practices.md +20 -20
- package/docs/schema-utils-chaining.md +150 -150
- package/docs/schema-utils.md +524 -524
- package/docs/security-checklist.md +20 -20
- package/docs/string-extensions.md +488 -488
- package/docs/troubleshooting.md +486 -486
- package/docs/type-converter.md +310 -310
- package/docs/type-reference.md +242 -242
- package/docs/typescript-guide.md +584 -584
- package/docs/union-type-guide.md +157 -157
- package/docs/union-types.md +284 -284
- package/docs/validate-async.md +491 -491
- package/docs/validate-batch.md +49 -49
- package/docs/validate-dsl-object-support.md +578 -578
- package/docs/validate.md +506 -506
- package/docs/validation-guide.md +502 -502
- package/docs/validator.md +39 -39
- package/package.json +131 -131
- package/plugins/custom-format.cjs +8 -8
- package/plugins/custom-type-example.cjs +8 -8
- package/plugins/custom-validator.cjs +8 -8
- package/src/adapters/DslAdapter.ts +111 -111
- package/src/adapters/index.ts +1 -1
- package/src/config/constants.ts +83 -83
- package/src/config/index.ts +2 -2
- package/src/config/patterns.ts +77 -77
- package/src/core/CacheManager.ts +169 -159
- package/src/core/ConditionalBuilder.ts +382 -382
- package/src/core/ConditionalRuntime.ts +27 -27
- package/src/core/ConditionalValidator.ts +254 -254
- package/src/core/DslBuilder.ts +687 -677
- package/src/core/ErrorCodes.ts +38 -38
- package/src/core/ErrorFormatter.ts +271 -271
- package/src/core/JSONSchemaCore.ts +65 -65
- package/src/core/Locale.ts +187 -187
- package/src/core/MessageTemplate.ts +42 -42
- package/src/core/ObjectDslBuilder.ts +64 -64
- package/src/core/PluginManager.ts +326 -326
- package/src/core/StringExtensions.ts +140 -140
- package/src/core/TemplateEngine.ts +44 -44
- package/src/core/Validator.ts +448 -448
- package/src/errors/I18nError.ts +159 -159
- package/src/errors/ValidationError.ts +105 -105
- package/src/exporters/BaseExporter.ts +60 -60
- package/src/exporters/MarkdownExporter.ts +305 -305
- package/src/exporters/MongoDBExporter.ts +126 -126
- package/src/exporters/MySQLExporter.ts +156 -155
- package/src/exporters/PostgreSQLExporter.ts +222 -222
- package/src/exporters/index.ts +18 -18
- package/src/index.ts +651 -633
- package/src/locales/en-US.ts +160 -160
- package/src/locales/es-ES.ts +160 -160
- package/src/locales/fr-FR.ts +160 -160
- package/src/locales/index.ts +103 -103
- package/src/locales/ja-JP.ts +160 -160
- package/src/locales/types.ts +156 -156
- package/src/locales/zh-CN.ts +160 -160
- package/src/parser/ConstraintParser.ts +101 -101
- package/src/parser/DslParser.ts +470 -470
- package/src/parser/SchemaCompiler.ts +66 -66
- package/src/parser/TypeRegistry.ts +250 -250
- package/src/parser/index.ts +6 -6
- package/src/plugins/custom-format.ts +124 -126
- package/src/plugins/custom-type-example.ts +106 -108
- package/src/plugins/custom-validator.ts +138 -140
- package/src/types/conditional.ts +28 -28
- package/src/types/config.ts +59 -59
- package/src/types/dsl.ts +131 -131
- package/src/types/error.ts +60 -60
- package/src/types/index.ts +17 -17
- package/src/types/infer.ts +127 -127
- package/src/types/plugin.ts +58 -58
- package/src/types/safe-regex.d.ts +9 -9
- package/src/types/schema.ts +66 -66
- package/src/types/validate.ts +71 -71
- package/src/utils/SchemaHelper.ts +196 -196
- package/src/utils/SchemaUtils.ts +365 -346
- package/src/utils/TypeConverter.ts +215 -215
- package/src/utils/index.ts +10 -10
- package/src/validators/CustomKeywords.ts +477 -477
package/docs/typescript-guide.md
CHANGED
|
@@ -1,584 +1,584 @@
|
|
|
1
|
-
# TypeScript 使用指南
|
|
2
|
-
|
|
3
|
-
> **版本**: schema-dsl v2.0.0-beta.2
|
|
4
|
-
> **更新日期**: 2026-05-08
|
|
5
|
-
> **重要**: v1.0.6 移除了全局 String 类型扩展以避免类型污染
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 📋 目录
|
|
10
|
-
|
|
11
|
-
1. [快速开始](#1-快速开始)
|
|
12
|
-
2. [TypeScript 中的链式调用](#2-typescript-中的链式调用)
|
|
13
|
-
3. [类型推导最佳实践](#3-类型推导最佳实践)
|
|
14
|
-
4. [完整示例](#4-完整示例)
|
|
15
|
-
5. [常见问题](#5-常见问题)
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 1. 快速开始
|
|
20
|
-
|
|
21
|
-
### 1.1 安装
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npm install schema-dsl
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### 1.2 基础用法
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
import { dsl, validate } from 'schema-dsl';
|
|
31
|
-
|
|
32
|
-
// 定义 Schema
|
|
33
|
-
const userSchema = dsl({
|
|
34
|
-
username: 'string:3-32!',
|
|
35
|
-
email: 'email!',
|
|
36
|
-
age: 'number:18-100'
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// 验证数据
|
|
40
|
-
const result = validate(userSchema, {
|
|
41
|
-
username: 'testuser',
|
|
42
|
-
email: 'test@example.com',
|
|
43
|
-
age: 25
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (result.valid) {
|
|
47
|
-
console.log('验证通过:', result.data);
|
|
48
|
-
} else {
|
|
49
|
-
console.log('验证失败:', result.errors);
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## 2. TypeScript 中的链式调用
|
|
56
|
-
|
|
57
|
-
### 2.1 重要变更(v1.0.6)
|
|
58
|
-
|
|
59
|
-
**v1.0.6 移除了全局 `interface String` 扩展**,原因:
|
|
60
|
-
- ❌ 全局类型扩展会污染原生 String 类型
|
|
61
|
-
- ❌ 导致 `trim()`、`toLowerCase()` 等原生方法的类型推断错误
|
|
62
|
-
- ❌ 影响所有使用 TypeScript 的项目的类型安全
|
|
63
|
-
|
|
64
|
-
**结果**:在 TypeScript 中直接对字符串链式调用会报类型错误:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
// ❌ TypeScript 中会报错(v1.0.6+)
|
|
68
|
-
const schema = dsl({
|
|
69
|
-
email: 'email!'.label('邮箱') // 类型错误:Property 'label' does not exist on type 'string'
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// ✅ JavaScript 中仍然可以正常使用
|
|
73
|
-
const schema = dsl({
|
|
74
|
-
email: 'email!'.label('邮箱') // 运行时完全正常
|
|
75
|
-
});
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### 2.2 正确用法 ⭐⭐⭐
|
|
79
|
-
|
|
80
|
-
**TypeScript 中必须使用 `dsl()` 函数包裹字符串**,才能获得类型提示和链式调用:
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// ✅ 正确:使用 dsl() 包裹(v1.0.6+ 必须)
|
|
84
|
-
const schema = dsl({
|
|
85
|
-
email: dsl('email!').label('邮箱').pattern(/custom/)
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// ✅ 也可以先定义再使用
|
|
89
|
-
const emailField = dsl('email!').label('邮箱');
|
|
90
|
-
const schema = dsl({ email: emailField });
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**好处**:
|
|
94
|
-
- ✅ 获得完整的类型推导和 IDE 自动提示
|
|
95
|
-
- ✅ 不污染原生 String 类型(`trim()` 正确返回 `string`)
|
|
96
|
-
- ✅ 更好的类型安全和开发体验
|
|
97
|
-
|
|
98
|
-
### 2.3 工作原理
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
// dsl(string) 返回 DslBuilder 实例
|
|
102
|
-
const emailBuilder = dsl('email!');
|
|
103
|
-
// ^? DslBuilder - 完整的类型定义
|
|
104
|
-
|
|
105
|
-
// DslBuilder 支持所有链式方法,并有完整类型提示
|
|
106
|
-
emailBuilder.label('邮箱')
|
|
107
|
-
// ^? IDE 自动提示所有可用方法
|
|
108
|
-
.pattern(/^[a-z]+@[a-z]+\.[a-z]+$/)
|
|
109
|
-
.error({ required: '邮箱必填' });
|
|
110
|
-
|
|
111
|
-
> ℹ️ 当前类型声明优先覆盖稳定链式 API,例如 `label()`、`pattern()`、`error()`、`default()`。
|
|
112
|
-
> 某些运行时扩展方法依然可用,但如果类型声明未暴露,建议在 TypeScript 代码里优先改写为上述稳定组合。
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## 3. 类型推导最佳实践
|
|
118
|
-
|
|
119
|
-
### 3.1 方式对比
|
|
120
|
-
|
|
121
|
-
| 方式 | JavaScript | TypeScript | 类型推导 | 推荐度 |
|
|
122
|
-
|------|-----------|-----------|---------|--------|
|
|
123
|
-
| 直接字符串 | ✅ 完美 | ⚠️ 可能无提示 | ❌ 弱 | ⭐⭐ |
|
|
124
|
-
| dsl() 包裹 | ✅ 完美 | ✅ 完美 | ✅ 强 | ⭐⭐⭐⭐⭐ |
|
|
125
|
-
| 先定义再使用 | ✅ 完美 | ✅ 完美 | ✅ 强 | ⭐⭐⭐⭐ |
|
|
126
|
-
|
|
127
|
-
### 3.2 推荐写法
|
|
128
|
-
|
|
129
|
-
#### ✅ 方式 1: 内联使用 dsl() 包裹(最推荐)
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
const schema = dsl({
|
|
133
|
-
username: dsl('string:3-32!')
|
|
134
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
135
|
-
.label('用户名'),
|
|
136
|
-
.error({ pattern: '只能包含字母、数字和下划线' }),
|
|
137
|
-
|
|
138
|
-
email: dsl('email!')
|
|
139
|
-
.label('邮箱地址')
|
|
140
|
-
.error({ required: '邮箱必填' }),
|
|
141
|
-
|
|
142
|
-
age: dsl('number:18-100')
|
|
143
|
-
.label('年龄')
|
|
144
|
-
});
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
**优点**:
|
|
148
|
-
- ✅ 完整的类型推导
|
|
149
|
-
- ✅ IDE 自动提示所有方法
|
|
150
|
-
- ✅ 代码紧凑,逻辑清晰
|
|
151
|
-
|
|
152
|
-
#### ✅ 方式 2: 先定义字段,再组合(适合复用)
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
// 定义可复用的字段
|
|
156
|
-
const emailField = dsl('email!')
|
|
157
|
-
.label('邮箱地址')
|
|
158
|
-
.error({ required: '邮箱必填' });
|
|
159
|
-
|
|
160
|
-
const usernameField = dsl('string:3-32!')
|
|
161
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
162
|
-
.label('用户名')
|
|
163
|
-
.error({ pattern: '用户名只能包含字母、数字和下划线' });
|
|
164
|
-
|
|
165
|
-
// 组合使用
|
|
166
|
-
const registrationSchema = dsl({
|
|
167
|
-
email: emailField,
|
|
168
|
-
username: usernameField,
|
|
169
|
-
password: dsl('string:8-64!')
|
|
170
|
-
.pattern(/^(?=.*[A-Za-z])(?=.*\d).{8,}$/)
|
|
171
|
-
.label('密码')
|
|
172
|
-
.error({ pattern: '密码至少 8 位且必须包含字母和数字' })
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const loginSchema = dsl({
|
|
176
|
-
email: emailField, // 复用
|
|
177
|
-
password: dsl('string!').label('密码')
|
|
178
|
-
});
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**优点**:
|
|
182
|
-
- ✅ 字段定义可复用
|
|
183
|
-
- ✅ 代码更模块化
|
|
184
|
-
- ✅ 适合大型项目
|
|
185
|
-
|
|
186
|
-
#### ❌ 不推荐的写法
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
// ❌ 在 TypeScript 中直接使用字符串链式调用
|
|
190
|
-
const schema = dsl({
|
|
191
|
-
email: 'email!'.label('邮箱') // 可能无类型提示
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// ❌ 混合使用(不一致)
|
|
195
|
-
const schema = dsl({
|
|
196
|
-
email: 'email!'.label('邮箱'), // 字符串扩展
|
|
197
|
-
username: dsl('string!').label('用户名') // dsl 包裹
|
|
198
|
-
});
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
## 4. 完整示例
|
|
204
|
-
|
|
205
|
-
### 4.1 用户注册表单
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
import { dsl, validateAsync, ValidationError } from 'schema-dsl';
|
|
209
|
-
|
|
210
|
-
// 定义 Schema
|
|
211
|
-
const registrationSchema = dsl({
|
|
212
|
-
profile: dsl({
|
|
213
|
-
username: dsl('string:3-32!')
|
|
214
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
215
|
-
.label('用户名')
|
|
216
|
-
.error({ pattern: '只能包含字母、数字和下划线' }),
|
|
217
|
-
|
|
218
|
-
email: dsl('email!')
|
|
219
|
-
.label('邮箱地址')
|
|
220
|
-
.error({ required: '邮箱必填' }),
|
|
221
|
-
|
|
222
|
-
password: dsl('string:8-64!')
|
|
223
|
-
.pattern(/^(?=.*[A-Za-z])(?=.*\d).{8,}$/)
|
|
224
|
-
.label('密码')
|
|
225
|
-
.error({ pattern: '密码至少 8 位且必须包含字母和数字' }),
|
|
226
|
-
|
|
227
|
-
age: dsl('number:18-100')
|
|
228
|
-
.label('年龄')
|
|
229
|
-
}),
|
|
230
|
-
|
|
231
|
-
settings: dsl({
|
|
232
|
-
emailNotify: dsl('boolean')
|
|
233
|
-
.default(true)
|
|
234
|
-
.label('邮件通知'),
|
|
235
|
-
|
|
236
|
-
language: dsl('string')
|
|
237
|
-
.default('zh-CN')
|
|
238
|
-
.label('语言设置')
|
|
239
|
-
})
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// 异步验证(推荐)
|
|
243
|
-
async function registerUser(data: any) {
|
|
244
|
-
try {
|
|
245
|
-
const validData = await validateAsync(registrationSchema, data);
|
|
246
|
-
console.log('注册成功:', validData);
|
|
247
|
-
return validData;
|
|
248
|
-
} catch (error) {
|
|
249
|
-
if (error instanceof ValidationError) {
|
|
250
|
-
console.log('验证失败:');
|
|
251
|
-
error.errors.forEach(err => {
|
|
252
|
-
console.log(` - ${err.path}: ${err.message}`);
|
|
253
|
-
});
|
|
254
|
-
throw error;
|
|
255
|
-
}
|
|
256
|
-
throw error;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// 使用
|
|
261
|
-
registerUser({
|
|
262
|
-
profile: {
|
|
263
|
-
username: 'testuser',
|
|
264
|
-
email: 'test@example.com',
|
|
265
|
-
password: 'StrongPass123!',
|
|
266
|
-
age: 25
|
|
267
|
-
},
|
|
268
|
-
settings: {
|
|
269
|
-
emailNotify: true,
|
|
270
|
-
language: 'en-US'
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### 4.2 API 请求验证
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
import { ValidationError, dsl, validateAsync } from 'schema-dsl';
|
|
279
|
-
import express from 'express';
|
|
280
|
-
|
|
281
|
-
const app = express();
|
|
282
|
-
app.use(express.json());
|
|
283
|
-
|
|
284
|
-
// 定义 API Schema
|
|
285
|
-
const createUserSchema = dsl({
|
|
286
|
-
username: dsl('string:3-32!')
|
|
287
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
288
|
-
.label('用户名'),
|
|
289
|
-
|
|
290
|
-
email: dsl('email!').label('邮箱'),
|
|
291
|
-
|
|
292
|
-
role: dsl('string')
|
|
293
|
-
.default('user')
|
|
294
|
-
.label('角色')
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// 使用中间件
|
|
298
|
-
app.post('/api/users', async (req, res) => {
|
|
299
|
-
try {
|
|
300
|
-
const validData = await validateAsync(createUserSchema, req.body);
|
|
301
|
-
|
|
302
|
-
// 创建用户逻辑
|
|
303
|
-
const user = await createUser(validData);
|
|
304
|
-
|
|
305
|
-
res.json({ success: true, data: user });
|
|
306
|
-
} catch (error) {
|
|
307
|
-
if (error instanceof ValidationError) {
|
|
308
|
-
res.status(400).json({
|
|
309
|
-
success: false,
|
|
310
|
-
errors: error.errors.map(e => ({
|
|
311
|
-
field: e.path,
|
|
312
|
-
message: e.message
|
|
313
|
-
}))
|
|
314
|
-
});
|
|
315
|
-
} else {
|
|
316
|
-
res.status(500).json({ success: false, message: '服务器错误' });
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### 4.3 表单字段复用
|
|
323
|
-
|
|
324
|
-
```typescript
|
|
325
|
-
import { dsl } from 'schema-dsl';
|
|
326
|
-
|
|
327
|
-
// 定义常用字段
|
|
328
|
-
const commonFields = {
|
|
329
|
-
email: dsl('email!')
|
|
330
|
-
.label('邮箱地址')
|
|
331
|
-
.error({ required: '邮箱必填' }),
|
|
332
|
-
|
|
333
|
-
username: dsl('string:3-32!')
|
|
334
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
335
|
-
.label('用户名')
|
|
336
|
-
.error({ pattern: '用户名只能包含字母、数字和下划线' }),
|
|
337
|
-
|
|
338
|
-
password: dsl('string:8-64!')
|
|
339
|
-
.pattern(/^(?=.*[A-Za-z])(?=.*\d).{8,}$/)
|
|
340
|
-
.label('密码')
|
|
341
|
-
.error({ pattern: '密码至少 8 位且必须包含字母和数字' })
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
// 注册表单
|
|
345
|
-
const registrationSchema = dsl({
|
|
346
|
-
...commonFields,
|
|
347
|
-
confirmPassword: dsl('string!')
|
|
348
|
-
.label('确认密码')
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
// 登录表单
|
|
352
|
-
const loginSchema = dsl({
|
|
353
|
-
email: commonFields.email,
|
|
354
|
-
password: dsl('string!').label('密码') // 登录时不需要强密码验证
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// 密码重置表单
|
|
358
|
-
const resetPasswordSchema = dsl({
|
|
359
|
-
email: commonFields.email,
|
|
360
|
-
newPassword: commonFields.password,
|
|
361
|
-
confirmPassword: dsl('string!').label('确认新密码')
|
|
362
|
-
});
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
## 5. 常见问题
|
|
368
|
-
|
|
369
|
-
### 5.1 为什么 TypeScript 中字符串链式调用没有类型提示?
|
|
370
|
-
|
|
371
|
-
**原因**: TypeScript 对全局 `String.prototype` 扩展的类型推导有限制。
|
|
372
|
-
|
|
373
|
-
**解决**: 使用 `dsl()` 包裹字符串:
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
// ❌ 可能无提示
|
|
377
|
-
'email!'.label('邮箱')
|
|
378
|
-
|
|
379
|
-
// ✅ 完整提示
|
|
380
|
-
dsl('email!').label('邮箱')
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### 5.2 JavaScript 用户需要改变写法吗?
|
|
384
|
-
|
|
385
|
-
**不需要!** JavaScript 用户可以继续使用字符串链式调用:
|
|
386
|
-
|
|
387
|
-
```javascript
|
|
388
|
-
// JavaScript 中完全正常
|
|
389
|
-
const schema = dsl({
|
|
390
|
-
email: 'email!'.label('邮箱')
|
|
391
|
-
});
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### 5.3 如何在严格模式下使用?
|
|
395
|
-
|
|
396
|
-
在 `tsconfig.json` 中启用严格模式也没问题:
|
|
397
|
-
|
|
398
|
-
```json
|
|
399
|
-
{
|
|
400
|
-
"compilerOptions": {
|
|
401
|
-
"strict": true,
|
|
402
|
-
"noImplicitAny": true
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
只需使用 `dsl()` 包裹即可:
|
|
408
|
-
|
|
409
|
-
```typescript
|
|
410
|
-
const schema = dsl({
|
|
411
|
-
email: dsl('email!').label('邮箱') // ✅ 严格模式下正常
|
|
412
|
-
});
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### 5.4 如何获取验证后的数据类型?
|
|
416
|
-
|
|
417
|
-
使用泛型参数:
|
|
418
|
-
|
|
419
|
-
```typescript
|
|
420
|
-
interface User {
|
|
421
|
-
username: string;
|
|
422
|
-
email: string;
|
|
423
|
-
age?: number;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// 同步验证
|
|
427
|
-
const result = validate<User>(userSchema, data);
|
|
428
|
-
if (result.valid) {
|
|
429
|
-
const user: User = result.data; // ✅ 类型安全
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// 异步验证
|
|
433
|
-
const validUser = await validateAsync<User>(userSchema, data);
|
|
434
|
-
// ^? User - 完整的类型推导
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### 5.5 如何处理嵌套对象的验证错误?
|
|
438
|
-
|
|
439
|
-
```typescript
|
|
440
|
-
try {
|
|
441
|
-
await validateAsync(schema, data);
|
|
442
|
-
} catch (error) {
|
|
443
|
-
if (error instanceof ValidationError) {
|
|
444
|
-
// 方式 1: 遍历所有错误
|
|
445
|
-
error.errors.forEach(err => {
|
|
446
|
-
console.log(`${err.path}: ${err.message}`);
|
|
447
|
-
// 输出: profile.username: 用户名至少3个字符
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
// 方式 2: 获取特定字段错误
|
|
451
|
-
const usernameError = error.getFieldError('profile.username');
|
|
452
|
-
if (usernameError) {
|
|
453
|
-
console.log(usernameError.message);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// 方式 3: 获取所有字段错误映射
|
|
457
|
-
const fieldErrors = error.getFieldErrors();
|
|
458
|
-
// { 'profile.username': {...}, 'profile.email': {...} }
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
---
|
|
464
|
-
|
|
465
|
-
## 6. 进阶技巧
|
|
466
|
-
|
|
467
|
-
### 6.1 额外业务规则
|
|
468
|
-
|
|
469
|
-
```typescript
|
|
470
|
-
const schema = dsl({
|
|
471
|
-
username: dsl('string:3-32!').label('用户名')
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
const result = await validateAsync(schema, data);
|
|
475
|
-
if (result.username === 'admin') {
|
|
476
|
-
throw new Error('用户名已存在');
|
|
477
|
-
}
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
这种写法的好处是:结构校验仍由 schema-dsl 负责,业务唯一性、数据库查重等规则继续留在 TypeScript 业务层,避免把外部依赖塞进字段声明。
|
|
481
|
-
|
|
482
|
-
### 6.2 条件验证
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
const schema = dsl({
|
|
486
|
-
userType: dsl('string!').label('用户类型'),
|
|
487
|
-
|
|
488
|
-
// 使用 dsl.match() 根据 userType 字段动态验证
|
|
489
|
-
companyName: dsl.match('userType', {
|
|
490
|
-
'company': 'string!', // 企业用户必填
|
|
491
|
-
'_default': 'string' // 个人用户可选
|
|
492
|
-
})
|
|
493
|
-
});
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
### 6.3 Schema 复用和扩展
|
|
497
|
-
|
|
498
|
-
```typescript
|
|
499
|
-
import { SchemaUtils } from 'schema-dsl';
|
|
500
|
-
|
|
501
|
-
// 基础用户 Schema
|
|
502
|
-
const baseUserSchema = dsl({
|
|
503
|
-
username: dsl('string:3-32!').label('用户名'),
|
|
504
|
-
email: dsl('email!').label('邮箱')
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// 扩展为管理员 Schema
|
|
508
|
-
const adminSchema = SchemaUtils.extend(baseUserSchema, {
|
|
509
|
-
role: dsl('string!').default('admin').label('角色'),
|
|
510
|
-
permissions: dsl('array<string>').label('权限列表')
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
// 只选择部分字段
|
|
514
|
-
const publicUserSchema = SchemaUtils.pick(
|
|
515
|
-
baseUserSchema,
|
|
516
|
-
['username']
|
|
517
|
-
);
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
---
|
|
521
|
-
|
|
522
|
-
## 7. 性能优化
|
|
523
|
-
|
|
524
|
-
### 7.1 复用 Schema 与默认缓存
|
|
525
|
-
|
|
526
|
-
```typescript
|
|
527
|
-
const schema = dsl({
|
|
528
|
-
email: dsl('email!').label('邮箱')
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
// 多次验证会复用默认 Validator 的编译缓存
|
|
532
|
-
await validateAsync(schema, data1);
|
|
533
|
-
await validateAsync(schema, data2);
|
|
534
|
-
await validateAsync(schema, data3);
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
### 7.2 缓存配置
|
|
538
|
-
|
|
539
|
-
```typescript
|
|
540
|
-
import { dsl } from 'schema-dsl';
|
|
541
|
-
|
|
542
|
-
// 配置缓存大小
|
|
543
|
-
dsl.config({
|
|
544
|
-
cache: {
|
|
545
|
-
maxSize: 5000, // 缓存条目数
|
|
546
|
-
ttl: 60000 // 过期时间(毫秒)
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
---
|
|
552
|
-
|
|
553
|
-
## 8. 最佳实践总结
|
|
554
|
-
|
|
555
|
-
1. ✅ **TypeScript 中始终使用 `dsl()` 包裹字符串**
|
|
556
|
-
2. ✅ **使用 `validateAsync` 进行异步验证**
|
|
557
|
-
3. ✅ **为验证结果添加泛型类型参数**
|
|
558
|
-
4. ✅ **复用常用字段定义**
|
|
559
|
-
5. ✅ **使用 `ValidationError` 类型守卫处理错误**
|
|
560
|
-
6. ✅ **为用户提供友好的错误消息**
|
|
561
|
-
7. ✅ **复用常用 Schema 对象,让默认缓存命中**
|
|
562
|
-
|
|
563
|
-
---
|
|
564
|
-
|
|
565
|
-
## 9. 相关资源
|
|
566
|
-
|
|
567
|
-
- [API 参考文档](./api-reference.md)
|
|
568
|
-
- [DSL 语法完整指南](./dsl-syntax.md)
|
|
569
|
-
- [验证规则参考](./validation-guide.md)
|
|
570
|
-
- [错误处理指南](./error-handling.md)
|
|
571
|
-
- [GitHub 仓库](https://github.com/vextjs/schema-dsl)
|
|
572
|
-
|
|
573
|
-
---
|
|
574
|
-
|
|
575
|
-
## 对应示例文件
|
|
576
|
-
|
|
577
|
-
**示例入口**: [typescript-guide.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/typescript-guide.ts)
|
|
578
|
-
**说明**: 展示 TypeScript 下推荐的 `dsl()` 包裹写法、`validate<T>()` / `validateAsync<T>()`、以及 `ValidationError` 的字段错误读取方式。
|
|
579
|
-
|
|
580
|
-
---
|
|
581
|
-
|
|
582
|
-
**更新日期**: 2026-05-08
|
|
583
|
-
**文档版本**: v2.0.0-beta.2
|
|
584
|
-
|
|
1
|
+
# TypeScript 使用指南
|
|
2
|
+
|
|
3
|
+
> **版本**: schema-dsl v2.0.0-beta.2
|
|
4
|
+
> **更新日期**: 2026-05-08
|
|
5
|
+
> **重要**: v1.0.6 移除了全局 String 类型扩展以避免类型污染
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📋 目录
|
|
10
|
+
|
|
11
|
+
1. [快速开始](#1-快速开始)
|
|
12
|
+
2. [TypeScript 中的链式调用](#2-typescript-中的链式调用)
|
|
13
|
+
3. [类型推导最佳实践](#3-类型推导最佳实践)
|
|
14
|
+
4. [完整示例](#4-完整示例)
|
|
15
|
+
5. [常见问题](#5-常见问题)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. 快速开始
|
|
20
|
+
|
|
21
|
+
### 1.1 安装
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install schema-dsl
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 1.2 基础用法
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { dsl, validate } from 'schema-dsl';
|
|
31
|
+
|
|
32
|
+
// 定义 Schema
|
|
33
|
+
const userSchema = dsl({
|
|
34
|
+
username: 'string:3-32!',
|
|
35
|
+
email: 'email!',
|
|
36
|
+
age: 'number:18-100'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 验证数据
|
|
40
|
+
const result = validate(userSchema, {
|
|
41
|
+
username: 'testuser',
|
|
42
|
+
email: 'test@example.com',
|
|
43
|
+
age: 25
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (result.valid) {
|
|
47
|
+
console.log('验证通过:', result.data);
|
|
48
|
+
} else {
|
|
49
|
+
console.log('验证失败:', result.errors);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 2. TypeScript 中的链式调用
|
|
56
|
+
|
|
57
|
+
### 2.1 重要变更(v1.0.6)
|
|
58
|
+
|
|
59
|
+
**v1.0.6 移除了全局 `interface String` 扩展**,原因:
|
|
60
|
+
- ❌ 全局类型扩展会污染原生 String 类型
|
|
61
|
+
- ❌ 导致 `trim()`、`toLowerCase()` 等原生方法的类型推断错误
|
|
62
|
+
- ❌ 影响所有使用 TypeScript 的项目的类型安全
|
|
63
|
+
|
|
64
|
+
**结果**:在 TypeScript 中直接对字符串链式调用会报类型错误:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// ❌ TypeScript 中会报错(v1.0.6+)
|
|
68
|
+
const schema = dsl({
|
|
69
|
+
email: 'email!'.label('邮箱') // 类型错误:Property 'label' does not exist on type 'string'
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ✅ JavaScript 中仍然可以正常使用
|
|
73
|
+
const schema = dsl({
|
|
74
|
+
email: 'email!'.label('邮箱') // 运行时完全正常
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2.2 正确用法 ⭐⭐⭐
|
|
79
|
+
|
|
80
|
+
**TypeScript 中必须使用 `dsl()` 函数包裹字符串**,才能获得类型提示和链式调用:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// ✅ 正确:使用 dsl() 包裹(v1.0.6+ 必须)
|
|
84
|
+
const schema = dsl({
|
|
85
|
+
email: dsl('email!').label('邮箱').pattern(/custom/)
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ✅ 也可以先定义再使用
|
|
89
|
+
const emailField = dsl('email!').label('邮箱');
|
|
90
|
+
const schema = dsl({ email: emailField });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**好处**:
|
|
94
|
+
- ✅ 获得完整的类型推导和 IDE 自动提示
|
|
95
|
+
- ✅ 不污染原生 String 类型(`trim()` 正确返回 `string`)
|
|
96
|
+
- ✅ 更好的类型安全和开发体验
|
|
97
|
+
|
|
98
|
+
### 2.3 工作原理
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// dsl(string) 返回 DslBuilder 实例
|
|
102
|
+
const emailBuilder = dsl('email!');
|
|
103
|
+
// ^? DslBuilder - 完整的类型定义
|
|
104
|
+
|
|
105
|
+
// DslBuilder 支持所有链式方法,并有完整类型提示
|
|
106
|
+
emailBuilder.label('邮箱')
|
|
107
|
+
// ^? IDE 自动提示所有可用方法
|
|
108
|
+
.pattern(/^[a-z]+@[a-z]+\.[a-z]+$/)
|
|
109
|
+
.error({ required: '邮箱必填' });
|
|
110
|
+
|
|
111
|
+
> ℹ️ 当前类型声明优先覆盖稳定链式 API,例如 `label()`、`pattern()`、`error()`、`default()`。
|
|
112
|
+
> 某些运行时扩展方法依然可用,但如果类型声明未暴露,建议在 TypeScript 代码里优先改写为上述稳定组合。
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 3. 类型推导最佳实践
|
|
118
|
+
|
|
119
|
+
### 3.1 方式对比
|
|
120
|
+
|
|
121
|
+
| 方式 | JavaScript | TypeScript | 类型推导 | 推荐度 |
|
|
122
|
+
|------|-----------|-----------|---------|--------|
|
|
123
|
+
| 直接字符串 | ✅ 完美 | ⚠️ 可能无提示 | ❌ 弱 | ⭐⭐ |
|
|
124
|
+
| dsl() 包裹 | ✅ 完美 | ✅ 完美 | ✅ 强 | ⭐⭐⭐⭐⭐ |
|
|
125
|
+
| 先定义再使用 | ✅ 完美 | ✅ 完美 | ✅ 强 | ⭐⭐⭐⭐ |
|
|
126
|
+
|
|
127
|
+
### 3.2 推荐写法
|
|
128
|
+
|
|
129
|
+
#### ✅ 方式 1: 内联使用 dsl() 包裹(最推荐)
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const schema = dsl({
|
|
133
|
+
username: dsl('string:3-32!')
|
|
134
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
135
|
+
.label('用户名'),
|
|
136
|
+
.error({ pattern: '只能包含字母、数字和下划线' }),
|
|
137
|
+
|
|
138
|
+
email: dsl('email!')
|
|
139
|
+
.label('邮箱地址')
|
|
140
|
+
.error({ required: '邮箱必填' }),
|
|
141
|
+
|
|
142
|
+
age: dsl('number:18-100')
|
|
143
|
+
.label('年龄')
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**优点**:
|
|
148
|
+
- ✅ 完整的类型推导
|
|
149
|
+
- ✅ IDE 自动提示所有方法
|
|
150
|
+
- ✅ 代码紧凑,逻辑清晰
|
|
151
|
+
|
|
152
|
+
#### ✅ 方式 2: 先定义字段,再组合(适合复用)
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// 定义可复用的字段
|
|
156
|
+
const emailField = dsl('email!')
|
|
157
|
+
.label('邮箱地址')
|
|
158
|
+
.error({ required: '邮箱必填' });
|
|
159
|
+
|
|
160
|
+
const usernameField = dsl('string:3-32!')
|
|
161
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
162
|
+
.label('用户名')
|
|
163
|
+
.error({ pattern: '用户名只能包含字母、数字和下划线' });
|
|
164
|
+
|
|
165
|
+
// 组合使用
|
|
166
|
+
const registrationSchema = dsl({
|
|
167
|
+
email: emailField,
|
|
168
|
+
username: usernameField,
|
|
169
|
+
password: dsl('string:8-64!')
|
|
170
|
+
.pattern(/^(?=.*[A-Za-z])(?=.*\d).{8,}$/)
|
|
171
|
+
.label('密码')
|
|
172
|
+
.error({ pattern: '密码至少 8 位且必须包含字母和数字' })
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const loginSchema = dsl({
|
|
176
|
+
email: emailField, // 复用
|
|
177
|
+
password: dsl('string!').label('密码')
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**优点**:
|
|
182
|
+
- ✅ 字段定义可复用
|
|
183
|
+
- ✅ 代码更模块化
|
|
184
|
+
- ✅ 适合大型项目
|
|
185
|
+
|
|
186
|
+
#### ❌ 不推荐的写法
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// ❌ 在 TypeScript 中直接使用字符串链式调用
|
|
190
|
+
const schema = dsl({
|
|
191
|
+
email: 'email!'.label('邮箱') // 可能无类型提示
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ❌ 混合使用(不一致)
|
|
195
|
+
const schema = dsl({
|
|
196
|
+
email: 'email!'.label('邮箱'), // 字符串扩展
|
|
197
|
+
username: dsl('string!').label('用户名') // dsl 包裹
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 4. 完整示例
|
|
204
|
+
|
|
205
|
+
### 4.1 用户注册表单
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { dsl, validateAsync, ValidationError } from 'schema-dsl';
|
|
209
|
+
|
|
210
|
+
// 定义 Schema
|
|
211
|
+
const registrationSchema = dsl({
|
|
212
|
+
profile: dsl({
|
|
213
|
+
username: dsl('string:3-32!')
|
|
214
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
215
|
+
.label('用户名')
|
|
216
|
+
.error({ pattern: '只能包含字母、数字和下划线' }),
|
|
217
|
+
|
|
218
|
+
email: dsl('email!')
|
|
219
|
+
.label('邮箱地址')
|
|
220
|
+
.error({ required: '邮箱必填' }),
|
|
221
|
+
|
|
222
|
+
password: dsl('string:8-64!')
|
|
223
|
+
.pattern(/^(?=.*[A-Za-z])(?=.*\d).{8,}$/)
|
|
224
|
+
.label('密码')
|
|
225
|
+
.error({ pattern: '密码至少 8 位且必须包含字母和数字' }),
|
|
226
|
+
|
|
227
|
+
age: dsl('number:18-100')
|
|
228
|
+
.label('年龄')
|
|
229
|
+
}),
|
|
230
|
+
|
|
231
|
+
settings: dsl({
|
|
232
|
+
emailNotify: dsl('boolean')
|
|
233
|
+
.default(true)
|
|
234
|
+
.label('邮件通知'),
|
|
235
|
+
|
|
236
|
+
language: dsl('string')
|
|
237
|
+
.default('zh-CN')
|
|
238
|
+
.label('语言设置')
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// 异步验证(推荐)
|
|
243
|
+
async function registerUser(data: any) {
|
|
244
|
+
try {
|
|
245
|
+
const validData = await validateAsync(registrationSchema, data);
|
|
246
|
+
console.log('注册成功:', validData);
|
|
247
|
+
return validData;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (error instanceof ValidationError) {
|
|
250
|
+
console.log('验证失败:');
|
|
251
|
+
error.errors.forEach(err => {
|
|
252
|
+
console.log(` - ${err.path}: ${err.message}`);
|
|
253
|
+
});
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 使用
|
|
261
|
+
registerUser({
|
|
262
|
+
profile: {
|
|
263
|
+
username: 'testuser',
|
|
264
|
+
email: 'test@example.com',
|
|
265
|
+
password: 'StrongPass123!',
|
|
266
|
+
age: 25
|
|
267
|
+
},
|
|
268
|
+
settings: {
|
|
269
|
+
emailNotify: true,
|
|
270
|
+
language: 'en-US'
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 4.2 API 请求验证
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { ValidationError, dsl, validateAsync } from 'schema-dsl';
|
|
279
|
+
import express from 'express';
|
|
280
|
+
|
|
281
|
+
const app = express();
|
|
282
|
+
app.use(express.json());
|
|
283
|
+
|
|
284
|
+
// 定义 API Schema
|
|
285
|
+
const createUserSchema = dsl({
|
|
286
|
+
username: dsl('string:3-32!')
|
|
287
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
288
|
+
.label('用户名'),
|
|
289
|
+
|
|
290
|
+
email: dsl('email!').label('邮箱'),
|
|
291
|
+
|
|
292
|
+
role: dsl('string')
|
|
293
|
+
.default('user')
|
|
294
|
+
.label('角色')
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// 使用中间件
|
|
298
|
+
app.post('/api/users', async (req, res) => {
|
|
299
|
+
try {
|
|
300
|
+
const validData = await validateAsync(createUserSchema, req.body);
|
|
301
|
+
|
|
302
|
+
// 创建用户逻辑
|
|
303
|
+
const user = await createUser(validData);
|
|
304
|
+
|
|
305
|
+
res.json({ success: true, data: user });
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (error instanceof ValidationError) {
|
|
308
|
+
res.status(400).json({
|
|
309
|
+
success: false,
|
|
310
|
+
errors: error.errors.map(e => ({
|
|
311
|
+
field: e.path,
|
|
312
|
+
message: e.message
|
|
313
|
+
}))
|
|
314
|
+
});
|
|
315
|
+
} else {
|
|
316
|
+
res.status(500).json({ success: false, message: '服务器错误' });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 4.3 表单字段复用
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import { dsl } from 'schema-dsl';
|
|
326
|
+
|
|
327
|
+
// 定义常用字段
|
|
328
|
+
const commonFields = {
|
|
329
|
+
email: dsl('email!')
|
|
330
|
+
.label('邮箱地址')
|
|
331
|
+
.error({ required: '邮箱必填' }),
|
|
332
|
+
|
|
333
|
+
username: dsl('string:3-32!')
|
|
334
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
335
|
+
.label('用户名')
|
|
336
|
+
.error({ pattern: '用户名只能包含字母、数字和下划线' }),
|
|
337
|
+
|
|
338
|
+
password: dsl('string:8-64!')
|
|
339
|
+
.pattern(/^(?=.*[A-Za-z])(?=.*\d).{8,}$/)
|
|
340
|
+
.label('密码')
|
|
341
|
+
.error({ pattern: '密码至少 8 位且必须包含字母和数字' })
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// 注册表单
|
|
345
|
+
const registrationSchema = dsl({
|
|
346
|
+
...commonFields,
|
|
347
|
+
confirmPassword: dsl('string!')
|
|
348
|
+
.label('确认密码')
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// 登录表单
|
|
352
|
+
const loginSchema = dsl({
|
|
353
|
+
email: commonFields.email,
|
|
354
|
+
password: dsl('string!').label('密码') // 登录时不需要强密码验证
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// 密码重置表单
|
|
358
|
+
const resetPasswordSchema = dsl({
|
|
359
|
+
email: commonFields.email,
|
|
360
|
+
newPassword: commonFields.password,
|
|
361
|
+
confirmPassword: dsl('string!').label('确认新密码')
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 5. 常见问题
|
|
368
|
+
|
|
369
|
+
### 5.1 为什么 TypeScript 中字符串链式调用没有类型提示?
|
|
370
|
+
|
|
371
|
+
**原因**: TypeScript 对全局 `String.prototype` 扩展的类型推导有限制。
|
|
372
|
+
|
|
373
|
+
**解决**: 使用 `dsl()` 包裹字符串:
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// ❌ 可能无提示
|
|
377
|
+
'email!'.label('邮箱')
|
|
378
|
+
|
|
379
|
+
// ✅ 完整提示
|
|
380
|
+
dsl('email!').label('邮箱')
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 5.2 JavaScript 用户需要改变写法吗?
|
|
384
|
+
|
|
385
|
+
**不需要!** JavaScript 用户可以继续使用字符串链式调用:
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
// JavaScript 中完全正常
|
|
389
|
+
const schema = dsl({
|
|
390
|
+
email: 'email!'.label('邮箱')
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### 5.3 如何在严格模式下使用?
|
|
395
|
+
|
|
396
|
+
在 `tsconfig.json` 中启用严格模式也没问题:
|
|
397
|
+
|
|
398
|
+
```json
|
|
399
|
+
{
|
|
400
|
+
"compilerOptions": {
|
|
401
|
+
"strict": true,
|
|
402
|
+
"noImplicitAny": true
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
只需使用 `dsl()` 包裹即可:
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
const schema = dsl({
|
|
411
|
+
email: dsl('email!').label('邮箱') // ✅ 严格模式下正常
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 5.4 如何获取验证后的数据类型?
|
|
416
|
+
|
|
417
|
+
使用泛型参数:
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
interface User {
|
|
421
|
+
username: string;
|
|
422
|
+
email: string;
|
|
423
|
+
age?: number;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// 同步验证
|
|
427
|
+
const result = validate<User>(userSchema, data);
|
|
428
|
+
if (result.valid) {
|
|
429
|
+
const user: User = result.data; // ✅ 类型安全
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 异步验证
|
|
433
|
+
const validUser = await validateAsync<User>(userSchema, data);
|
|
434
|
+
// ^? User - 完整的类型推导
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### 5.5 如何处理嵌套对象的验证错误?
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
try {
|
|
441
|
+
await validateAsync(schema, data);
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (error instanceof ValidationError) {
|
|
444
|
+
// 方式 1: 遍历所有错误
|
|
445
|
+
error.errors.forEach(err => {
|
|
446
|
+
console.log(`${err.path}: ${err.message}`);
|
|
447
|
+
// 输出: profile.username: 用户名至少3个字符
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// 方式 2: 获取特定字段错误
|
|
451
|
+
const usernameError = error.getFieldError('profile.username');
|
|
452
|
+
if (usernameError) {
|
|
453
|
+
console.log(usernameError.message);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 方式 3: 获取所有字段错误映射
|
|
457
|
+
const fieldErrors = error.getFieldErrors();
|
|
458
|
+
// { 'profile.username': {...}, 'profile.email': {...} }
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## 6. 进阶技巧
|
|
466
|
+
|
|
467
|
+
### 6.1 额外业务规则
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
const schema = dsl({
|
|
471
|
+
username: dsl('string:3-32!').label('用户名')
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const result = await validateAsync(schema, data);
|
|
475
|
+
if (result.username === 'admin') {
|
|
476
|
+
throw new Error('用户名已存在');
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
这种写法的好处是:结构校验仍由 schema-dsl 负责,业务唯一性、数据库查重等规则继续留在 TypeScript 业务层,避免把外部依赖塞进字段声明。
|
|
481
|
+
|
|
482
|
+
### 6.2 条件验证
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
const schema = dsl({
|
|
486
|
+
userType: dsl('string!').label('用户类型'),
|
|
487
|
+
|
|
488
|
+
// 使用 dsl.match() 根据 userType 字段动态验证
|
|
489
|
+
companyName: dsl.match('userType', {
|
|
490
|
+
'company': 'string!', // 企业用户必填
|
|
491
|
+
'_default': 'string' // 个人用户可选
|
|
492
|
+
})
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### 6.3 Schema 复用和扩展
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
import { SchemaUtils } from 'schema-dsl';
|
|
500
|
+
|
|
501
|
+
// 基础用户 Schema
|
|
502
|
+
const baseUserSchema = dsl({
|
|
503
|
+
username: dsl('string:3-32!').label('用户名'),
|
|
504
|
+
email: dsl('email!').label('邮箱')
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// 扩展为管理员 Schema
|
|
508
|
+
const adminSchema = SchemaUtils.extend(baseUserSchema, {
|
|
509
|
+
role: dsl('string!').default('admin').label('角色'),
|
|
510
|
+
permissions: dsl('array<string>').label('权限列表')
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// 只选择部分字段
|
|
514
|
+
const publicUserSchema = SchemaUtils.pick(
|
|
515
|
+
baseUserSchema,
|
|
516
|
+
['username']
|
|
517
|
+
);
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## 7. 性能优化
|
|
523
|
+
|
|
524
|
+
### 7.1 复用 Schema 与默认缓存
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
const schema = dsl({
|
|
528
|
+
email: dsl('email!').label('邮箱')
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// 多次验证会复用默认 Validator 的编译缓存
|
|
532
|
+
await validateAsync(schema, data1);
|
|
533
|
+
await validateAsync(schema, data2);
|
|
534
|
+
await validateAsync(schema, data3);
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 7.2 缓存配置
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
import { dsl } from 'schema-dsl';
|
|
541
|
+
|
|
542
|
+
// 配置缓存大小
|
|
543
|
+
dsl.config({
|
|
544
|
+
cache: {
|
|
545
|
+
maxSize: 5000, // 缓存条目数
|
|
546
|
+
ttl: 60000 // 过期时间(毫秒)
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## 8. 最佳实践总结
|
|
554
|
+
|
|
555
|
+
1. ✅ **TypeScript 中始终使用 `dsl()` 包裹字符串**
|
|
556
|
+
2. ✅ **使用 `validateAsync` 进行异步验证**
|
|
557
|
+
3. ✅ **为验证结果添加泛型类型参数**
|
|
558
|
+
4. ✅ **复用常用字段定义**
|
|
559
|
+
5. ✅ **使用 `ValidationError` 类型守卫处理错误**
|
|
560
|
+
6. ✅ **为用户提供友好的错误消息**
|
|
561
|
+
7. ✅ **复用常用 Schema 对象,让默认缓存命中**
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## 9. 相关资源
|
|
566
|
+
|
|
567
|
+
- [API 参考文档](./api-reference.md)
|
|
568
|
+
- [DSL 语法完整指南](./dsl-syntax.md)
|
|
569
|
+
- [验证规则参考](./validation-guide.md)
|
|
570
|
+
- [错误处理指南](./error-handling.md)
|
|
571
|
+
- [GitHub 仓库](https://github.com/vextjs/schema-dsl)
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## 对应示例文件
|
|
576
|
+
|
|
577
|
+
**示例入口**: [typescript-guide.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/typescript-guide.ts)
|
|
578
|
+
**说明**: 展示 TypeScript 下推荐的 `dsl()` 包裹写法、`validate<T>()` / `validateAsync<T>()`、以及 `ValidationError` 的字段错误读取方式。
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
**更新日期**: 2026-05-08
|
|
583
|
+
**文档版本**: v2.0.0-beta.2
|
|
584
|
+
|