schema-dsl 2.3.0 → 2.3.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/.github/workflows/ci.yml +1 -3
- package/README.md +69 -7
- package/docs/FEATURE-INDEX.md +1 -1
- package/docs/INDEX.md +31 -2
- package/docs/export-guide.md +3 -0
- package/docs/export-limitations.md +551 -0
- package/docs/mongodb-exporter.md +16 -0
- package/docs/mysql-exporter.md +16 -0
- package/docs/postgresql-exporter.md +14 -0
- package/docs/schema-utils-chaining.md +146 -0
- package/docs/schema-utils.md +7 -9
- package/docs/validate-async.md +480 -0
- package/examples/array-dsl-example.js +1 -1
- package/examples/express-integration.js +376 -0
- package/examples/new-features-comparison.js +315 -0
- package/examples/schema-utils-chaining.examples.js +250 -0
- package/examples/simple-example.js +2 -2
- package/index.js +13 -1
- package/lib/adapters/DslAdapter.js +47 -1
- package/lib/core/Validator.js +60 -0
- package/lib/errors/ValidationError.js +191 -0
- package/lib/utils/SchemaUtils.js +170 -38
- package/package.json +1 -1
package/lib/utils/SchemaUtils.js
CHANGED
|
@@ -45,42 +45,13 @@ class SchemaUtils {
|
|
|
45
45
|
return fragments;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// ========== Schema
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 合并多个Schema
|
|
52
|
-
* @param {...Object} schemas - 要合并的Schema对象
|
|
53
|
-
* @returns {Object} 合并后的Schema
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* const baseUser = dsl({ name: 'string!', email: 'email!' });
|
|
57
|
-
* const withAge = dsl({ age: 'number:18-120' });
|
|
58
|
-
* const merged = SchemaUtils.merge(baseUser, withAge);
|
|
59
|
-
*/
|
|
60
|
-
static merge(...schemas) {
|
|
61
|
-
const result = {
|
|
62
|
-
type: 'object',
|
|
63
|
-
properties: {},
|
|
64
|
-
required: []
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
schemas.forEach(schema => {
|
|
68
|
-
if (schema.properties) {
|
|
69
|
-
Object.assign(result.properties, schema.properties);
|
|
70
|
-
}
|
|
71
|
-
if (schema.required) {
|
|
72
|
-
result.required = [...new Set([...result.required, ...schema.required])];
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
48
|
+
// ========== Schema复用和扩展 ==========
|
|
78
49
|
|
|
79
50
|
/**
|
|
80
51
|
* 扩展Schema(类似继承)
|
|
81
52
|
* @param {Object} baseSchema - 基础Schema
|
|
82
53
|
* @param {Object} extensions - 扩展定义
|
|
83
|
-
* @returns {Object} 扩展后的Schema
|
|
54
|
+
* @returns {Object} 扩展后的Schema(支持链式调用)
|
|
84
55
|
*
|
|
85
56
|
* @example
|
|
86
57
|
* const baseUser = dsl({ name: 'string!', email: 'email!' });
|
|
@@ -90,12 +61,35 @@ class SchemaUtils {
|
|
|
90
61
|
* });
|
|
91
62
|
*/
|
|
92
63
|
static extend(baseSchema, extensions) {
|
|
93
|
-
const
|
|
64
|
+
const dsl = require('../adapters/DslAdapter');
|
|
94
65
|
const extensionSchema = typeof extensions === 'function'
|
|
95
66
|
? extensions
|
|
96
67
|
: dsl(extensions);
|
|
97
68
|
|
|
98
|
-
|
|
69
|
+
// 合并 properties
|
|
70
|
+
const result = {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {},
|
|
73
|
+
required: []
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// 复制基础 schema
|
|
77
|
+
if (baseSchema.properties) {
|
|
78
|
+
Object.assign(result.properties, baseSchema.properties);
|
|
79
|
+
}
|
|
80
|
+
if (baseSchema.required) {
|
|
81
|
+
result.required = [...baseSchema.required];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 添加扩展
|
|
85
|
+
if (extensionSchema.properties) {
|
|
86
|
+
Object.assign(result.properties, extensionSchema.properties);
|
|
87
|
+
}
|
|
88
|
+
if (extensionSchema.required) {
|
|
89
|
+
result.required = [...new Set([...result.required, ...extensionSchema.required])];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return this._makeChainable(result);
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
/**
|
|
@@ -124,19 +118,86 @@ class SchemaUtils {
|
|
|
124
118
|
}
|
|
125
119
|
});
|
|
126
120
|
|
|
127
|
-
return result;
|
|
121
|
+
return this._makeChainable(result);
|
|
128
122
|
}
|
|
129
123
|
|
|
130
124
|
/**
|
|
131
125
|
* 排除Schema的部分字段
|
|
132
126
|
* @param {Object} schema - 原始Schema
|
|
133
127
|
* @param {string[]} fields - 要排除的字段
|
|
134
|
-
* @returns {Object} 新Schema
|
|
128
|
+
* @returns {Object} 新Schema(支持链式调用)
|
|
135
129
|
*/
|
|
136
130
|
static omit(schema, fields) {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
const result = this._clone(schema);
|
|
132
|
+
|
|
133
|
+
fields.forEach(field => {
|
|
134
|
+
if (result.properties) {
|
|
135
|
+
delete result.properties[field];
|
|
136
|
+
}
|
|
137
|
+
if (result.required) {
|
|
138
|
+
result.required = result.required.filter(f => f !== field);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// 清理空数组
|
|
143
|
+
if (result.required && result.required.length === 0) {
|
|
144
|
+
delete result.required;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return this._makeChainable(result);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ========== v2.1.0 新增:Schema转换方法(支持链式调用) ==========
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 部分验证:移除必填限制
|
|
154
|
+
*
|
|
155
|
+
* @param {Object} schema - 原始Schema
|
|
156
|
+
* @param {string[]} fields - 要验证的字段(可选,默认全部)
|
|
157
|
+
* @returns {Object} 新Schema(支持链式调用)
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* // 所有字段变为可选
|
|
161
|
+
* const partialSchema = SchemaUtils.partial(userSchema);
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* // 只验证指定字段
|
|
165
|
+
* const updateSchema = SchemaUtils.partial(userSchema, ['name', 'age']);
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* // 链式调用
|
|
169
|
+
* const patchSchema = SchemaUtils
|
|
170
|
+
* .pick(userSchema, ['name', 'age'])
|
|
171
|
+
* .partial();
|
|
172
|
+
*/
|
|
173
|
+
static partial(schema, fields = null) {
|
|
174
|
+
let result;
|
|
175
|
+
|
|
176
|
+
if (fields) {
|
|
177
|
+
// 只保留指定字段 (pick 已经返回 chainable 对象)
|
|
178
|
+
result = this.pick(schema, fields);
|
|
179
|
+
// 提取原始 schema
|
|
180
|
+
if (result._isChainable) {
|
|
181
|
+
result = this._extractSchema(result);
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
result = this._clone(schema);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 移除所有 required
|
|
188
|
+
delete result.required;
|
|
189
|
+
|
|
190
|
+
// 递归处理嵌套对象
|
|
191
|
+
if (result.properties) {
|
|
192
|
+
Object.keys(result.properties).forEach(key => {
|
|
193
|
+
const prop = result.properties[key];
|
|
194
|
+
if (prop && prop.type === 'object' && prop.required) {
|
|
195
|
+
delete prop.required;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return this._makeChainable(result);
|
|
140
201
|
}
|
|
141
202
|
|
|
142
203
|
// ========== 性能监控 ==========
|
|
@@ -307,6 +368,77 @@ class SchemaUtils {
|
|
|
307
368
|
static clone(schema) {
|
|
308
369
|
return JSON.parse(JSON.stringify(schema));
|
|
309
370
|
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* 深拷贝Schema
|
|
374
|
+
* @private
|
|
375
|
+
* @param {Object} schema - 原始Schema
|
|
376
|
+
* @returns {Object} 拷贝后的Schema
|
|
377
|
+
*/
|
|
378
|
+
static _clone(schema) {
|
|
379
|
+
// 如果是 chainable 对象,先提取原始 schema
|
|
380
|
+
if (schema && schema._isChainable) {
|
|
381
|
+
schema = this._extractSchema(schema);
|
|
382
|
+
}
|
|
383
|
+
return JSON.parse(JSON.stringify(schema));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* 使 schema 支持链式调用
|
|
388
|
+
* @private
|
|
389
|
+
* @param {Object} schema - Schema对象
|
|
390
|
+
* @returns {Object} 支持链式调用的 Schema
|
|
391
|
+
*/
|
|
392
|
+
static _makeChainable(schema) {
|
|
393
|
+
// 如果已经是 chainable,直接返回
|
|
394
|
+
if (schema && schema._isChainable) {
|
|
395
|
+
return schema;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 复制 schema 的所有属性
|
|
399
|
+
const chainable = Object.assign({}, schema);
|
|
400
|
+
|
|
401
|
+
// 标记为 chainable
|
|
402
|
+
Object.defineProperty(chainable, '_isChainable', {
|
|
403
|
+
value: true,
|
|
404
|
+
enumerable: false,
|
|
405
|
+
configurable: false
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// 添加链式方法(只保留核心4个方法)
|
|
409
|
+
const methods = ['partial', 'omit', 'pick', 'extend'];
|
|
410
|
+
const self = this; // 保存 this 引用
|
|
411
|
+
methods.forEach(method => {
|
|
412
|
+
Object.defineProperty(chainable, method, {
|
|
413
|
+
value: (...args) => {
|
|
414
|
+
// 提取原始 schema(去掉链式方法)
|
|
415
|
+
const rawSchema = self._extractSchema(chainable);
|
|
416
|
+
// 调用 SchemaUtils 的静态方法
|
|
417
|
+
return SchemaUtils[method](rawSchema, ...args);
|
|
418
|
+
},
|
|
419
|
+
enumerable: false,
|
|
420
|
+
configurable: false
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
return chainable;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* 从 chainable 对象中提取原始 schema
|
|
429
|
+
* @private
|
|
430
|
+
* @param {Object} chainable - Chainable对象
|
|
431
|
+
* @returns {Object} 原始 Schema
|
|
432
|
+
*/
|
|
433
|
+
static _extractSchema(chainable) {
|
|
434
|
+
const schema = {};
|
|
435
|
+
for (const key in chainable) {
|
|
436
|
+
if (chainable.hasOwnProperty(key) && key !== '_isChainable') {
|
|
437
|
+
schema[key] = chainable[key];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return schema;
|
|
441
|
+
}
|
|
310
442
|
}
|
|
311
443
|
|
|
312
444
|
module.exports = SchemaUtils;
|