@zhin.js/schema 1.0.42 → 1.0.44
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/package.json +7 -4
- package/CHANGELOG.md +0 -274
- package/src/index.ts +0 -2
- package/src/schema.ts +0 -374
- package/src/utils.ts +0 -32
- package/tests/schema.test.ts +0 -243
- package/tsconfig.json +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/schema",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.44",
|
|
4
4
|
"description": "Configuration validation and schema system for Zhin.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -11,10 +11,13 @@
|
|
|
11
11
|
"import": "./lib/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"lib"
|
|
16
|
+
],
|
|
14
17
|
"devDependencies": {
|
|
15
|
-
"typescript": "^5.3
|
|
18
|
+
"typescript": "^5.9.3",
|
|
16
19
|
"@types/node": "^24.3.0",
|
|
17
|
-
"zhin.js": "1.0.
|
|
20
|
+
"zhin.js": "1.0.60"
|
|
18
21
|
},
|
|
19
22
|
"repository": {
|
|
20
23
|
"type": "git",
|
|
@@ -26,7 +29,7 @@
|
|
|
26
29
|
"registry": "https://registry.npmjs.org"
|
|
27
30
|
},
|
|
28
31
|
"peerDependencies": {
|
|
29
|
-
"zhin.js": "1.0.
|
|
32
|
+
"zhin.js": "1.0.60"
|
|
30
33
|
},
|
|
31
34
|
"keywords": [
|
|
32
35
|
"zhin",
|
package/CHANGELOG.md
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
# @zhin.js/schema
|
|
2
|
-
|
|
3
|
-
## 1.0.42
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- zhin.js@1.0.58
|
|
8
|
-
|
|
9
|
-
## 1.0.41
|
|
10
|
-
|
|
11
|
-
### Patch Changes
|
|
12
|
-
|
|
13
|
-
- zhin.js@1.0.57
|
|
14
|
-
|
|
15
|
-
## 1.0.40
|
|
16
|
-
|
|
17
|
-
### Patch Changes
|
|
18
|
-
|
|
19
|
-
- zhin.js@1.0.56
|
|
20
|
-
|
|
21
|
-
## 1.0.39
|
|
22
|
-
|
|
23
|
-
### Patch Changes
|
|
24
|
-
|
|
25
|
-
- zhin.js@1.0.55
|
|
26
|
-
|
|
27
|
-
## 1.0.38
|
|
28
|
-
|
|
29
|
-
### Patch Changes
|
|
30
|
-
|
|
31
|
-
- 16c8f92: fix: 统一发一次版
|
|
32
|
-
- Updated dependencies [16c8f92]
|
|
33
|
-
- zhin.js@1.0.54
|
|
34
|
-
|
|
35
|
-
## 1.0.37
|
|
36
|
-
|
|
37
|
-
### Patch Changes
|
|
38
|
-
|
|
39
|
-
- zhin.js@1.0.53
|
|
40
|
-
|
|
41
|
-
## 1.0.36
|
|
42
|
-
|
|
43
|
-
### Patch Changes
|
|
44
|
-
|
|
45
|
-
- Updated dependencies [bb6bfa8]
|
|
46
|
-
- Updated dependencies [bb6bfa8]
|
|
47
|
-
- zhin.js@1.0.52
|
|
48
|
-
|
|
49
|
-
## 1.0.35
|
|
50
|
-
|
|
51
|
-
### Patch Changes
|
|
52
|
-
|
|
53
|
-
- zhin.js@1.0.51
|
|
54
|
-
|
|
55
|
-
## 1.0.34
|
|
56
|
-
|
|
57
|
-
### Patch Changes
|
|
58
|
-
|
|
59
|
-
- zhin.js@1.0.50
|
|
60
|
-
|
|
61
|
-
## 1.0.33
|
|
62
|
-
|
|
63
|
-
### Patch Changes
|
|
64
|
-
|
|
65
|
-
- zhin.js@1.0.49
|
|
66
|
-
|
|
67
|
-
## 1.0.32
|
|
68
|
-
|
|
69
|
-
### Patch Changes
|
|
70
|
-
|
|
71
|
-
- zhin.js@1.0.48
|
|
72
|
-
|
|
73
|
-
## 1.0.31
|
|
74
|
-
|
|
75
|
-
### Patch Changes
|
|
76
|
-
|
|
77
|
-
- Updated dependencies [de3e352]
|
|
78
|
-
- zhin.js@1.0.47
|
|
79
|
-
|
|
80
|
-
## 1.0.30
|
|
81
|
-
|
|
82
|
-
### Patch Changes
|
|
83
|
-
|
|
84
|
-
- Updated dependencies [7394603]
|
|
85
|
-
- zhin.js@1.0.46
|
|
86
|
-
|
|
87
|
-
## 1.0.29
|
|
88
|
-
|
|
89
|
-
### Patch Changes
|
|
90
|
-
|
|
91
|
-
- zhin.js@1.0.45
|
|
92
|
-
|
|
93
|
-
## 1.0.28
|
|
94
|
-
|
|
95
|
-
### Patch Changes
|
|
96
|
-
|
|
97
|
-
- zhin.js@1.0.44
|
|
98
|
-
|
|
99
|
-
## 1.0.27
|
|
100
|
-
|
|
101
|
-
### Patch Changes
|
|
102
|
-
|
|
103
|
-
- Updated dependencies [72ec4ba]
|
|
104
|
-
- zhin.js@1.0.43
|
|
105
|
-
|
|
106
|
-
## 1.0.26
|
|
107
|
-
|
|
108
|
-
### Patch Changes
|
|
109
|
-
|
|
110
|
-
- zhin.js@1.0.42
|
|
111
|
-
|
|
112
|
-
## 1.0.25
|
|
113
|
-
|
|
114
|
-
### Patch Changes
|
|
115
|
-
|
|
116
|
-
- zhin.js@1.0.41
|
|
117
|
-
|
|
118
|
-
## 1.0.24
|
|
119
|
-
|
|
120
|
-
### Patch Changes
|
|
121
|
-
|
|
122
|
-
- Updated dependencies [7ef9057]
|
|
123
|
-
- zhin.js@1.0.40
|
|
124
|
-
|
|
125
|
-
## 1.0.23
|
|
126
|
-
|
|
127
|
-
### Patch Changes
|
|
128
|
-
|
|
129
|
-
- zhin.js@1.0.39
|
|
130
|
-
|
|
131
|
-
## 1.0.22
|
|
132
|
-
|
|
133
|
-
### Patch Changes
|
|
134
|
-
|
|
135
|
-
- Updated dependencies [ab5c54a]
|
|
136
|
-
- zhin.js@1.0.38
|
|
137
|
-
|
|
138
|
-
## 1.0.21
|
|
139
|
-
|
|
140
|
-
### Patch Changes
|
|
141
|
-
|
|
142
|
-
- zhin.js@1.0.37
|
|
143
|
-
|
|
144
|
-
## 1.0.20
|
|
145
|
-
|
|
146
|
-
### Patch Changes
|
|
147
|
-
|
|
148
|
-
- zhin.js@1.0.36
|
|
149
|
-
|
|
150
|
-
## 1.0.19
|
|
151
|
-
|
|
152
|
-
### Patch Changes
|
|
153
|
-
|
|
154
|
-
- zhin.js@1.0.35
|
|
155
|
-
|
|
156
|
-
## 1.0.18
|
|
157
|
-
|
|
158
|
-
### Patch Changes
|
|
159
|
-
|
|
160
|
-
- zhin.js@1.0.34
|
|
161
|
-
|
|
162
|
-
## 1.0.17
|
|
163
|
-
|
|
164
|
-
### Patch Changes
|
|
165
|
-
|
|
166
|
-
- zhin.js@1.0.33
|
|
167
|
-
|
|
168
|
-
## 1.0.16
|
|
169
|
-
|
|
170
|
-
### Patch Changes
|
|
171
|
-
|
|
172
|
-
- zhin.js@1.0.32
|
|
173
|
-
|
|
174
|
-
## 1.0.15
|
|
175
|
-
|
|
176
|
-
### Patch Changes
|
|
177
|
-
|
|
178
|
-
- zhin.js@1.0.31
|
|
179
|
-
|
|
180
|
-
## 1.0.14
|
|
181
|
-
|
|
182
|
-
### Patch Changes
|
|
183
|
-
|
|
184
|
-
- Updated dependencies [460a6c6]
|
|
185
|
-
- zhin.js@1.0.30
|
|
186
|
-
|
|
187
|
-
## 1.0.13
|
|
188
|
-
|
|
189
|
-
### Patch Changes
|
|
190
|
-
|
|
191
|
-
- zhin.js@1.0.29
|
|
192
|
-
|
|
193
|
-
## 1.0.12
|
|
194
|
-
|
|
195
|
-
### Patch Changes
|
|
196
|
-
|
|
197
|
-
- zhin.js@1.0.28
|
|
198
|
-
|
|
199
|
-
## 1.0.11
|
|
200
|
-
|
|
201
|
-
### Patch Changes
|
|
202
|
-
|
|
203
|
-
- Updated dependencies [b27e633]
|
|
204
|
-
- zhin.js@1.0.27
|
|
205
|
-
|
|
206
|
-
## 1.0.10
|
|
207
|
-
|
|
208
|
-
### Patch Changes
|
|
209
|
-
|
|
210
|
-
- 106d357: fix: ai
|
|
211
|
-
- Updated dependencies [106d357]
|
|
212
|
-
- zhin.js@1.0.26
|
|
213
|
-
|
|
214
|
-
## 1.0.9
|
|
215
|
-
|
|
216
|
-
### Patch Changes
|
|
217
|
-
|
|
218
|
-
- 26d2942: fix: ai
|
|
219
|
-
- 6b02c41: fix: ai
|
|
220
|
-
- Updated dependencies [26d2942]
|
|
221
|
-
- Updated dependencies [6b02c41]
|
|
222
|
-
- zhin.js@1.0.25
|
|
223
|
-
|
|
224
|
-
## 1.0.8
|
|
225
|
-
|
|
226
|
-
### Patch Changes
|
|
227
|
-
|
|
228
|
-
- zhin.js@1.0.24
|
|
229
|
-
|
|
230
|
-
## 1.0.7
|
|
231
|
-
|
|
232
|
-
### Patch Changes
|
|
233
|
-
|
|
234
|
-
- Updated dependencies [52ae08a]
|
|
235
|
-
- zhin.js@1.0.23
|
|
236
|
-
|
|
237
|
-
## 1.0.6
|
|
238
|
-
|
|
239
|
-
### Patch Changes
|
|
240
|
-
|
|
241
|
-
- Updated dependencies [26aba27]
|
|
242
|
-
- zhin.js@1.0.22
|
|
243
|
-
|
|
244
|
-
## 1.0.5
|
|
245
|
-
|
|
246
|
-
### Patch Changes
|
|
247
|
-
|
|
248
|
-
- zhin.js@1.0.21
|
|
249
|
-
|
|
250
|
-
## 1.0.4
|
|
251
|
-
|
|
252
|
-
### Patch Changes
|
|
253
|
-
|
|
254
|
-
- a3b7673: fix: 调整依赖项
|
|
255
|
-
- Updated dependencies [5141137]
|
|
256
|
-
- zhin.js@1.0.20
|
|
257
|
-
|
|
258
|
-
## 1.0.3
|
|
259
|
-
|
|
260
|
-
### Patch Changes
|
|
261
|
-
|
|
262
|
-
- f9faa1d: fix: test release
|
|
263
|
-
|
|
264
|
-
## 1.0.2
|
|
265
|
-
|
|
266
|
-
### Patch Changes
|
|
267
|
-
|
|
268
|
-
- d16a69c: fix: test trust publish
|
|
269
|
-
|
|
270
|
-
## 1.0.1
|
|
271
|
-
|
|
272
|
-
### Patch Changes
|
|
273
|
-
|
|
274
|
-
- 547028f: fix: 优化包结构,优化客户端支持
|
package/src/index.ts
DELETED
package/src/schema.ts
DELETED
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
import { isEmpty } from './utils.js';
|
|
2
|
-
|
|
3
|
-
export class Schema<S = any, T = S> {
|
|
4
|
-
public [Symbol.toStringTag] = 'Schema';
|
|
5
|
-
constructor(
|
|
6
|
-
public meta: Schema.Meta<S, T>,
|
|
7
|
-
public options: Schema.Options = {},
|
|
8
|
-
) {
|
|
9
|
-
const _this = this;
|
|
10
|
-
const schema = function (value?: S, key: string = 'value') {
|
|
11
|
-
const formatter = Schema.resolve(_this.meta.type);
|
|
12
|
-
if (!formatter) throw new Error(`type ${_this.meta.type} not found`);
|
|
13
|
-
return formatter.call(_this, key, value);
|
|
14
|
-
} as Schema<S, T>;
|
|
15
|
-
return new Proxy(schema, {
|
|
16
|
-
get(target, p: string | symbol, receiver: any): any {
|
|
17
|
-
return Reflect.get(_this, p, receiver);
|
|
18
|
-
},
|
|
19
|
-
set(target, p: string | symbol, value: any, receiver: any): boolean {
|
|
20
|
-
return Reflect.set(_this, p, value, receiver);
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
static fromJSON<S, T>(json: Schema.JSON<S, T>) {
|
|
25
|
-
const { object, inner, list, ...meta } = json;
|
|
26
|
-
const options: Schema.Options = {};
|
|
27
|
-
if (object)
|
|
28
|
-
options.object = Object.fromEntries(Object.entries(object).map(([key, value]) => [key, Schema.fromJSON(value)]));
|
|
29
|
-
if (inner) options.inner = Schema.fromJSON(inner);
|
|
30
|
-
if (list) options.list = list.map(item => Schema.fromJSON(item));
|
|
31
|
-
return new Schema<S, T>(meta, options);
|
|
32
|
-
}
|
|
33
|
-
toJSON(): Record<string, any> {
|
|
34
|
-
return Object.fromEntries(
|
|
35
|
-
Object.entries({
|
|
36
|
-
...this.meta,
|
|
37
|
-
default: typeof this.meta.default === 'function' ? this.meta.default() : this.meta.default,
|
|
38
|
-
inner: this.options.inner?.toJSON(),
|
|
39
|
-
object: this.options.object
|
|
40
|
-
? Object.fromEntries(Object.entries(this.options.object || {}).map(([key, value]) => [
|
|
41
|
-
key,
|
|
42
|
-
{
|
|
43
|
-
...value.toJSON(),
|
|
44
|
-
key
|
|
45
|
-
}
|
|
46
|
-
]))
|
|
47
|
-
: undefined,
|
|
48
|
-
list: this.options.list?.map(item => item.toJSON()),
|
|
49
|
-
}).filter(([key, value]) => typeof value !== 'undefined'),
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
[Symbol.unscopables]() {
|
|
53
|
-
return {
|
|
54
|
-
options: true,
|
|
55
|
-
meta: true,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/** 设置是否必填 */
|
|
59
|
-
required(): this {
|
|
60
|
-
this.meta.required = true;
|
|
61
|
-
return this;
|
|
62
|
-
}
|
|
63
|
-
/** 是否隐藏 */
|
|
64
|
-
hidden(): this {
|
|
65
|
-
this.meta.hidden = true;
|
|
66
|
-
return this;
|
|
67
|
-
}
|
|
68
|
-
/** 设置描述 */
|
|
69
|
-
description(description: string): this {
|
|
70
|
-
this.meta.description = description;
|
|
71
|
-
return this;
|
|
72
|
-
}
|
|
73
|
-
/** 设置默认值 */
|
|
74
|
-
default(defaultValue: T): this {
|
|
75
|
-
this.meta.default = defaultValue;
|
|
76
|
-
return this;
|
|
77
|
-
}
|
|
78
|
-
/** 设置选项列表 */
|
|
79
|
-
option(list: (T | Schema.Option<T>)[]): this {
|
|
80
|
-
this.meta.options = Schema.formatOptionList(list);
|
|
81
|
-
return this;
|
|
82
|
-
}
|
|
83
|
-
/** 设置是否允许多选 */
|
|
84
|
-
multiple(): this {
|
|
85
|
-
if (this.meta.type !== 'list') throw new Error('multiple only support list type');
|
|
86
|
-
this.meta.multiple = true;
|
|
87
|
-
return this;
|
|
88
|
-
}
|
|
89
|
-
/** 设置最小值 */
|
|
90
|
-
min(value: number): this {
|
|
91
|
-
this.meta.min = value;
|
|
92
|
-
return this;
|
|
93
|
-
}
|
|
94
|
-
/** 设置最大值 */
|
|
95
|
-
max(value: number): this {
|
|
96
|
-
this.meta.max = value;
|
|
97
|
-
return this;
|
|
98
|
-
}
|
|
99
|
-
/** 设置步进值 */
|
|
100
|
-
step(value: number): this {
|
|
101
|
-
this.meta.step = value;
|
|
102
|
-
return this;
|
|
103
|
-
}
|
|
104
|
-
/** 设置组件类型 */
|
|
105
|
-
component(type: string): this {
|
|
106
|
-
this.meta.component = type;
|
|
107
|
-
return this;
|
|
108
|
-
}
|
|
109
|
-
/** 声明一个数字类型 */
|
|
110
|
-
static number(key?: string): Schema<number> {
|
|
111
|
-
return new Schema<number>({ type: 'number', key });
|
|
112
|
-
}
|
|
113
|
-
/** 声明一个字符串类型 */
|
|
114
|
-
static string(key?: string): Schema<string> {
|
|
115
|
-
return new Schema<string>({ type: 'string', key });
|
|
116
|
-
}
|
|
117
|
-
/** 声明一个布尔类型 */
|
|
118
|
-
static boolean(key?: string): Schema<boolean> {
|
|
119
|
-
return new Schema<boolean>({ type: 'boolean', key });
|
|
120
|
-
}
|
|
121
|
-
/** 声明一个正则类型 */
|
|
122
|
-
static regexp(key?: string) {
|
|
123
|
-
return new Schema<RegExp | string, RegExp>({ type: 'regexp', key });
|
|
124
|
-
}
|
|
125
|
-
/** 声明一个日期类型 */
|
|
126
|
-
static date(key?: string) {
|
|
127
|
-
return new Schema<Date | number, Date>({ type: 'date', key });
|
|
128
|
-
}
|
|
129
|
-
/** 声明一个字典类型 */
|
|
130
|
-
static dict<X extends Schema>(input: X, key?: string) {
|
|
131
|
-
return new Schema<Record<string, Schema.Types<X>>>({ type: 'dict', key }, { inner: input });
|
|
132
|
-
}
|
|
133
|
-
static object<X extends Record<string, Schema>>(input: X, key: string = '') {
|
|
134
|
-
return new Schema<Schema.RecordTypes<X>>({ type: 'object', key }, { object: input });
|
|
135
|
-
}
|
|
136
|
-
/** 声明一个列表类型 */
|
|
137
|
-
static list<X extends Schema>(inner: X, key?: string) {
|
|
138
|
-
return new Schema<Schema.Types<X>[]>({ type: 'list', key }, { inner });
|
|
139
|
-
}
|
|
140
|
-
/** 声明一个元组类型 */
|
|
141
|
-
static tuple<X extends readonly any[]>(list: X, key?: string) {
|
|
142
|
-
return new Schema<Schema.Tuple<X>>({ type: 'tuple', key }, { list });
|
|
143
|
-
}
|
|
144
|
-
/** 声明一个联合类型 */
|
|
145
|
-
static union<X extends readonly any[]>(list: X, key?: string) {
|
|
146
|
-
return new Schema<Schema.Union<X>>({ type: 'union', key }, { list });
|
|
147
|
-
}
|
|
148
|
-
/** 声明一个交叉类型 */
|
|
149
|
-
static intersect<X extends readonly any[]>(list: X, key?: string) {
|
|
150
|
-
return new Schema<Schema.Intersect<X>>({ type: 'intersect', key }, { list });
|
|
151
|
-
}
|
|
152
|
-
/** 声明一个常量 */
|
|
153
|
-
static const<X extends string | number | boolean>(value: X, key?: string) {
|
|
154
|
-
return new Schema<X>({ type: 'const', default: value as any, key });
|
|
155
|
-
}
|
|
156
|
-
/** 声明任意类型 */
|
|
157
|
-
static any(key?: any) {
|
|
158
|
-
return new Schema<any>({ type: 'any', key });
|
|
159
|
-
}
|
|
160
|
-
static resolve<T extends string>(type: T): Schema.Formatter {
|
|
161
|
-
return Schema.formatters.get(type)!;
|
|
162
|
-
}
|
|
163
|
-
static extend<T extends string>(type: T, formatter: Schema.Formatter) {
|
|
164
|
-
Schema.formatters.set(type, formatter);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
export interface Schema<S = any> {
|
|
168
|
-
(value?: S, key?: string): S;
|
|
169
|
-
}
|
|
170
|
-
export namespace Schema {
|
|
171
|
-
export const formatters: Map<string, Formatter> = new Map<string, Formatter>();
|
|
172
|
-
export type Formatter<S = any, T = S> = (this: Schema, key: string, value: S) => T;
|
|
173
|
-
export type JSON<S = any, T = S> = Meta<S, T> & {
|
|
174
|
-
object?: Record<string, JSON>;
|
|
175
|
-
inner?: JSON;
|
|
176
|
-
list?: JSON[];
|
|
177
|
-
};
|
|
178
|
-
export interface Meta<S = any, T = S> {
|
|
179
|
-
hidden?: boolean;
|
|
180
|
-
type: string;
|
|
181
|
-
default?: T;
|
|
182
|
-
required?: boolean;
|
|
183
|
-
options?: Option<T>[];
|
|
184
|
-
multiple?: boolean;
|
|
185
|
-
key?: string;
|
|
186
|
-
description?: string;
|
|
187
|
-
component?: string;
|
|
188
|
-
min?: number;
|
|
189
|
-
max?: number;
|
|
190
|
-
step?: number;
|
|
191
|
-
}
|
|
192
|
-
export interface Options {
|
|
193
|
-
object?: Record<string, Schema>;
|
|
194
|
-
inner?: Schema;
|
|
195
|
-
list?: readonly Schema[];
|
|
196
|
-
}
|
|
197
|
-
export type Types<T> = T extends Schema<infer S> ? S : never;
|
|
198
|
-
export type RecordTypes<T> = T extends Record<string, Schema>
|
|
199
|
-
? {
|
|
200
|
-
[K in keyof T]?: Types<T[K]>;
|
|
201
|
-
}
|
|
202
|
-
: unknown;
|
|
203
|
-
export type Union<T extends readonly any[]> = T extends readonly [infer L, ...infer R]
|
|
204
|
-
? Types<L> | Union<R>
|
|
205
|
-
: never;
|
|
206
|
-
export type Intersect<T extends readonly any[]> = T extends readonly [infer L, ...infer R]
|
|
207
|
-
? Types<L> & Intersect<R>
|
|
208
|
-
: unknown;
|
|
209
|
-
export type Tuple<T extends readonly any[]> = T extends readonly [infer L, ...infer R]
|
|
210
|
-
? [Types<L>, ...Tuple<R>]
|
|
211
|
-
: [];
|
|
212
|
-
export function checkDefault<T>(schema: Schema, key: string, value: T, fallback: T = value) {
|
|
213
|
-
if (isEmpty(value)) {
|
|
214
|
-
value = schema.meta.default || fallback;
|
|
215
|
-
}
|
|
216
|
-
const validateType = (schema: Schema, key: string, value: any) => {
|
|
217
|
-
switch (schema.meta.type) {
|
|
218
|
-
case 'string':
|
|
219
|
-
if (!['string', 'undefined'].includes(typeof value)) throw new TypeError(`${key} is not a string`);
|
|
220
|
-
break;
|
|
221
|
-
case 'number':
|
|
222
|
-
if (!['number', 'undefined'].includes(typeof value)) throw new TypeError(`${key} is not a number`);
|
|
223
|
-
break;
|
|
224
|
-
case 'boolean':
|
|
225
|
-
if (!['boolean', 'undefined'].includes(typeof value)) throw new TypeError(`${key} is not a boolean`);
|
|
226
|
-
break;
|
|
227
|
-
case 'regexp':
|
|
228
|
-
if (!['string', 'undefined'].includes(typeof value) && !(value instanceof RegExp))
|
|
229
|
-
throw new TypeError(`${key} is not a RegExp|string`);
|
|
230
|
-
break;
|
|
231
|
-
case 'date':
|
|
232
|
-
if (!['number', 'undefined'].includes(typeof value) && !(value instanceof Date))
|
|
233
|
-
throw new TypeError(`${key} is not a Date|number`);
|
|
234
|
-
if (value instanceof Date && isNaN(value.getTime())) throw new TypeError(`${key} is not a valid Date`);
|
|
235
|
-
break;
|
|
236
|
-
case 'dict':
|
|
237
|
-
if (!['object', 'undefined', 'null'].includes(typeof value)) throw new TypeError(`${key} is not a object`);
|
|
238
|
-
break;
|
|
239
|
-
case 'object':
|
|
240
|
-
if (!['object', 'undefined', 'null'].includes(typeof value)) throw new TypeError(`${key} is not a object`);
|
|
241
|
-
break;
|
|
242
|
-
case 'list':
|
|
243
|
-
if (typeof value !== 'undefined' && !Array.isArray(value)) throw new TypeError(`${key} is not a list`);
|
|
244
|
-
break;
|
|
245
|
-
case 'tuple':
|
|
246
|
-
if (typeof value !== 'undefined' && !Array.isArray(value)) throw new TypeError(`${key} is not a tuple`);
|
|
247
|
-
break;
|
|
248
|
-
case 'union':
|
|
249
|
-
// union类型不在这里验证,在extend函数中验证
|
|
250
|
-
break;
|
|
251
|
-
case 'intersect':
|
|
252
|
-
// intersect类型不在这里验证,在extend函数中验证
|
|
253
|
-
break;
|
|
254
|
-
case 'const':
|
|
255
|
-
if (typeof value !== 'undefined' && value !== schema.meta.default) throw new TypeError(`${key} is not const`);
|
|
256
|
-
break;
|
|
257
|
-
case 'any':
|
|
258
|
-
// any类型不验证
|
|
259
|
-
break;
|
|
260
|
-
default:
|
|
261
|
-
throw new TypeError(`${key} is not a valid type`);
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
if (schema.meta.required && typeof value === 'undefined') throw new Error(`${key} is required`);
|
|
265
|
-
validateType(schema, key, value);
|
|
266
|
-
return value;
|
|
267
|
-
}
|
|
268
|
-
export type Option<T = any> = {
|
|
269
|
-
label: string;
|
|
270
|
-
value: T;
|
|
271
|
-
};
|
|
272
|
-
export function formatOptionList<T extends (any | Schema.Option)[]>(list: T): Schema.Option[] {
|
|
273
|
-
return list.map(item => {
|
|
274
|
-
if (typeof item === 'string') {
|
|
275
|
-
return {
|
|
276
|
-
label: `${item}`,
|
|
277
|
-
value: item,
|
|
278
|
-
} as Schema.Option;
|
|
279
|
-
}
|
|
280
|
-
return item as unknown as Schema.Option;
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
Schema.extend('number', function (this: Schema, key: string, value: any) {
|
|
285
|
-
value = Schema.checkDefault(this, key, value);
|
|
286
|
-
return value;
|
|
287
|
-
});
|
|
288
|
-
Schema.extend('string', function (this: Schema, key: string, value: any) {
|
|
289
|
-
value = Schema.checkDefault(this, key, value);
|
|
290
|
-
return value;
|
|
291
|
-
});
|
|
292
|
-
Schema.extend('boolean', function (this: Schema, key: string, value: any) {
|
|
293
|
-
return Schema.checkDefault(this, key, value);
|
|
294
|
-
});
|
|
295
|
-
Schema.extend('dict', function (this: Schema, key: string, value: any) {
|
|
296
|
-
value = Schema.checkDefault(this, key, value, {});
|
|
297
|
-
return Object.fromEntries(
|
|
298
|
-
Object.entries(value).map(([k, schema]) => {
|
|
299
|
-
return [k, this.options.inner!(schema, `${key}.${k}`)];
|
|
300
|
-
}),
|
|
301
|
-
);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
Schema.extend('object', function (this: Schema, key: string, value: any) {
|
|
305
|
-
const getDefault = (schema: Schema) => {
|
|
306
|
-
const result = Object.create(null);
|
|
307
|
-
for (const key in schema.options.object) {
|
|
308
|
-
const propSchema = schema.options.object[key];
|
|
309
|
-
result[key] = propSchema.default !== undefined ? propSchema.default : undefined;
|
|
310
|
-
}
|
|
311
|
-
return result;
|
|
312
|
-
};
|
|
313
|
-
value = Schema.checkDefault(this, key, value, getDefault(this));
|
|
314
|
-
const result = Object.create(null);
|
|
315
|
-
for (const [k, val] of Object.entries(value)) {
|
|
316
|
-
const propSchema = this.options.object![k];
|
|
317
|
-
if (propSchema) {
|
|
318
|
-
// propSchema是一个可调用的Schema实例
|
|
319
|
-
result[k] = (propSchema as any)(val, `${key}.${k}`);
|
|
320
|
-
} else {
|
|
321
|
-
result[k] = val;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return result;
|
|
325
|
-
});
|
|
326
|
-
Schema.extend('list', function (this: Schema, key: string, value: any) {
|
|
327
|
-
value = Schema.checkDefault(this, key, value, []);
|
|
328
|
-
return value.map((item: any, index: number) => this.options.inner!(item, `${key}[${index}]`));
|
|
329
|
-
});
|
|
330
|
-
Schema.extend('regexp', function (this: Schema, key: string, value: any) {
|
|
331
|
-
value = Schema.checkDefault(this, key, value);
|
|
332
|
-
if (typeof value === 'string') {
|
|
333
|
-
return new RegExp(value);
|
|
334
|
-
}
|
|
335
|
-
return value;
|
|
336
|
-
});
|
|
337
|
-
Schema.extend('date', function (this: Schema, key: string, value: any) {
|
|
338
|
-
value = Schema.checkDefault(this, key, value);
|
|
339
|
-
return new Date(value);
|
|
340
|
-
});
|
|
341
|
-
Schema.extend('const', function (this: Schema, key: string, value: any) {
|
|
342
|
-
value = Schema.checkDefault(this, key, value);
|
|
343
|
-
if (value !== this.meta.default) {
|
|
344
|
-
throw new Error(`${key} const value not match`);
|
|
345
|
-
}
|
|
346
|
-
return value;
|
|
347
|
-
});
|
|
348
|
-
Schema.extend('tuple', function (this: Schema, key: string, value: any) {
|
|
349
|
-
value = Schema.checkDefault(this, key, value, []);
|
|
350
|
-
return value.map((item: any, index: number) => this.options.list);
|
|
351
|
-
});
|
|
352
|
-
Schema.extend('union', function (this: Schema, key: string, value: any) {
|
|
353
|
-
value = Schema.checkDefault(this, key, value);
|
|
354
|
-
for (const schema of this.options.list!) {
|
|
355
|
-
try {
|
|
356
|
-
return (schema as any)(value, key);
|
|
357
|
-
} catch { /* schema mismatch, try next variant */ }
|
|
358
|
-
}
|
|
359
|
-
throw new Error(`${key} union type not match`);
|
|
360
|
-
});
|
|
361
|
-
Schema.extend('intersect', function (this: Schema, key: string, value: any) {
|
|
362
|
-
value = Schema.checkDefault(this, key, value);
|
|
363
|
-
for (const schema of this.options.list!) {
|
|
364
|
-
try {
|
|
365
|
-
value = (schema as any)(value, key);
|
|
366
|
-
} catch (e) {
|
|
367
|
-
throw new Error(`${key} intersect type not match`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return value;
|
|
371
|
-
});
|
|
372
|
-
Schema.extend('any', function (this: Schema, key: string, value: any) {
|
|
373
|
-
return Schema.checkDefault(this, key, value);
|
|
374
|
-
});
|
package/src/utils.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 检查值是否为空
|
|
3
|
-
*/
|
|
4
|
-
export function isEmpty(value: any): boolean {
|
|
5
|
-
return value === null || value === undefined || value === '';
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 深度合并对象
|
|
10
|
-
*/
|
|
11
|
-
export function deepMerge<T>(target: T, source: Partial<T>): T {
|
|
12
|
-
if (!target || !source) return target;
|
|
13
|
-
|
|
14
|
-
const result = { ...target };
|
|
15
|
-
|
|
16
|
-
for (const key in source) {
|
|
17
|
-
const sourceValue = source[key];
|
|
18
|
-
const targetValue = result[key];
|
|
19
|
-
|
|
20
|
-
if (sourceValue !== undefined) {
|
|
21
|
-
if (typeof sourceValue === 'object' && sourceValue !== null &&
|
|
22
|
-
typeof targetValue === 'object' && targetValue !== null &&
|
|
23
|
-
!Array.isArray(sourceValue) && !Array.isArray(targetValue)) {
|
|
24
|
-
(result as any)[key] = deepMerge(targetValue, sourceValue);
|
|
25
|
-
} else {
|
|
26
|
-
(result as any)[key] = sourceValue;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return result;
|
|
32
|
-
}
|
package/tests/schema.test.ts
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll } from 'vitest'
|
|
2
|
-
import { Schema } from '../src/index'
|
|
3
|
-
|
|
4
|
-
describe('Schema', () => {
|
|
5
|
-
describe('Basic types', () => {
|
|
6
|
-
it('should create string schema', () => {
|
|
7
|
-
const schema = Schema.string()
|
|
8
|
-
expect(schema).toBeDefined()
|
|
9
|
-
expect(schema.meta.type).toBe('string')
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('should create number schema', () => {
|
|
13
|
-
const schema = Schema.number()
|
|
14
|
-
expect(schema).toBeDefined()
|
|
15
|
-
expect(schema.meta.type).toBe('number')
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('should create boolean schema', () => {
|
|
19
|
-
const schema = Schema.boolean()
|
|
20
|
-
expect(schema).toBeDefined()
|
|
21
|
-
expect(schema.meta.type).toBe('boolean')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('should create date schema', () => {
|
|
25
|
-
const schema = Schema.date()
|
|
26
|
-
expect(schema).toBeDefined()
|
|
27
|
-
expect(schema.meta.type).toBe('date')
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('should create regexp schema', () => {
|
|
31
|
-
const schema = Schema.regexp()
|
|
32
|
-
expect(schema).toBeDefined()
|
|
33
|
-
expect(schema.meta.type).toBe('regexp')
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe('Complex types', () => {
|
|
38
|
-
it('should create object schema', () => {
|
|
39
|
-
const schema = Schema.object({
|
|
40
|
-
name: Schema.string(),
|
|
41
|
-
age: Schema.number()
|
|
42
|
-
})
|
|
43
|
-
expect(schema).toBeDefined()
|
|
44
|
-
expect(schema.meta.type).toBe('object')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should create list schema', () => {
|
|
48
|
-
const schema = Schema.list(Schema.string())
|
|
49
|
-
expect(schema).toBeDefined()
|
|
50
|
-
expect(schema.meta.type).toBe('list')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('should create dict schema', () => {
|
|
54
|
-
const schema = Schema.dict(Schema.number())
|
|
55
|
-
expect(schema).toBeDefined()
|
|
56
|
-
expect(schema.meta.type).toBe('dict')
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('should create union schema', () => {
|
|
60
|
-
const schema = Schema.union(['option1', 'option2', 'option3'])
|
|
61
|
-
expect(schema).toBeDefined()
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
describe('Schema methods', () => {
|
|
66
|
-
it('should set description', () => {
|
|
67
|
-
const schema = Schema.string().description('Test description')
|
|
68
|
-
expect(schema.meta.description).toBe('Test description')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('should set default value', () => {
|
|
72
|
-
const schema = Schema.string().default('default value')
|
|
73
|
-
expect(schema.meta.default).toBe('default value')
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('should set required', () => {
|
|
77
|
-
const schema = Schema.string().required()
|
|
78
|
-
expect(schema.meta.required).toBe(true)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('should have meta property', () => {
|
|
82
|
-
const schema = Schema.string()
|
|
83
|
-
expect(schema.meta).toBeDefined()
|
|
84
|
-
expect(schema.meta.type).toBe('string')
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('should chain multiple methods', () => {
|
|
88
|
-
const schema = Schema.string()
|
|
89
|
-
.description('Name')
|
|
90
|
-
.default('John')
|
|
91
|
-
.required()
|
|
92
|
-
|
|
93
|
-
expect(schema.meta.description).toBe('Name')
|
|
94
|
-
expect(schema.meta.default).toBe('John')
|
|
95
|
-
expect(schema.meta.required).toBe(true)
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
describe('Const schema', () => {
|
|
100
|
-
it('should create const schema with string', () => {
|
|
101
|
-
const schema = Schema.const('constant')
|
|
102
|
-
expect(schema.meta.type).toBe('const')
|
|
103
|
-
expect(schema.meta.default).toBe('constant')
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('should create const schema with number', () => {
|
|
107
|
-
const schema = Schema.const(42)
|
|
108
|
-
expect(schema.meta.type).toBe('const')
|
|
109
|
-
expect(schema.meta.default).toBe(42)
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('should create const schema with object', () => {
|
|
113
|
-
const obj = { key: 'value' }
|
|
114
|
-
const schema = Schema.const(obj)
|
|
115
|
-
expect(schema.meta.type).toBe('const')
|
|
116
|
-
expect(schema.meta.default).toBe(obj)
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
describe('Schema properties', () => {
|
|
121
|
-
it('should have meta for string schema', () => {
|
|
122
|
-
const schema = Schema.string()
|
|
123
|
-
expect(schema.meta).toBeDefined()
|
|
124
|
-
expect(schema.meta.type).toBe('string')
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('should have meta for number schema', () => {
|
|
128
|
-
const schema = Schema.number()
|
|
129
|
-
expect(schema.meta).toBeDefined()
|
|
130
|
-
expect(schema.meta.type).toBe('number')
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('should have meta for boolean schema', () => {
|
|
134
|
-
const schema = Schema.boolean()
|
|
135
|
-
expect(schema.meta).toBeDefined()
|
|
136
|
-
expect(schema.meta.type).toBe('boolean')
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('should have options for object schema', () => {
|
|
140
|
-
const schema = Schema.object({
|
|
141
|
-
name: Schema.string(),
|
|
142
|
-
age: Schema.number()
|
|
143
|
-
})
|
|
144
|
-
expect(schema.options).toBeDefined()
|
|
145
|
-
expect(schema.options.object).toBeDefined()
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it('should have options for list schema', () => {
|
|
149
|
-
const schema = Schema.list(Schema.string())
|
|
150
|
-
expect(schema.options).toBeDefined()
|
|
151
|
-
expect(schema.options.inner).toBeDefined()
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
describe('Schema intersect', () => {
|
|
156
|
-
it('should create intersect schema', () => {
|
|
157
|
-
const schema1 = Schema.object({ name: Schema.string() })
|
|
158
|
-
const schema2 = Schema.object({ age: Schema.number() })
|
|
159
|
-
const intersect = Schema.intersect([schema1, schema2])
|
|
160
|
-
|
|
161
|
-
expect(intersect).toBeDefined()
|
|
162
|
-
expect(intersect.meta.type).toBe('intersect')
|
|
163
|
-
})
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
// ============================================================================
|
|
168
|
-
// Schema 工具函数补全
|
|
169
|
-
// ============================================================================
|
|
170
|
-
describe('Schema utils', () => {
|
|
171
|
-
let isEmpty: (v: any) => boolean;
|
|
172
|
-
let deepMerge: <T>(target: T, source: Partial<T>) => T;
|
|
173
|
-
|
|
174
|
-
beforeAll(async () => {
|
|
175
|
-
const utils = await import('../src/utils.js');
|
|
176
|
-
isEmpty = utils.isEmpty;
|
|
177
|
-
deepMerge = utils.deepMerge;
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe('isEmpty', () => {
|
|
181
|
-
it('null 应为空', () => {
|
|
182
|
-
expect(isEmpty(null)).toBe(true)
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('undefined 应为空', () => {
|
|
186
|
-
expect(isEmpty(undefined)).toBe(true)
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
it('空字符串应为空', () => {
|
|
190
|
-
expect(isEmpty('')).toBe(true)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('0 不应为空', () => {
|
|
194
|
-
expect(isEmpty(0)).toBe(false)
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('false 不应为空', () => {
|
|
198
|
-
expect(isEmpty(false)).toBe(false)
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it('非空字符串不应为空', () => {
|
|
202
|
-
expect(isEmpty('hello')).toBe(false)
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('空数组不应为空', () => {
|
|
206
|
-
expect(isEmpty([])).toBe(false)
|
|
207
|
-
})
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
describe('deepMerge', () => {
|
|
211
|
-
it('应合并简单对象', () => {
|
|
212
|
-
const target = { a: 1, b: 2 }
|
|
213
|
-
const source = { b: 3, c: 4 }
|
|
214
|
-
const result = deepMerge(target, source)
|
|
215
|
-
expect(result).toEqual({ a: 1, b: 3, c: 4 })
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
it('应深度合并嵌套对象', () => {
|
|
219
|
-
const target = { a: { x: 1, y: 2 }, b: 3 }
|
|
220
|
-
const source = { a: { y: 5, z: 6 } }
|
|
221
|
-
const result = deepMerge(target, source)
|
|
222
|
-
expect(result).toEqual({ a: { x: 1, y: 5, z: 6 }, b: 3 })
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
it('数组应直接覆盖而非合并', () => {
|
|
226
|
-
const target = { a: [1, 2] }
|
|
227
|
-
const source = { a: [3, 4, 5] }
|
|
228
|
-
const result = deepMerge(target, source)
|
|
229
|
-
expect(result).toEqual({ a: [3, 4, 5] })
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
it('undefined 值不应覆盖', () => {
|
|
233
|
-
const target = { a: 1, b: 2 }
|
|
234
|
-
const source = { a: undefined, b: 3 }
|
|
235
|
-
const result = deepMerge(target, source)
|
|
236
|
-
expect(result).toEqual({ a: 1, b: 3 })
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('null target 应返回 target', () => {
|
|
240
|
-
expect(deepMerge(null, { a: 1 })).toBeNull()
|
|
241
|
-
})
|
|
242
|
-
})
|
|
243
|
-
})
|
package/tsconfig.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"allowImportingTsExtensions": false,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"resolveJsonModule": true,
|
|
12
|
-
"isolatedModules": true,
|
|
13
|
-
"verbatimModuleSyntax": false,
|
|
14
|
-
"allowSyntheticDefaultImports": true,
|
|
15
|
-
"declaration": true,
|
|
16
|
-
"declarationMap": true,
|
|
17
|
-
"sourceMap": true,
|
|
18
|
-
"outDir": "./lib",
|
|
19
|
-
"rootDir": "./src"
|
|
20
|
-
},
|
|
21
|
-
"include": [
|
|
22
|
-
"src/**/*"
|
|
23
|
-
]
|
|
24
|
-
}
|