koatty_validation 1.4.0 → 1.6.0

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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-10 23:32:27
3
+ * @Date: 2025-10-23 01:25:03
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
@@ -10,8 +10,8 @@
10
10
  var helper = require('koatty_lib');
11
11
  require('reflect-metadata');
12
12
  var koatty_container = require('koatty_container');
13
- var classValidator = require('class-validator');
14
13
  var lruCache = require('lru-cache');
14
+ var classValidator = require('class-validator');
15
15
 
16
16
  function _interopNamespaceDefault(e) {
17
17
  var n = Object.create(null);
@@ -33,1418 +33,1496 @@ function _interopNamespaceDefault(e) {
33
33
  var helper__namespace = /*#__PURE__*/_interopNamespaceDefault(helper);
34
34
 
35
35
  /**
36
- * @ author: richen
37
- * @ copyright: Copyright (c) - <richenlin(at)gmail.com>
38
- * @ license: MIT
39
- * @ version: 2020-03-20 11:34:38
36
+ * koatty_validation 类型定义
37
+ * @author richen
38
+ * @copyright Copyright (c) - <richenlin(at)gmail.com>
39
+ * @license MIT
40
+ */
41
+ /**
42
+ * 参数类型键常量
40
43
  */
41
- // tslint:disable-next-line: no-import-side-effect
42
- // 参数类型键常量
43
44
  const PARAM_TYPE_KEY = 'PARAM_TYPE_KEY';
45
+
44
46
  /**
45
- * Set property as included in the process of transformation.
46
- *
47
- * @export
48
- * @param {Object} object
49
- * @param {(string | symbol)} propertyName
47
+ * 性能缓存模块 - 提供多层次缓存和性能监控
48
+ * @author richen
50
49
  */
51
- function setExpose(object, propertyName) {
52
- const types = Reflect.getMetadata("design:type", object, propertyName);
53
- if (types) {
54
- const originMap = koatty_container.getOriginMetadata(PARAM_TYPE_KEY, object);
55
- originMap.set(propertyName, types.name);
56
- }
57
- }
58
50
  /**
59
- * plain object convert to class instance
60
- *
61
- * @export
62
- * @param {*} clazz
63
- * @param {*} data
64
- * @param {boolean} [convert=false]
65
- * @returns
51
+ * 元数据缓存
66
52
  */
67
- function plainToClass(clazz, data, convert = false) {
68
- if (helper__namespace.isClass(clazz)) {
69
- if (!helper__namespace.isObject(data)) {
70
- data = {};
71
- }
72
- if (data instanceof clazz) {
73
- return data;
53
+ class MetadataCache {
54
+ constructor() {
55
+ this.cache = new WeakMap();
56
+ }
57
+ static getInstance() {
58
+ if (!MetadataCache.instance) {
59
+ MetadataCache.instance = new MetadataCache();
74
60
  }
75
- return assignDtoParams(clazz, data, convert);
61
+ return MetadataCache.instance;
76
62
  }
77
- return data;
78
- }
79
- /**
80
- * assign dto params
81
- * @param clazz
82
- * @param data
83
- * @param convert
84
- * @returns
85
- */
86
- function assignDtoParams(clazz, data, convert = false) {
87
- const cls = Reflect.construct(clazz, []);
88
- if (convert) {
89
- const metaData = getDtoParamsMeta(clazz, cls);
90
- for (const [key, type] of metaData) {
91
- if (key && data[key] !== undefined) {
92
- cls[key] = convertParamsType(data[key], type);
93
- }
63
+ /**
64
+ * 获取类的元数据缓存
65
+ */
66
+ getClassCache(target) {
67
+ if (!this.cache.has(target)) {
68
+ this.cache.set(target, new Map());
94
69
  }
70
+ return this.cache.get(target);
95
71
  }
96
- else {
97
- for (const key in cls) {
98
- if (Object.prototype.hasOwnProperty.call(data, key) &&
99
- data[key] !== undefined) {
100
- cls[key] = data[key];
101
- }
72
+ /**
73
+ * 缓存元数据
74
+ */
75
+ setMetadata(target, key, value) {
76
+ const classCache = this.getClassCache(target);
77
+ classCache.set(key, value);
78
+ }
79
+ /**
80
+ * 获取缓存的元数据
81
+ */
82
+ getMetadata(target, key) {
83
+ const classCache = this.getClassCache(target);
84
+ return classCache.get(key);
85
+ }
86
+ /**
87
+ * 检查是否已缓存
88
+ */
89
+ hasMetadata(target, key) {
90
+ const classCache = this.getClassCache(target);
91
+ return classCache.has(key);
92
+ }
93
+ /**
94
+ * 清空指定类的缓存
95
+ */
96
+ clearClassCache(target) {
97
+ if (this.cache.has(target)) {
98
+ this.cache.delete(target);
102
99
  }
103
100
  }
104
- return cls;
105
101
  }
106
102
  /**
107
- * get class prototype type def.
108
- * @param clazz
109
- * @param cls
110
- * @returns
103
+ * 验证结果缓存
111
104
  */
112
- function getDtoParamsMeta(clazz, cls) {
113
- // Non-own properties are inherited from the prototype chain,
114
- // ensure that properties are not polluted
115
- if (!Object.prototype.hasOwnProperty.call(cls, "_typeDef") &&
116
- ("_typeDef" in cls)) {
117
- return cls._typeDef;
105
+ class ValidationCache {
106
+ constructor(options) {
107
+ this.hits = 0;
108
+ this.misses = 0;
109
+ this.cache = new lruCache.LRUCache({
110
+ max: (options === null || options === void 0 ? void 0 : options.max) || 5000,
111
+ ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 10, // 10分钟
112
+ allowStale: (options === null || options === void 0 ? void 0 : options.allowStale) || false,
113
+ updateAgeOnGet: (options === null || options === void 0 ? void 0 : options.updateAgeOnGet) || true,
114
+ });
118
115
  }
119
- const typeDef = koatty_container.getOriginMetadata(PARAM_TYPE_KEY, clazz);
120
- Reflect.defineProperty(clazz.prototype, "_typeDef", {
121
- enumerable: true,
122
- configurable: false,
123
- writable: false,
124
- value: typeDef,
125
- });
126
- return typeDef;
127
- }
128
- /**
129
- * convertDtoParamsType
130
- *
131
- * @param {*} clazz
132
- * @param {*} cls
133
- * @returns {*} cls
134
- */
135
- function convertDtoParamsType(clazz, cls) {
136
- if (Object.prototype.hasOwnProperty.call(cls, "_typeDef")) {
137
- for (const key in cls) {
138
- if (Object.prototype.hasOwnProperty.call(cls._typeDef, key) &&
139
- cls[key] !== undefined) {
140
- cls[key] = convertParamsType(cls[key], cls._typeDef[key]);
141
- }
116
+ static getInstance(options) {
117
+ if (!ValidationCache.instance) {
118
+ ValidationCache.instance = new ValidationCache(options);
142
119
  }
120
+ return ValidationCache.instance;
143
121
  }
144
- else {
145
- const originMap = koatty_container.getOriginMetadata(PARAM_TYPE_KEY, clazz);
146
- for (const [key, type] of originMap) {
147
- if (key && cls[key] !== undefined) {
148
- cls[key] = convertParamsType(cls[key], type);
149
- }
122
+ /**
123
+ * 生成缓存键
124
+ */
125
+ generateKey(validator, value, ...args) {
126
+ const valueStr = this.serializeValue(value);
127
+ const argsStr = args.length > 0 ? JSON.stringify(args) : '';
128
+ return `${validator}:${valueStr}:${argsStr}`;
129
+ }
130
+ /**
131
+ * 序列化值用于缓存键
132
+ */
133
+ serializeValue(value) {
134
+ if (value === null)
135
+ return 'null';
136
+ if (value === undefined)
137
+ return 'undefined';
138
+ if (typeof value === 'string')
139
+ return `s:${value}`;
140
+ if (typeof value === 'number')
141
+ return `n:${value}`;
142
+ if (typeof value === 'boolean')
143
+ return `b:${value}`;
144
+ if (Array.isArray(value))
145
+ return `a:${JSON.stringify(value)}`;
146
+ if (typeof value === 'object')
147
+ return `o:${JSON.stringify(value)}`;
148
+ return String(value);
149
+ }
150
+ /**
151
+ * 获取缓存的验证结果
152
+ */
153
+ get(validator, value, ...args) {
154
+ const key = this.generateKey(validator, value, ...args);
155
+ const result = this.cache.get(key);
156
+ // Track cache hits and misses
157
+ if (result !== undefined) {
158
+ this.hits++;
159
+ }
160
+ else {
161
+ this.misses++;
162
+ }
163
+ return result;
164
+ }
165
+ /**
166
+ * 缓存验证结果
167
+ */
168
+ set(validator, value, result, ...args) {
169
+ const key = this.generateKey(validator, value, ...args);
170
+ this.cache.set(key, result);
171
+ }
172
+ /**
173
+ * 检查是否存在缓存
174
+ */
175
+ has(validator, value, ...args) {
176
+ const key = this.generateKey(validator, value, ...args);
177
+ return this.cache.has(key);
178
+ }
179
+ /**
180
+ * 删除特定缓存
181
+ */
182
+ delete(validator, value, ...args) {
183
+ const key = this.generateKey(validator, value, ...args);
184
+ return this.cache.delete(key);
185
+ }
186
+ /**
187
+ * 清空缓存
188
+ */
189
+ clear() {
190
+ this.cache.clear();
191
+ this.hits = 0;
192
+ this.misses = 0;
193
+ }
194
+ /**
195
+ * 获取缓存统计
196
+ */
197
+ getStats() {
198
+ const totalRequests = this.hits + this.misses;
199
+ const hitRate = totalRequests > 0 ? this.hits / totalRequests : 0;
200
+ return {
201
+ size: this.cache.size,
202
+ max: this.cache.max,
203
+ calculatedSize: this.cache.calculatedSize,
204
+ keyCount: this.cache.size,
205
+ hits: this.hits,
206
+ misses: this.misses,
207
+ hitRate: Math.round(hitRate * 10000) / 100, // Percentage with 2 decimal places
208
+ totalRequests,
209
+ };
210
+ }
211
+ /**
212
+ * 设置缓存TTL
213
+ */
214
+ setTTL(validator, value, ttl, ...args) {
215
+ const key = this.generateKey(validator, value, ...args);
216
+ const existingValue = this.cache.get(key);
217
+ if (existingValue !== undefined) {
218
+ this.cache.set(key, existingValue, { ttl });
150
219
  }
151
220
  }
152
- return cls;
153
221
  }
154
222
  /**
155
- * 绑定参数类型转换
156
- *
157
- * @param {*} param
158
- * @param {string} type
159
- * @returns {*}
223
+ * 正则表达式缓存
160
224
  */
161
- function convertParamsType(param, type) {
162
- try {
163
- switch (type) {
164
- case "Number":
165
- case "number":
166
- if (helper__namespace.isNaN(param)) {
167
- return NaN;
168
- }
169
- if (helper__namespace.isNumber(param)) {
170
- return param;
171
- }
172
- if (helper__namespace.isNumberString(param)) {
173
- return helper__namespace.toNumber(param);
174
- }
175
- return NaN;
176
- case "Boolean":
177
- case "boolean":
178
- return !!param;
179
- case "Array":
180
- case "array":
181
- case "Tuple":
182
- case "tuple":
183
- if (helper__namespace.isArray(param)) {
184
- return param;
185
- }
186
- return helper__namespace.toArray(param);
187
- case "String":
188
- case "string":
189
- if (helper__namespace.isString(param)) {
190
- return param;
191
- }
192
- return helper__namespace.toString(param);
193
- case "Null":
194
- case "null":
195
- return null;
196
- case "Undefined":
197
- case "undefined":
198
- return undefined;
199
- case "Bigint":
200
- case "bigint":
201
- if (typeof param === 'bigint') {
202
- return param;
203
- }
204
- return BigInt(param);
205
- // case "object":
206
- // case "enum":
207
- default: //any
208
- return param;
209
- }
210
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
225
+ class RegexCache {
226
+ constructor(options) {
227
+ this.cache = new lruCache.LRUCache({
228
+ max: (options === null || options === void 0 ? void 0 : options.max) || 200,
229
+ ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 30, // 30分钟
230
+ allowStale: (options === null || options === void 0 ? void 0 : options.allowStale) || false,
231
+ updateAgeOnGet: (options === null || options === void 0 ? void 0 : options.updateAgeOnGet) || true,
232
+ });
211
233
  }
212
- catch (_err) {
213
- return param;
234
+ static getInstance(options) {
235
+ if (!RegexCache.instance) {
236
+ RegexCache.instance = new RegexCache(options);
237
+ }
238
+ return RegexCache.instance;
214
239
  }
215
- }
216
- /**
217
- * Check the base types.
218
- *
219
- * @param {*} value
220
- * @param {string} type
221
- * @returns {*}
222
- */
223
- function checkParamsType(value, type) {
224
- switch (type) {
225
- case "Number":
226
- case "number":
227
- if (!helper__namespace.isNumber(value) || helper__namespace.isNaN(value)) {
228
- return false;
229
- }
230
- return true;
231
- case "Boolean":
232
- case "boolean":
233
- if (!helper__namespace.isBoolean(value)) {
234
- return false;
235
- }
236
- return true;
237
- case "Array":
238
- case "array":
239
- case "Tuple":
240
- case "tuple":
241
- if (!helper__namespace.isArray(value)) {
242
- return false;
243
- }
244
- return true;
245
- case "String":
246
- case "string":
247
- if (!helper__namespace.isString(value)) {
248
- return false;
249
- }
250
- return true;
251
- case "Object":
252
- case "object":
253
- case "Enum":
254
- case "enum":
255
- if (helper__namespace.isTrueEmpty(value)) {
256
- return false;
257
- }
258
- return true;
259
- case "Null":
260
- case "null":
261
- if (!helper__namespace.isNull(value)) {
262
- return false;
263
- }
264
- return true;
265
- case "Undefined":
266
- case "undefined":
267
- if (!helper__namespace.isUndefined(value)) {
268
- return false;
240
+ /**
241
+ * 获取缓存的正则表达式
242
+ */
243
+ get(pattern, flags) {
244
+ const key = flags ? `${pattern}:::${flags}` : pattern;
245
+ let regex = this.cache.get(key);
246
+ if (!regex) {
247
+ try {
248
+ regex = new RegExp(pattern, flags);
249
+ this.cache.set(key, regex);
250
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
269
251
  }
270
- return true;
271
- case "Bigint":
272
- case "bigint":
273
- if (typeof value !== 'bigint') {
274
- return false;
252
+ catch (_error) {
253
+ // 如果正则表达式无效,抛出错误
254
+ throw new Error(`Invalid regex pattern: ${pattern}`);
275
255
  }
276
- return true;
277
- default: //any
278
- return true;
279
- }
280
- }
281
- /**
282
- * Checks if value is a chinese name.
283
- *
284
- * @param {string} value
285
- * @returns {boolean}
286
- */
287
- function cnName(value) {
288
- const reg = /^([a-zA-Z0-9\u4e00-\u9fa5\·]{1,10})$/;
289
- return reg.test(value);
290
- }
291
- /**
292
- * Checks if value is a idCard number.
293
- *
294
- * @param {string} value
295
- * @returns
296
- */
297
- function idNumber(value) {
298
- if (/^\d{15}$/.test(value)) {
299
- return true;
300
- }
301
- if ((/^\d{17}[0-9X]$/).test(value)) {
302
- const vs = '1,0,x,9,8,7,6,5,4,3,2'.split(',');
303
- const ps = '7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2'.split(',');
304
- const ss = value.toLowerCase().split('');
305
- let r = 0;
306
- for (let i = 0; i < 17; i++) {
307
- r += ps[i] * ss[i];
308
256
  }
309
- const isOk = (vs[r % 11] === ss[17]);
310
- return isOk;
311
- }
312
- return false;
313
- }
314
- /**
315
- * Checks if value is a mobile phone number.
316
- *
317
- * @param {string} value
318
- * @returns {boolean}
319
- */
320
- function mobile(value) {
321
- const reg = /^(13|14|15|16|17|18|19)\d{9}$/;
322
- return reg.test(value);
323
- }
324
- /**
325
- * Checks if value is a zipCode.
326
- *
327
- * @param {string} value
328
- * @returns {boolean}
329
- */
330
- function zipCode(value) {
331
- const reg = /^\d{6}$/;
332
- return reg.test(value);
333
- }
334
- /**
335
- * Checks if value is a plateNumber.
336
- *
337
- * @param {string} value
338
- * @returns {boolean}
339
- */
340
- function plateNumber(value) {
341
- // let reg = new RegExp('^(([\u4e00-\u9fa5][a-zA-Z]|[\u4e00-\u9fa5]{2}\d{2}|[\u4e00-\u9fa5]{2}[a-zA-Z])[-]?|([wW][Jj][\u4e00-\u9fa5]{1}[-]?)|([a-zA-Z]{2}))([A-Za-z0-9]{5}|[DdFf][A-HJ-NP-Za-hj-np-z0-9][0-9]{4}|[0-9]{5}[DdFf])$');
342
- // let xReg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
343
- const xReg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
344
- // let cReg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
345
- const cReg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
346
- if (value.length === 7) {
347
- return cReg.test(value);
348
- }
349
- else {
350
- //新能源车牌
351
- return xReg.test(value);
257
+ return regex;
352
258
  }
353
- }
354
-
355
- /*
356
- * @Description:
357
- * @Usage:
358
- * @Author: richen
359
- * @Date: 2021-11-25 10:47:04
360
- * @LastEditTime: 2024-01-03 14:32:49
361
- */
362
- // constant
363
- const PARAM_RULE_KEY = 'PARAM_RULE_KEY';
364
- const PARAM_CHECK_KEY = 'PARAM_CHECK_KEY';
365
- const ENABLE_VALIDATED = "ENABLE_VALIDATED";
366
- /**
367
- * paramterTypes
368
- *
369
- * @export
370
- * @enum {number}
371
- */
372
- exports.paramterTypes = void 0;
373
- (function (paramterTypes) {
374
- paramterTypes[paramterTypes["Number"] = 0] = "Number";
375
- paramterTypes[paramterTypes["number"] = 1] = "number";
376
- paramterTypes[paramterTypes["String"] = 2] = "String";
377
- paramterTypes[paramterTypes["string"] = 3] = "string";
378
- paramterTypes[paramterTypes["Boolean"] = 4] = "Boolean";
379
- paramterTypes[paramterTypes["boolean"] = 5] = "boolean";
380
- paramterTypes[paramterTypes["Array"] = 6] = "Array";
381
- paramterTypes[paramterTypes["array"] = 7] = "array";
382
- paramterTypes[paramterTypes["Tuple"] = 8] = "Tuple";
383
- paramterTypes[paramterTypes["tuple"] = 9] = "tuple";
384
- paramterTypes[paramterTypes["Object"] = 10] = "Object";
385
- paramterTypes[paramterTypes["object"] = 11] = "object";
386
- paramterTypes[paramterTypes["Enum"] = 12] = "Enum";
387
- paramterTypes[paramterTypes["enum"] = 13] = "enum";
388
- paramterTypes[paramterTypes["Bigint"] = 14] = "Bigint";
389
- paramterTypes[paramterTypes["bigint"] = 15] = "bigint";
390
- paramterTypes[paramterTypes["Null"] = 16] = "Null";
391
- paramterTypes[paramterTypes["null"] = 17] = "null";
392
- paramterTypes[paramterTypes["Undefined"] = 18] = "Undefined";
393
- paramterTypes[paramterTypes["undefined"] = 19] = "undefined";
394
- })(exports.paramterTypes || (exports.paramterTypes = {}));
395
- class ValidateClass {
396
- constructor() {
259
+ /**
260
+ * 预编译常用正则表达式
261
+ */
262
+ precompile(patterns) {
263
+ patterns.forEach(({ pattern, flags }) => {
264
+ try {
265
+ this.get(pattern, flags);
266
+ }
267
+ catch (error) {
268
+ console.warn(`Failed to precompile regex: ${pattern}`, error);
269
+ }
270
+ });
397
271
  }
398
272
  /**
399
- *
400
- *
401
- * @static
402
- * @returns
403
- * @memberof ValidateUtil
273
+ * 获取缓存统计
404
274
  */
405
- static getInstance() {
406
- return this.instance || (this.instance = new ValidateClass());
275
+ getStats() {
276
+ return {
277
+ size: this.cache.size,
278
+ max: this.cache.max,
279
+ calculatedSize: this.cache.calculatedSize,
280
+ };
407
281
  }
408
282
  /**
409
- * validated data vs dto class
410
- *
411
- * @param {*} Clazz
412
- * @param {*} data
413
- * @param {boolean} [convert=false] auto convert parameters type
414
- * @returns {Promise<any>}
415
- * @memberof ValidateClass
283
+ * 清空缓存
416
284
  */
417
- async valid(Clazz, data, convert = false) {
418
- let obj = {};
419
- if (data instanceof Clazz) {
420
- obj = data;
421
- }
422
- else {
423
- obj = plainToClass(Clazz, data, convert);
424
- }
425
- let errors = [];
426
- if (convert) {
427
- errors = await classValidator.validate(obj);
428
- }
429
- else {
430
- errors = await classValidator.validate(obj, { skipMissingProperties: true });
431
- }
432
- if (errors.length > 0) {
433
- throw new Error(Object.values(errors[0].constraints)[0]);
434
- }
435
- return obj;
285
+ clear() {
286
+ this.cache.clear();
436
287
  }
437
288
  }
438
289
  /**
439
- * ClassValidator for manual
440
- */
441
- const ClassValidator = ValidateClass.getInstance();
442
- /**
443
- * Validator Functions
290
+ * 性能监控
444
291
  */
445
- const ValidFuncs = {
446
- /**
447
- * Checks value is not empty, undefined, null, '', NaN, [], {} and any empty string(including spaces,
448
- * tabs, formfeeds, etc.), returns false
449
- */
450
- IsNotEmpty: (value) => {
451
- return !helper__namespace.isEmpty(value);
452
- },
453
- /**
454
- * Checks if a given value is a real date.
455
- */
456
- IsDate: (value) => {
457
- return helper__namespace.isDate(value);
458
- },
459
- /**
460
- * Checks if the string is an email. If given value is not a string, then it returns false.
461
- */
462
- IsEmail: (value, options) => {
463
- return classValidator.isEmail(value, options);
464
- },
465
- /**
466
- * Checks if the string is an IP (version 4 or 6). If given value is not a string, then it returns false.
467
- */
468
- IsIP: (value, version) => {
469
- return classValidator.isIP(value, version);
470
- },
471
- /**
472
- * Checks if the string is a valid phone number.
473
- * @param value — the potential phone number string to test
474
- * @param region 2 characters uppercase country code (e.g. DE, US, CH). If users must enter the intl.
475
- * prefix (e.g. +41), then you may pass "ZZ" or null as region.
476
- * See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github]
477
- * {@link https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33}
478
- */
479
- IsPhoneNumber: (value, region) => {
480
- return classValidator.isPhoneNumber(value, region);
481
- },
482
- /**
483
- * Checks if the string is an url. If given value is not a string, then it returns false.
484
- */
485
- IsUrl: (value, options) => {
486
- return classValidator.isURL(value, options);
487
- },
488
- /**
489
- * check if the string is a hash of type algorithm. Algorithm is one of
490
- * ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b']
491
- */
492
- IsHash: (value, algorithm) => {
493
- return classValidator.isHash(value, algorithm);
494
- },
495
- /**
496
- * Checks if value is a chinese name.
497
- */
498
- IsCnName: (value) => {
499
- if (!helper__namespace.isString(value)) {
500
- return false;
501
- }
502
- return cnName(value);
503
- },
504
- /**
505
- * Checks if value is a idcard number.
506
- */
507
- IsIdNumber: (value) => {
508
- if (!helper__namespace.isString(value)) {
509
- return false;
292
+ class PerformanceMonitor {
293
+ constructor() {
294
+ this.metrics = new Map();
295
+ }
296
+ static getInstance() {
297
+ if (!PerformanceMonitor.instance) {
298
+ PerformanceMonitor.instance = new PerformanceMonitor();
510
299
  }
511
- return idNumber(value);
512
- },
300
+ return PerformanceMonitor.instance;
301
+ }
513
302
  /**
514
- * Checks if value is a zipCode.
303
+ * 开始计时
515
304
  */
516
- IsZipCode: (value) => {
517
- if (!helper__namespace.isString(value)) {
518
- return false;
519
- }
520
- return zipCode(value);
521
- },
305
+ startTimer(name) {
306
+ const start = performance.now();
307
+ return () => {
308
+ const duration = performance.now() - start;
309
+ this.recordMetric(name, duration);
310
+ };
311
+ }
522
312
  /**
523
- * Checks if value is a mobile phone number.
313
+ * 记录性能指标
524
314
  */
525
- IsMobile: (value) => {
526
- if (!helper__namespace.isString(value)) {
527
- return false;
315
+ recordMetric(name, duration) {
316
+ if (!this.metrics.has(name)) {
317
+ this.metrics.set(name, {
318
+ count: 0,
319
+ totalTime: 0,
320
+ avgTime: 0,
321
+ maxTime: 0,
322
+ minTime: Infinity,
323
+ lastExecutionTime: new Date(),
324
+ });
528
325
  }
529
- return mobile(value);
530
- },
326
+ const metric = this.metrics.get(name);
327
+ metric.count++;
328
+ metric.totalTime += duration;
329
+ metric.avgTime = metric.totalTime / metric.count;
330
+ metric.maxTime = Math.max(metric.maxTime, duration);
331
+ metric.minTime = Math.min(metric.minTime, duration);
332
+ metric.lastExecutionTime = new Date();
333
+ }
531
334
  /**
532
- * Checks if value is a plateNumber.
335
+ * 获取性能报告
533
336
  */
534
- IsPlateNumber: (value) => {
535
- if (!helper__namespace.isString(value)) {
536
- return false;
337
+ getReport() {
338
+ const report = {};
339
+ for (const [name, metric] of this.metrics) {
340
+ report[name] = {
341
+ ...metric,
342
+ minTime: metric.minTime === Infinity ? 0 : metric.minTime,
343
+ avgTimeFormatted: `${metric.avgTime.toFixed(2)}ms`,
344
+ totalTimeFormatted: `${metric.totalTime.toFixed(2)}ms`,
345
+ };
537
346
  }
538
- return plateNumber(value);
539
- },
540
- /**
541
- * Checks if value matches ("===") the comparison.
542
- */
543
- Equals: (value, comparison) => {
544
- return classValidator.equals(value, comparison);
545
- },
546
- /**
547
- * Checks if value does not match ("!==") the comparison.
548
- */
549
- NotEquals: (value, comparison) => {
550
- return classValidator.notEquals(value, comparison);
551
- },
552
- /**
553
- * Checks if the string contains the seed. If given value is not a string, then it returns false.
554
- */
555
- Contains: (value, seed) => {
556
- return classValidator.contains(value, seed);
557
- },
558
- /**
559
- * Checks if given value is in a array of allowed values.
560
- */
561
- IsIn: (value, possibleValues) => {
562
- return classValidator.isIn(value, possibleValues);
563
- },
564
- /**
565
- * Checks if given value not in a array of allowed values.
566
- */
567
- IsNotIn: (value, possibleValues) => {
568
- return classValidator.isNotIn(value, possibleValues);
569
- },
570
- /**
571
- * Checks if the first number is greater than or equal to the second.
572
- */
573
- Gt: (num, min) => {
574
- return helper__namespace.toNumber(num) > min;
575
- },
347
+ return report;
348
+ }
576
349
  /**
577
- * Checks if the first number is less than or equal to the second.
350
+ * 获取热点分析(执行时间最长的操作)
578
351
  */
579
- Lt: (num, max) => {
580
- return helper__namespace.toNumber(num) < max;
581
- },
352
+ getHotspots(limit = 10) {
353
+ return Array.from(this.metrics.entries())
354
+ .map(([name, metric]) => ({
355
+ name,
356
+ avgTime: metric.avgTime,
357
+ count: metric.count,
358
+ }))
359
+ .sort((a, b) => b.avgTime - a.avgTime)
360
+ .slice(0, limit);
361
+ }
582
362
  /**
583
- * Checks if the first number is greater than or equal to the second.
363
+ * 清空指标
584
364
  */
585
- Gte: (num, min) => {
586
- return helper__namespace.toNumber(num) >= min;
587
- },
365
+ clear() {
366
+ this.metrics.clear();
367
+ }
588
368
  /**
589
- * Checks if the first number is less than or equal to the second.
369
+ * 导出性能数据为CSV格式
590
370
  */
591
- Lte: (num, max) => {
592
- return helper__namespace.toNumber(num) <= max;
593
- },
594
- };
371
+ exportToCSV() {
372
+ const headers = ['Name', 'Count', 'Total Time (ms)', 'Avg Time (ms)', 'Max Time (ms)', 'Min Time (ms)', 'Last Execution'];
373
+ const rows = [headers.join(',')];
374
+ for (const [name, metric] of this.metrics) {
375
+ const row = [
376
+ name,
377
+ metric.count.toString(),
378
+ metric.totalTime.toFixed(2),
379
+ metric.avgTime.toFixed(2),
380
+ metric.maxTime.toFixed(2),
381
+ (metric.minTime === Infinity ? 0 : metric.minTime).toFixed(2),
382
+ metric.lastExecutionTime.toISOString(),
383
+ ];
384
+ rows.push(row.join(','));
385
+ }
386
+ return rows.join('\n');
387
+ }
388
+ }
389
+ // 导出单例实例
390
+ const metadataCache = MetadataCache.getInstance();
391
+ const validationCache = ValidationCache.getInstance();
392
+ const regexCache = RegexCache.getInstance();
393
+ const performanceMonitor = PerformanceMonitor.getInstance();
595
394
  /**
596
- * Use functions or built-in rules for validation.
597
- *
598
- * @export
599
- * @param {ValidRules} rule
600
- * @param {unknown} value
601
- * @param {(string | ValidOtpions)} [options]
602
- * @returns {*}
395
+ * 缓存装饰器 - 用于缓存验证函数结果
603
396
  */
604
- /* eslint-disable @typescript-eslint/no-unused-vars */
605
- const FunctionValidator = {
606
- IsNotEmpty: function (_value, _options) {
607
- throw new Error("Function not implemented.");
608
- },
609
- IsDate: function (_value, _options) {
610
- throw new Error("Function not implemented.");
611
- },
612
- IsEmail: function (_value, _options) {
613
- throw new Error("Function not implemented.");
614
- },
615
- IsIP: function (_value, _options) {
616
- throw new Error("Function not implemented.");
617
- },
618
- IsPhoneNumber: function (_value, _options) {
619
- throw new Error("Function not implemented.");
620
- },
621
- IsUrl: function (_value, _options) {
622
- throw new Error("Function not implemented.");
623
- },
624
- IsHash: function (_value, _options) {
625
- throw new Error("Function not implemented.");
626
- },
627
- IsCnName: function (_value, _options) {
628
- throw new Error("Function not implemented.");
629
- },
630
- IsIdNumber: function (_value, _options) {
631
- throw new Error("Function not implemented.");
632
- },
633
- IsZipCode: function (_value, _options) {
634
- throw new Error("Function not implemented.");
635
- },
636
- IsMobile: function (_value, _options) {
637
- throw new Error("Function not implemented.");
638
- },
639
- IsPlateNumber: function (_value, _options) {
640
- throw new Error("Function not implemented.");
641
- },
642
- Equals: function (_value, _options) {
643
- throw new Error("Function not implemented.");
644
- },
645
- NotEquals: function (_value, _options) {
646
- throw new Error("Function not implemented.");
647
- },
648
- Contains: function (_value, _options) {
649
- throw new Error("Function not implemented.");
650
- },
651
- IsIn: function (_value, _options) {
652
- throw new Error("Function not implemented.");
653
- },
654
- IsNotIn: function (_value, _options) {
655
- throw new Error("Function not implemented.");
656
- },
657
- Gt: function (_value, _options) {
658
- throw new Error("Function not implemented.");
659
- },
660
- Lt: function (_value, _options) {
661
- throw new Error("Function not implemented.");
662
- },
663
- Gte: function (_value, _options) {
664
- throw new Error("Function not implemented.");
665
- },
666
- Lte: function (_value, _options) {
667
- throw new Error("Function not implemented.");
668
- }
669
- };
670
- /* eslint-enable @typescript-eslint/no-unused-vars */
671
- Object.keys(ValidFuncs).forEach((key) => {
672
- FunctionValidator[key] = (value, options) => {
673
- let validOptions;
674
- if (typeof options === 'string') {
675
- validOptions = { message: options, value: null };
676
- }
677
- else {
678
- validOptions = options || { message: `ValidatorError: invalid arguments.`, value: null };
679
- }
680
- if (!ValidFuncs[key](value, validOptions.value)) {
681
- throw new Error(validOptions.message || `ValidatorError: invalid arguments.`);
682
- }
397
+ function cached(validator, ttl) {
398
+ return function (target, propertyName, descriptor) {
399
+ const originalMethod = descriptor.value;
400
+ descriptor.value = function (...args) {
401
+ const value = args[0];
402
+ const additionalArgs = args.slice(1);
403
+ // 尝试从缓存获取结果
404
+ const cachedResult = validationCache.get(validator, value, ...additionalArgs);
405
+ if (cachedResult !== undefined) {
406
+ return cachedResult;
407
+ }
408
+ // 执行验证并缓存结果
409
+ const endTimer = performanceMonitor.startTimer(validator);
410
+ try {
411
+ const result = originalMethod.apply(this, args);
412
+ validationCache.set(validator, value, result, ...additionalArgs);
413
+ // 如果指定了TTL,设置过期时间
414
+ if (ttl && ttl > 0) {
415
+ validationCache.setTTL(validator, value, ttl, ...additionalArgs);
416
+ }
417
+ return result;
418
+ }
419
+ finally {
420
+ endTimer();
421
+ }
422
+ };
423
+ return descriptor;
424
+ };
425
+ }
426
+ /**
427
+ * 获取所有缓存统计信息
428
+ */
429
+ function getAllCacheStats() {
430
+ return {
431
+ validation: validationCache.getStats(),
432
+ regex: regexCache.getStats(),
433
+ performance: performanceMonitor.getReport(),
434
+ hotspots: performanceMonitor.getHotspots(),
683
435
  };
684
- });
436
+ }
437
+ /**
438
+ * 预热缓存 - 预编译常用正则表达式
439
+ */
440
+ function warmupCaches() {
441
+ // 预编译中文验证相关的正则表达式
442
+ const commonPatterns = [
443
+ { pattern: '^[\u4e00-\u9fa5]{2,8}$' }, // 中文姓名
444
+ { pattern: '^1[3-9]\\d{9}$' }, // 手机号
445
+ { pattern: '^\\d{6}$' }, // 邮政编码
446
+ { pattern: '^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼]' }, // 车牌号开头
447
+ ];
448
+ regexCache.precompile(commonPatterns);
449
+ }
450
+ /**
451
+ * 清空所有缓存
452
+ */
453
+ function clearAllCaches() {
454
+ validationCache.clear();
455
+ regexCache.clear();
456
+ performanceMonitor.clear();
457
+ }
458
+ /**
459
+ * 配置缓存设置
460
+ */
461
+ function configureCaches(options) {
462
+ if (options.validation) {
463
+ // 重新创建验证缓存实例
464
+ ValidationCache.instance = new ValidationCache(options.validation);
465
+ }
466
+ if (options.regex) {
467
+ // 重新创建正则缓存实例
468
+ RegexCache.instance = new RegexCache(options.regex);
469
+ }
470
+ }
685
471
 
686
472
  /**
687
- * 装饰器工厂 - 消除装饰器代码重复
688
- * @author richen
473
+ * @ author: richen
474
+ * @ copyright: Copyright (c) - <richenlin(at)gmail.com>
475
+ * @ license: MIT
476
+ * @ version: 2020-03-20 11:34:38
689
477
  */
478
+ // tslint:disable-next-line: no-import-side-effect
690
479
  /**
691
- * 创建验证装饰器的工厂函数
692
- * @param options 装饰器配置选项
693
- * @returns 装饰器工厂函数
480
+ * Set property as included in the process of transformation.
481
+ *
482
+ * @export
483
+ * @param {Object} object
484
+ * @param {(string | symbol)} propertyName
694
485
  */
695
- function createValidationDecorator(options) {
696
- const { name, validator, defaultMessage, requiresValue = false } = options;
697
- return function decoratorFactory(...args) {
698
- // 处理参数:最后一个参数是ValidationOptions,前面是验证函数的参数
699
- const validationOptions = args[args.length - 1];
700
- const validatorArgs = requiresValue ? args.slice(0, -1) : [];
701
- return function propertyDecorator(object, propertyName) {
702
- // 设置属性为可导出
703
- setExpose(object, propertyName);
704
- // 注册验证装饰器
705
- classValidator.registerDecorator({
706
- name,
707
- target: object.constructor,
708
- propertyName,
709
- options: validationOptions,
710
- constraints: validatorArgs,
711
- validator: {
712
- validate(value) {
713
- try {
714
- return validator(value, ...validatorArgs);
715
- }
716
- catch {
717
- return false;
718
- }
719
- },
720
- defaultMessage(validationArguments) {
721
- const property = validationArguments.property;
722
- return defaultMessage
723
- ? defaultMessage.replace('$property', property)
724
- : `Invalid value for ${property}`;
725
- }
726
- }
727
- });
728
- };
729
- };
486
+ function setExpose(object, propertyName) {
487
+ const types = Reflect.getMetadata("design:type", object, propertyName);
488
+ if (types) {
489
+ const originMap = koatty_container.getOriginMetadata(PARAM_TYPE_KEY, object);
490
+ originMap.set(propertyName, types.name);
491
+ }
730
492
  }
731
493
  /**
732
- * 创建简单验证装饰器(不需要额外参数)
733
- * @param name 装饰器名称
734
- * @param validator 验证函数
735
- * @param defaultMessage 默认错误信息
736
- * @returns 装饰器函数
494
+ * plain object convert to class instance
495
+ *
496
+ * @export
497
+ * @param {*} clazz
498
+ * @param {*} data
499
+ * @param {boolean} [convert=false]
500
+ * @returns
737
501
  */
738
- function createSimpleDecorator(name, validator, defaultMessage) {
739
- return createValidationDecorator({
740
- name,
741
- validator,
742
- defaultMessage,
743
- requiresValue: false
744
- });
502
+ function plainToClass(clazz, data, convert = false) {
503
+ if (helper__namespace.isClass(clazz)) {
504
+ if (!helper__namespace.isObject(data)) {
505
+ data = {};
506
+ }
507
+ if (data instanceof clazz) {
508
+ return data;
509
+ }
510
+ return assignDtoParams(clazz, data, convert);
511
+ }
512
+ return data;
513
+ }
514
+ /**
515
+ * assign dto params
516
+ * @param clazz
517
+ * @param data
518
+ * @param convert
519
+ * @returns
520
+ */
521
+ function assignDtoParams(clazz, data, convert = false) {
522
+ const cls = Reflect.construct(clazz, []);
523
+ if (convert) {
524
+ const metaData = getDtoParamsMeta(clazz, cls);
525
+ for (const [key, type] of metaData) {
526
+ if (key && data[key] !== undefined) {
527
+ cls[key] = convertParamsType(data[key], type);
528
+ }
529
+ }
530
+ }
531
+ else {
532
+ for (const key in cls) {
533
+ if (Object.prototype.hasOwnProperty.call(data, key) &&
534
+ data[key] !== undefined) {
535
+ cls[key] = data[key];
536
+ }
537
+ }
538
+ }
539
+ return cls;
745
540
  }
746
541
  /**
747
- * 创建带参数的验证装饰器
748
- * @param name 装饰器名称
749
- * @param validator 验证函数
750
- * @param defaultMessage 默认错误信息
751
- * @returns 装饰器工厂函数
542
+ * get class prototype type def.
543
+ * @param clazz
544
+ * @param cls
545
+ * @returns
752
546
  */
753
- function createParameterizedDecorator(name, validator, defaultMessage) {
754
- return createValidationDecorator({
755
- name,
756
- validator,
757
- defaultMessage,
758
- requiresValue: true
547
+ function getDtoParamsMeta(clazz, cls) {
548
+ // Non-own properties are inherited from the prototype chain,
549
+ // ensure that properties are not polluted
550
+ if (!Object.prototype.hasOwnProperty.call(cls, "_typeDef") &&
551
+ ("_typeDef" in cls)) {
552
+ return cls._typeDef;
553
+ }
554
+ const typeDef = koatty_container.getOriginMetadata(PARAM_TYPE_KEY, clazz);
555
+ Reflect.defineProperty(clazz.prototype, "_typeDef", {
556
+ enumerable: true,
557
+ configurable: false,
558
+ writable: false,
559
+ value: typeDef,
759
560
  });
561
+ return typeDef;
760
562
  }
761
-
762
563
  /**
763
- * 重构后的装饰器定义 - 使用工厂函数消除重复
764
- * @author richen
564
+ * convertDtoParamsType
565
+ *
566
+ * @param {*} clazz
567
+ * @param {*} cls
568
+ * @returns {*} cls
765
569
  */
766
- // 中国本土化验证装饰器
767
- const IsCnName = createSimpleDecorator('IsCnName', (value) => helper__namespace.isString(value) && cnName(value), 'must be a valid Chinese name');
768
- const IsIdNumber = createSimpleDecorator('IsIdNumber', (value) => helper__namespace.isString(value) && idNumber(value), 'must be a valid ID number');
769
- const IsZipCode = createSimpleDecorator('IsZipCode', (value) => helper__namespace.isString(value) && zipCode(value), 'must be a valid zip code');
770
- const IsMobile = createSimpleDecorator('IsMobile', (value) => helper__namespace.isString(value) && mobile(value), 'must be a valid mobile number');
771
- const IsPlateNumber = createSimpleDecorator('IsPlateNumber', (value) => helper__namespace.isString(value) && plateNumber(value), 'must be a valid plate number');
772
- // 基础验证装饰器
773
- const IsNotEmpty = createSimpleDecorator('IsNotEmpty', (value) => !helper__namespace.isEmpty(value), 'should not be empty');
774
- const IsDate = createSimpleDecorator('IsDate', (value) => helper__namespace.isDate(value), 'must be a valid date');
775
- // 带参数的验证装饰器
776
- const Equals = createParameterizedDecorator('Equals', (value, comparison) => value === comparison, 'must equal to $constraint1');
777
- const NotEquals = createParameterizedDecorator('NotEquals', (value, comparison) => value !== comparison, 'should not equal to $constraint1');
778
- const Contains = createParameterizedDecorator('Contains', (value, seed) => helper__namespace.isString(value) && value.includes(seed), 'must contain $constraint1');
779
- const IsIn = createParameterizedDecorator('IsIn', (value, possibleValues) => possibleValues.includes(value), 'must be one of the following values: $constraint1');
780
- const IsNotIn = createParameterizedDecorator('IsNotIn', (value, possibleValues) => !possibleValues.includes(value), 'should not be one of the following values: $constraint1');
781
- // 数值比较装饰器
782
- const Gt = createParameterizedDecorator('Gt', (value, min) => helper__namespace.toNumber(value) > min, 'must be greater than $constraint1');
783
- const Gte = createParameterizedDecorator('Gte', (value, min) => helper__namespace.toNumber(value) >= min, 'must be greater than or equal to $constraint1');
784
- const Lt = createParameterizedDecorator('Lt', (value, max) => helper__namespace.toNumber(value) < max, 'must be less than $constraint1');
785
- const Lte = createParameterizedDecorator('Lte', (value, max) => helper__namespace.toNumber(value) <= max, 'must be less than or equal to $constraint1');
786
- // 复杂验证装饰器(需要特殊处理)
787
- function IsEmail(options, validationOptions) {
788
- return createParameterizedDecorator('IsEmail', (value) => classValidator.isEmail(value, options), 'must be a valid email')(validationOptions);
789
- }
790
- function IsIP(version, validationOptions) {
791
- return createParameterizedDecorator('IsIP', (value) => classValidator.isIP(value, version), 'must be a valid IP address')(validationOptions);
792
- }
793
- function IsPhoneNumber(region, validationOptions) {
794
- return createParameterizedDecorator('IsPhoneNumber', (value) => classValidator.isPhoneNumber(value, region), 'must be a valid phone number')(validationOptions);
795
- }
796
- function IsUrl(options, validationOptions) {
797
- return createParameterizedDecorator('IsUrl', (value) => classValidator.isURL(value, options), 'must be a valid URL')(validationOptions);
570
+ function convertDtoParamsType(clazz, cls) {
571
+ if (Object.prototype.hasOwnProperty.call(cls, "_typeDef")) {
572
+ for (const key in cls) {
573
+ if (Object.prototype.hasOwnProperty.call(cls._typeDef, key) &&
574
+ cls[key] !== undefined) {
575
+ cls[key] = convertParamsType(cls[key], cls._typeDef[key]);
576
+ }
577
+ }
578
+ }
579
+ else {
580
+ const originMap = koatty_container.getOriginMetadata(PARAM_TYPE_KEY, clazz);
581
+ for (const [key, type] of originMap) {
582
+ if (key && cls[key] !== undefined) {
583
+ cls[key] = convertParamsType(cls[key], type);
584
+ }
585
+ }
586
+ }
587
+ return cls;
798
588
  }
799
- function IsHash(algorithm, validationOptions) {
800
- return createParameterizedDecorator('IsHash', (value) => classValidator.isHash(value, algorithm), 'must be a valid hash')(validationOptions);
589
+ /**
590
+ * 绑定参数类型转换
591
+ *
592
+ * @param {*} param
593
+ * @param {string} type
594
+ * @returns {*}
595
+ */
596
+ function convertParamsType(param, type) {
597
+ try {
598
+ switch (type) {
599
+ case "Number":
600
+ case "number":
601
+ if (helper__namespace.isNaN(param)) {
602
+ return NaN;
603
+ }
604
+ if (helper__namespace.isNumber(param)) {
605
+ // Check for safe integer range
606
+ if (Number.isInteger(param) && !Number.isSafeInteger(param)) {
607
+ console.warn(`[koatty_validation] Number ${param} exceeds safe integer range`);
608
+ }
609
+ return param;
610
+ }
611
+ if (helper__namespace.isNumberString(param)) {
612
+ const num = helper__namespace.toNumber(param);
613
+ if (Number.isInteger(num) && !Number.isSafeInteger(num)) {
614
+ console.warn(`[koatty_validation] Number ${param} exceeds safe integer range`);
615
+ }
616
+ return num;
617
+ }
618
+ return NaN;
619
+ case "Boolean":
620
+ case "boolean":
621
+ return !!param;
622
+ case "Array":
623
+ case "array":
624
+ case "Tuple":
625
+ case "tuple":
626
+ if (helper__namespace.isArray(param)) {
627
+ return param;
628
+ }
629
+ return helper__namespace.toArray(param);
630
+ case "String":
631
+ case "string":
632
+ if (helper__namespace.isString(param)) {
633
+ return param;
634
+ }
635
+ return helper__namespace.toString(param);
636
+ case "Null":
637
+ case "null":
638
+ return null;
639
+ case "Undefined":
640
+ case "undefined":
641
+ return undefined;
642
+ case "Bigint":
643
+ case "bigint":
644
+ if (typeof param === 'bigint') {
645
+ return param;
646
+ }
647
+ return BigInt(param);
648
+ // case "object":
649
+ // case "enum":
650
+ default: //any
651
+ return param;
652
+ }
653
+ }
654
+ catch (error) {
655
+ // Log conversion errors for debugging
656
+ console.warn(`[koatty_validation] Type conversion error for type "${type}":`, error instanceof Error ? error.message : String(error));
657
+ return param;
658
+ }
801
659
  }
802
- // 基础工具装饰器(从原始decorator.ts移植)
803
660
  /**
804
- * 标记属性为可导出
661
+ * Check the base types.
662
+ *
663
+ * @param {*} value
664
+ * @param {string} type
665
+ * @returns {*}
805
666
  */
806
- function Expose() {
807
- return function (object, propertyName) {
808
- setExpose(object, propertyName);
809
- };
667
+ function checkParamsType(value, type) {
668
+ switch (type) {
669
+ case "Number":
670
+ case "number":
671
+ if (!helper__namespace.isNumber(value) || helper__namespace.isNaN(value)) {
672
+ return false;
673
+ }
674
+ return true;
675
+ case "Boolean":
676
+ case "boolean":
677
+ if (!helper__namespace.isBoolean(value)) {
678
+ return false;
679
+ }
680
+ return true;
681
+ case "Array":
682
+ case "array":
683
+ case "Tuple":
684
+ case "tuple":
685
+ if (!helper__namespace.isArray(value)) {
686
+ return false;
687
+ }
688
+ return true;
689
+ case "String":
690
+ case "string":
691
+ if (!helper__namespace.isString(value)) {
692
+ return false;
693
+ }
694
+ return true;
695
+ case "Object":
696
+ case "object":
697
+ case "Enum":
698
+ case "enum":
699
+ if (helper__namespace.isTrueEmpty(value)) {
700
+ return false;
701
+ }
702
+ return true;
703
+ case "Null":
704
+ case "null":
705
+ if (!helper__namespace.isNull(value)) {
706
+ return false;
707
+ }
708
+ return true;
709
+ case "Undefined":
710
+ case "undefined":
711
+ if (!helper__namespace.isUndefined(value)) {
712
+ return false;
713
+ }
714
+ return true;
715
+ case "Bigint":
716
+ case "bigint":
717
+ if (typeof value !== 'bigint') {
718
+ return false;
719
+ }
720
+ return true;
721
+ default: //any
722
+ return true;
723
+ }
810
724
  }
811
725
  /**
812
- * Expose的别名
726
+ * Checks if value is a chinese name.
727
+ *
728
+ * @param {string} value
729
+ * @returns {boolean}
813
730
  */
814
- function IsDefined() {
815
- return function (object, propertyName) {
816
- setExpose(object, propertyName);
817
- };
731
+ function cnName(value) {
732
+ // Add length limit for security
733
+ if (value.length > 50) {
734
+ return false;
735
+ }
736
+ // Use regex cache for better performance
737
+ const reg = regexCache.get('^([a-zA-Z0-9\\u4e00-\\u9fa5\\·]{1,10})$');
738
+ return reg.test(value);
818
739
  }
819
740
  /**
820
- * 参数验证装饰器
741
+ * Checks if value is a idCard number.
742
+ *
743
+ * @param {string} value
744
+ * @returns
821
745
  */
822
- function Valid(rule, options) {
823
- return function (object, propertyName, parameterIndex) {
824
- // 这里保持与原始实现一致
825
- const existingRules = Reflect.getOwnMetadata("validate", object, propertyName) || {};
826
- existingRules[parameterIndex] = { rule, options };
827
- Reflect.defineMetadata("validate", existingRules, object, propertyName);
828
- };
746
+ function idNumber(value) {
747
+ // Add length limit for security
748
+ if (!value || value.length > 20) {
749
+ return false;
750
+ }
751
+ // Check 15-digit ID
752
+ const reg15 = regexCache.get('^\\d{15}$');
753
+ if (reg15.test(value)) {
754
+ return true;
755
+ }
756
+ // Check 18-digit ID with validation
757
+ const reg18 = regexCache.get('^\\d{17}[0-9X]$');
758
+ if (reg18.test(value)) {
759
+ const vs = '1,0,x,9,8,7,6,5,4,3,2'.split(',');
760
+ const ps = '7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2'.split(',');
761
+ const ss = value.toLowerCase().split('');
762
+ let r = 0;
763
+ for (let i = 0; i < 17; i++) {
764
+ r += ps[i] * ss[i];
765
+ }
766
+ const isOk = (vs[r % 11] === ss[17]);
767
+ return isOk;
768
+ }
769
+ return false;
829
770
  }
830
771
  /**
831
- * 方法验证装饰器
772
+ * Checks if value is a mobile phone number.
773
+ *
774
+ * @param {string} value
775
+ * @returns {boolean}
832
776
  */
833
- function Validated() {
834
- return function (object, propertyName, descriptor) {
835
- const originalMethod = descriptor.value;
836
- descriptor.value = function (...args) {
837
- // 这里可以添加验证逻辑
838
- return originalMethod.apply(this, args);
839
- };
840
- return descriptor;
841
- };
777
+ function mobile(value) {
778
+ // Add length limit for security (prevent ReDoS)
779
+ if (!value || value.length > 20) {
780
+ return false;
781
+ }
782
+ const reg = regexCache.get('^(13|14|15|16|17|18|19)\\d{9}$');
783
+ return reg.test(value);
842
784
  }
843
-
844
- /**
845
- * 改进的错误处理机制
846
- * @author richen
847
- */
848
785
  /**
849
- * 错误信息国际化
786
+ * Checks if value is a zipCode.
787
+ *
788
+ * @param {string} value
789
+ * @returns {boolean}
850
790
  */
851
- const ERROR_MESSAGES = {
852
- zh: {
853
- // 中国本土化验证
854
- IsCnName: '必须是有效的中文姓名',
855
- IsIdNumber: '必须是有效的身份证号码',
856
- IsZipCode: '必须是有效的邮政编码',
857
- IsMobile: '必须是有效的手机号码',
858
- IsPlateNumber: '必须是有效的车牌号码',
859
- // 基础验证
860
- IsNotEmpty: '不能为空',
861
- IsDate: '必须是有效的日期',
862
- IsEmail: '必须是有效的邮箱地址',
863
- IsIP: '必须是有效的IP地址',
864
- IsPhoneNumber: '必须是有效的电话号码',
865
- IsUrl: '必须是有效的URL地址',
866
- IsHash: '必须是有效的哈希值',
867
- // 比较验证
868
- Equals: '必须等于 {comparison}',
869
- NotEquals: '不能等于 {comparison}',
870
- Contains: '必须包含 {seed}',
871
- IsIn: '必须是以下值之一: {possibleValues}',
872
- IsNotIn: '不能是以下值之一: {possibleValues}',
873
- Gt: '必须大于 {min}',
874
- Gte: '必须大于或等于 {min}',
875
- Lt: '必须小于 {max}',
876
- Lte: '必须小于或等于 {max}',
877
- // 通用错误
878
- invalidParameter: '参数 {field} 无效',
879
- validationFailed: '验证失败',
880
- },
881
- en: {
882
- // Chinese localization validators
883
- IsCnName: 'must be a valid Chinese name',
884
- IsIdNumber: 'must be a valid ID number',
885
- IsZipCode: 'must be a valid zip code',
886
- IsMobile: 'must be a valid mobile number',
887
- IsPlateNumber: 'must be a valid plate number',
888
- // Basic validators
889
- IsNotEmpty: 'should not be empty',
890
- IsDate: 'must be a valid date',
891
- IsEmail: 'must be a valid email',
892
- IsIP: 'must be a valid IP address',
893
- IsPhoneNumber: 'must be a valid phone number',
894
- IsUrl: 'must be a valid URL',
895
- IsHash: 'must be a valid hash',
896
- // Comparison validators
897
- Equals: 'must equal to {comparison}',
898
- NotEquals: 'should not equal to {comparison}',
899
- Contains: 'must contain {seed}',
900
- IsIn: 'must be one of the following values: {possibleValues}',
901
- IsNotIn: 'should not be one of the following values: {possibleValues}',
902
- Gt: 'must be greater than {min}',
903
- Gte: 'must be greater than or equal to {min}',
904
- Lt: 'must be less than {max}',
905
- Lte: 'must be less than or equal to {max}',
906
- // Common errors
907
- invalidParameter: 'invalid parameter {field}',
908
- validationFailed: 'validation failed',
791
+ function zipCode(value) {
792
+ // Add length limit for security
793
+ if (!value || value.length > 10) {
794
+ return false;
909
795
  }
910
- };
796
+ const reg = regexCache.get('^\\d{6}$');
797
+ return reg.test(value);
798
+ }
911
799
  /**
912
- * 增强的验证错误类
800
+ * Checks if value is a plateNumber.
801
+ *
802
+ * @param {string} value
803
+ * @returns {boolean}
913
804
  */
914
- class KoattyValidationError extends Error {
915
- constructor(errors, message) {
916
- const errorMessage = message || 'Validation failed';
917
- super(errorMessage);
918
- this.name = 'KoattyValidationError';
919
- this.errors = errors;
920
- this.statusCode = 400;
921
- this.timestamp = new Date();
922
- // 确保正确的原型链
923
- Object.setPrototypeOf(this, KoattyValidationError.prototype);
924
- }
925
- /**
926
- * 获取第一个错误信息
927
- */
928
- getFirstError() {
929
- return this.errors[0];
805
+ function plateNumber(value) {
806
+ // Add length limit for security
807
+ if (!value || value.length > 15) {
808
+ return false;
930
809
  }
931
- /**
932
- * 获取指定字段的错误
933
- */
934
- getFieldErrors(field) {
935
- return this.errors.filter(error => error.field === field);
810
+ // New energy vehicle plate number (8 characters)
811
+ const xReg = regexCache.get('^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))');
812
+ // Traditional vehicle plate number (7 characters)
813
+ const cReg = regexCache.get('^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$');
814
+ if (value.length === 7) {
815
+ return cReg.test(value);
936
816
  }
937
- /**
938
- * 转换为JSON格式
939
- */
940
- toJSON() {
941
- return {
942
- name: this.name,
943
- message: this.message,
944
- statusCode: this.statusCode,
945
- timestamp: this.timestamp,
946
- errors: this.errors
947
- };
817
+ else {
818
+ // New energy vehicle plate
819
+ return xReg.test(value);
948
820
  }
949
821
  }
822
+
823
+ /*
824
+ * @Description:
825
+ * @Usage:
826
+ * @Author: richen
827
+ * @Date: 2021-11-25 10:47:04
828
+ * @LastEditTime: 2024-01-03 14:32:49
829
+ */
830
+ // constant
831
+ const PARAM_RULE_KEY = 'PARAM_RULE_KEY';
832
+ const PARAM_CHECK_KEY = 'PARAM_CHECK_KEY';
833
+ const ENABLE_VALIDATED = "ENABLE_VALIDATED";
950
834
  /**
951
- * 错误信息格式化器
835
+ * paramterTypes
836
+ *
837
+ * @export
838
+ * @enum {number}
952
839
  */
953
- class ErrorMessageFormatter {
954
- constructor(language = 'zh') {
955
- this.language = 'zh';
956
- this.language = language;
840
+ exports.paramterTypes = void 0;
841
+ (function (paramterTypes) {
842
+ paramterTypes[paramterTypes["Number"] = 0] = "Number";
843
+ paramterTypes[paramterTypes["number"] = 1] = "number";
844
+ paramterTypes[paramterTypes["String"] = 2] = "String";
845
+ paramterTypes[paramterTypes["string"] = 3] = "string";
846
+ paramterTypes[paramterTypes["Boolean"] = 4] = "Boolean";
847
+ paramterTypes[paramterTypes["boolean"] = 5] = "boolean";
848
+ paramterTypes[paramterTypes["Array"] = 6] = "Array";
849
+ paramterTypes[paramterTypes["array"] = 7] = "array";
850
+ paramterTypes[paramterTypes["Tuple"] = 8] = "Tuple";
851
+ paramterTypes[paramterTypes["tuple"] = 9] = "tuple";
852
+ paramterTypes[paramterTypes["Object"] = 10] = "Object";
853
+ paramterTypes[paramterTypes["object"] = 11] = "object";
854
+ paramterTypes[paramterTypes["Enum"] = 12] = "Enum";
855
+ paramterTypes[paramterTypes["enum"] = 13] = "enum";
856
+ paramterTypes[paramterTypes["Bigint"] = 14] = "Bigint";
857
+ paramterTypes[paramterTypes["bigint"] = 15] = "bigint";
858
+ paramterTypes[paramterTypes["Null"] = 16] = "Null";
859
+ paramterTypes[paramterTypes["null"] = 17] = "null";
860
+ paramterTypes[paramterTypes["Undefined"] = 18] = "Undefined";
861
+ paramterTypes[paramterTypes["undefined"] = 19] = "undefined";
862
+ })(exports.paramterTypes || (exports.paramterTypes = {}));
863
+ class ValidateClass {
864
+ constructor() {
957
865
  }
958
866
  /**
959
- * 设置语言
867
+ *
868
+ *
869
+ * @static
870
+ * @returns
871
+ * @memberof ValidateUtil
960
872
  */
961
- setLanguage(language) {
962
- this.language = language;
873
+ static getInstance() {
874
+ return this.instance || (this.instance = new ValidateClass());
963
875
  }
964
876
  /**
965
- * 格式化错误消息
877
+ * validated data vs dto class
878
+ *
879
+ * @param {*} Clazz
880
+ * @param {*} data
881
+ * @param {boolean} [convert=false] auto convert parameters type
882
+ * @returns {Promise<any>}
883
+ * @memberof ValidateClass
966
884
  */
967
- formatMessage(constraint, field, value, context) {
968
- const messages = ERROR_MESSAGES[this.language];
969
- let template = messages[constraint] || messages.invalidParameter;
970
- // 替换占位符
971
- template = template.replace('{field}', field);
972
- // 优先使用上下文中的值,然后是传入的value
973
- if (context) {
974
- Object.entries(context).forEach(([key, val]) => {
975
- template = template.replace(`{${key}}`, this.formatValue(val));
976
- });
885
+ async valid(Clazz, data, convert = false) {
886
+ let obj = {};
887
+ if (data instanceof Clazz) {
888
+ obj = data;
977
889
  }
978
- // 如果还有{value}占位符且传入了value,则替换
979
- if (value !== undefined && template.includes('{value}')) {
980
- template = template.replace('{value}', this.formatValue(value));
890
+ else {
891
+ obj = plainToClass(Clazz, data, convert);
981
892
  }
982
- return template;
983
- }
984
- /**
985
- * 格式化值用于消息显示
986
- * @private
987
- */
988
- formatValue(value) {
989
- if (value === null)
990
- return 'null';
991
- if (value === undefined)
992
- return 'undefined';
993
- if (typeof value === 'number')
994
- return String(value);
995
- if (typeof value === 'string')
996
- return `"${value}"`;
997
- if (Array.isArray(value))
998
- return `[${value.map(v => this.formatValue(v)).join(', ')}]`;
999
- if (typeof value === 'object') {
1000
- try {
1001
- return JSON.stringify(value);
1002
- }
1003
- catch {
1004
- // 处理循环引用
1005
- return '[Circular Reference]';
1006
- }
893
+ let errors = [];
894
+ if (convert) {
895
+ errors = await classValidator.validate(obj);
1007
896
  }
1008
- return String(value);
1009
- }
1010
- }
1011
- /**
1012
- * 全局错误信息格式化器实例
1013
- */
1014
- const errorFormatter = new ErrorMessageFormatter();
1015
- /**
1016
- * 设置全局语言
1017
- */
1018
- function setValidationLanguage(language) {
1019
- errorFormatter.setLanguage(language);
897
+ else {
898
+ errors = await classValidator.validate(obj, { skipMissingProperties: true });
899
+ }
900
+ if (errors.length > 0) {
901
+ throw new Error(Object.values(errors[0].constraints)[0]);
902
+ }
903
+ return obj;
904
+ }
1020
905
  }
1021
906
  /**
1022
- * 创建验证错误
907
+ * ClassValidator for manual
1023
908
  */
1024
- function createValidationError(field, value, constraint, customMessage, context) {
1025
- const message = customMessage || errorFormatter.formatMessage(constraint, field, value, context);
1026
- return {
1027
- field,
1028
- value,
1029
- constraint,
1030
- message,
1031
- context
1032
- };
1033
- }
909
+ const ClassValidator = ValidateClass.getInstance();
1034
910
  /**
1035
- * 批量创建验证错误
911
+ * Helper function to wrap validation with cache
1036
912
  */
1037
- function createValidationErrors(errors) {
1038
- const validationErrors = errors.map(error => createValidationError(error.field, error.value, error.constraint, error.message, error.context));
1039
- return new KoattyValidationError(validationErrors);
913
+ function withCache(validatorName, validatorFunc) {
914
+ return (value, ...args) => {
915
+ // Check cache first
916
+ const cached = validationCache.get(validatorName, value, ...args);
917
+ if (cached !== undefined) {
918
+ return cached;
919
+ }
920
+ // Execute validation
921
+ const result = validatorFunc(value, ...args);
922
+ // Cache the result
923
+ validationCache.set(validatorName, value, result, ...args);
924
+ return result;
925
+ };
1040
926
  }
1041
-
1042
- /**
1043
- * 性能缓存模块 - 提供多层次缓存和性能监控
1044
- * @author richen
1045
- */
1046
927
  /**
1047
- * 元数据缓存
928
+ * Validator Functions
1048
929
  */
1049
- class MetadataCache {
1050
- constructor() {
1051
- this.cache = new WeakMap();
1052
- }
1053
- static getInstance() {
1054
- if (!MetadataCache.instance) {
1055
- MetadataCache.instance = new MetadataCache();
1056
- }
1057
- return MetadataCache.instance;
1058
- }
930
+ const ValidFuncs = {
1059
931
  /**
1060
- * 获取类的元数据缓存
932
+ * Checks value is not empty, undefined, null, '', NaN, [], {} and any empty string(including spaces,
933
+ * tabs, formfeeds, etc.), returns false
1061
934
  */
1062
- getClassCache(target) {
1063
- if (!this.cache.has(target)) {
1064
- this.cache.set(target, new Map());
1065
- }
1066
- return this.cache.get(target);
1067
- }
935
+ IsNotEmpty: withCache('IsNotEmpty', (value) => {
936
+ return !helper__namespace.isEmpty(value);
937
+ }),
1068
938
  /**
1069
- * 缓存元数据
939
+ * Checks if a given value is a real date.
1070
940
  */
1071
- setMetadata(target, key, value) {
1072
- const classCache = this.getClassCache(target);
1073
- classCache.set(key, value);
1074
- }
941
+ IsDate: withCache('IsDate', (value) => {
942
+ return helper__namespace.isDate(value);
943
+ }),
1075
944
  /**
1076
- * 获取缓存的元数据
945
+ * Checks if the string is an email. If given value is not a string, then it returns false.
1077
946
  */
1078
- getMetadata(target, key) {
1079
- const classCache = this.getClassCache(target);
1080
- return classCache.get(key);
1081
- }
947
+ IsEmail: withCache('IsEmail', (value, options) => {
948
+ return classValidator.isEmail(value, options);
949
+ }),
1082
950
  /**
1083
- * 检查是否已缓存
951
+ * Checks if the string is an IP (version 4 or 6). If given value is not a string, then it returns false.
1084
952
  */
1085
- hasMetadata(target, key) {
1086
- const classCache = this.getClassCache(target);
1087
- return classCache.has(key);
1088
- }
953
+ IsIP: withCache('IsIP', (value, version) => {
954
+ return classValidator.isIP(value, version);
955
+ }),
1089
956
  /**
1090
- * 清空指定类的缓存
957
+ * Checks if the string is a valid phone number.
958
+ * @param value — the potential phone number string to test
959
+ * @param region 2 characters uppercase country code (e.g. DE, US, CH). If users must enter the intl.
960
+ * prefix (e.g. +41), then you may pass "ZZ" or null as region.
961
+ * See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github]
962
+ * {@link https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33}
1091
963
  */
1092
- clearClassCache(target) {
1093
- if (this.cache.has(target)) {
1094
- this.cache.delete(target);
964
+ IsPhoneNumber: withCache('IsPhoneNumber', (value, region) => {
965
+ return classValidator.isPhoneNumber(value, region);
966
+ }),
967
+ /**
968
+ * Checks if the string is an url. If given value is not a string, then it returns false.
969
+ */
970
+ IsUrl: withCache('IsUrl', (value, options) => {
971
+ return classValidator.isURL(value, options);
972
+ }),
973
+ /**
974
+ * check if the string is a hash of type algorithm. Algorithm is one of
975
+ * ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b']
976
+ */
977
+ IsHash: withCache('IsHash', (value, algorithm) => {
978
+ return classValidator.isHash(value, algorithm);
979
+ }),
980
+ /**
981
+ * Checks if value is a chinese name.
982
+ */
983
+ IsCnName: withCache('IsCnName', (value) => {
984
+ if (!helper__namespace.isString(value)) {
985
+ return false;
1095
986
  }
1096
- }
1097
- }
1098
- /**
1099
- * 验证结果缓存
1100
- */
1101
- class ValidationCache {
1102
- constructor(options) {
1103
- this.cache = new lruCache.LRUCache({
1104
- max: (options === null || options === void 0 ? void 0 : options.max) || 5000,
1105
- ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 10, // 10分钟
1106
- allowStale: (options === null || options === void 0 ? void 0 : options.allowStale) || false,
1107
- updateAgeOnGet: (options === null || options === void 0 ? void 0 : options.updateAgeOnGet) || true,
1108
- });
1109
- }
1110
- static getInstance(options) {
1111
- if (!ValidationCache.instance) {
1112
- ValidationCache.instance = new ValidationCache(options);
987
+ return cnName(value);
988
+ }),
989
+ /**
990
+ * Checks if value is a idcard number.
991
+ */
992
+ IsIdNumber: withCache('IsIdNumber', (value) => {
993
+ if (!helper__namespace.isString(value)) {
994
+ return false;
1113
995
  }
1114
- return ValidationCache.instance;
1115
- }
996
+ return idNumber(value);
997
+ }),
1116
998
  /**
1117
- * 生成缓存键
999
+ * Checks if value is a zipCode.
1118
1000
  */
1119
- generateKey(validator, value, ...args) {
1120
- const valueStr = this.serializeValue(value);
1121
- const argsStr = args.length > 0 ? JSON.stringify(args) : '';
1122
- return `${validator}:${valueStr}:${argsStr}`;
1123
- }
1001
+ IsZipCode: withCache('IsZipCode', (value) => {
1002
+ if (!helper__namespace.isString(value)) {
1003
+ return false;
1004
+ }
1005
+ return zipCode(value);
1006
+ }),
1124
1007
  /**
1125
- * 序列化值用于缓存键
1008
+ * Checks if value is a mobile phone number.
1126
1009
  */
1127
- serializeValue(value) {
1128
- if (value === null)
1129
- return 'null';
1130
- if (value === undefined)
1131
- return 'undefined';
1132
- if (typeof value === 'string')
1133
- return `s:${value}`;
1134
- if (typeof value === 'number')
1135
- return `n:${value}`;
1136
- if (typeof value === 'boolean')
1137
- return `b:${value}`;
1138
- if (Array.isArray(value))
1139
- return `a:${JSON.stringify(value)}`;
1140
- if (typeof value === 'object')
1141
- return `o:${JSON.stringify(value)}`;
1142
- return String(value);
1143
- }
1010
+ IsMobile: withCache('IsMobile', (value) => {
1011
+ if (!helper__namespace.isString(value)) {
1012
+ return false;
1013
+ }
1014
+ return mobile(value);
1015
+ }),
1144
1016
  /**
1145
- * 获取缓存的验证结果
1017
+ * Checks if value is a plateNumber.
1146
1018
  */
1147
- get(validator, value, ...args) {
1148
- const key = this.generateKey(validator, value, ...args);
1149
- return this.cache.get(key);
1150
- }
1019
+ IsPlateNumber: withCache('IsPlateNumber', (value) => {
1020
+ if (!helper__namespace.isString(value)) {
1021
+ return false;
1022
+ }
1023
+ return plateNumber(value);
1024
+ }),
1151
1025
  /**
1152
- * 缓存验证结果
1026
+ * Checks if value matches ("===") the comparison.
1153
1027
  */
1154
- set(validator, value, result, ...args) {
1155
- const key = this.generateKey(validator, value, ...args);
1156
- this.cache.set(key, result);
1157
- }
1028
+ Equals: withCache('Equals', (value, comparison) => {
1029
+ return classValidator.equals(value, comparison);
1030
+ }),
1158
1031
  /**
1159
- * 检查是否存在缓存
1032
+ * Checks if value does not match ("!==") the comparison.
1160
1033
  */
1161
- has(validator, value, ...args) {
1162
- const key = this.generateKey(validator, value, ...args);
1163
- return this.cache.has(key);
1164
- }
1034
+ NotEquals: withCache('NotEquals', (value, comparison) => {
1035
+ return classValidator.notEquals(value, comparison);
1036
+ }),
1165
1037
  /**
1166
- * 删除特定缓存
1038
+ * Checks if the string contains the seed. If given value is not a string, then it returns false.
1167
1039
  */
1168
- delete(validator, value, ...args) {
1169
- const key = this.generateKey(validator, value, ...args);
1170
- return this.cache.delete(key);
1171
- }
1040
+ Contains: withCache('Contains', (value, seed) => {
1041
+ return classValidator.contains(value, seed);
1042
+ }),
1172
1043
  /**
1173
- * 清空缓存
1044
+ * Checks if given value is in a array of allowed values.
1174
1045
  */
1175
- clear() {
1176
- this.cache.clear();
1177
- }
1046
+ IsIn: withCache('IsIn', (value, possibleValues) => {
1047
+ return classValidator.isIn(value, possibleValues);
1048
+ }),
1178
1049
  /**
1179
- * 获取缓存统计
1050
+ * Checks if given value not in a array of allowed values.
1180
1051
  */
1181
- getStats() {
1182
- return {
1183
- size: this.cache.size,
1184
- max: this.cache.max,
1185
- calculatedSize: this.cache.calculatedSize,
1186
- keyCount: this.cache.size,
1187
- };
1188
- }
1052
+ IsNotIn: withCache('IsNotIn', (value, possibleValues) => {
1053
+ return classValidator.isNotIn(value, possibleValues);
1054
+ }),
1189
1055
  /**
1190
- * 设置缓存TTL
1056
+ * Checks if the first number is greater than or equal to the second.
1191
1057
  */
1192
- setTTL(validator, value, ttl, ...args) {
1193
- const key = this.generateKey(validator, value, ...args);
1194
- const existingValue = this.cache.get(key);
1195
- if (existingValue !== undefined) {
1196
- this.cache.set(key, existingValue, { ttl });
1197
- }
1198
- }
1199
- }
1200
- /**
1201
- * 正则表达式缓存
1202
- */
1203
- class RegexCache {
1204
- constructor(options) {
1205
- this.cache = new lruCache.LRUCache({
1206
- max: (options === null || options === void 0 ? void 0 : options.max) || 200,
1207
- ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 30, // 30分钟
1208
- allowStale: (options === null || options === void 0 ? void 0 : options.allowStale) || false,
1209
- updateAgeOnGet: (options === null || options === void 0 ? void 0 : options.updateAgeOnGet) || true,
1210
- });
1211
- }
1212
- static getInstance(options) {
1213
- if (!RegexCache.instance) {
1214
- RegexCache.instance = new RegexCache(options);
1215
- }
1216
- return RegexCache.instance;
1217
- }
1058
+ Gt: withCache('Gt', (num, min) => {
1059
+ return helper__namespace.toNumber(num) > min;
1060
+ }),
1218
1061
  /**
1219
- * 获取缓存的正则表达式
1062
+ * Checks if the first number is less than or equal to the second.
1220
1063
  */
1221
- get(pattern, flags) {
1222
- const key = flags ? `${pattern}:::${flags}` : pattern;
1223
- let regex = this.cache.get(key);
1224
- if (!regex) {
1225
- try {
1226
- regex = new RegExp(pattern, flags);
1227
- this.cache.set(key, regex);
1228
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1229
- }
1230
- catch (_error) {
1231
- // 如果正则表达式无效,抛出错误
1232
- throw new Error(`Invalid regex pattern: ${pattern}`);
1233
- }
1234
- }
1235
- return regex;
1236
- }
1064
+ Lt: withCache('Lt', (num, max) => {
1065
+ return helper__namespace.toNumber(num) < max;
1066
+ }),
1067
+ /**
1068
+ * Checks if the first number is greater than or equal to the second.
1069
+ */
1070
+ Gte: withCache('Gte', (num, min) => {
1071
+ return helper__namespace.toNumber(num) >= min;
1072
+ }),
1237
1073
  /**
1238
- * 预编译常用正则表达式
1074
+ * Checks if the first number is less than or equal to the second.
1239
1075
  */
1240
- precompile(patterns) {
1241
- patterns.forEach(({ pattern, flags }) => {
1242
- try {
1243
- this.get(pattern, flags);
1076
+ Lte: withCache('Lte', (num, max) => {
1077
+ return helper__namespace.toNumber(num) <= max;
1078
+ }),
1079
+ };
1080
+ /**
1081
+ * Helper function to create validator function
1082
+ */
1083
+ function createValidatorFunction(validatorFunc, defaultMessage) {
1084
+ return (value, options) => {
1085
+ let validOptions;
1086
+ if (typeof options === 'string') {
1087
+ validOptions = { message: options, value: null };
1088
+ }
1089
+ else {
1090
+ validOptions = options || { message: defaultMessage, value: null };
1091
+ }
1092
+ if (!validatorFunc(value, validOptions.value)) {
1093
+ // Priority: validOptions.message -> defaultMessage -> fallback error
1094
+ let errorMessage = validOptions.message;
1095
+ // If message is empty or whitespace, use defaultMessage
1096
+ if (!errorMessage || !errorMessage.trim()) {
1097
+ errorMessage = defaultMessage;
1244
1098
  }
1245
- catch (error) {
1246
- console.warn(`Failed to precompile regex: ${pattern}`, error);
1099
+ // If defaultMessage is also empty, use fallback
1100
+ if (!errorMessage || !errorMessage.trim()) {
1101
+ errorMessage = 'ValidatorError: invalid arguments.';
1247
1102
  }
1248
- });
1249
- }
1250
- /**
1251
- * 获取缓存统计
1252
- */
1253
- getStats() {
1254
- return {
1255
- size: this.cache.size,
1256
- max: this.cache.max,
1257
- calculatedSize: this.cache.calculatedSize,
1103
+ throw new Error(errorMessage);
1104
+ }
1105
+ };
1106
+ }
1107
+ /**
1108
+ * Use functions or built-in rules for validation.
1109
+ * Throws error if validation fails.
1110
+ *
1111
+ * @export
1112
+ */
1113
+ const FunctionValidator = {
1114
+ IsNotEmpty: createValidatorFunction(ValidFuncs.IsNotEmpty, 'Value should not be empty'),
1115
+ IsDate: createValidatorFunction(ValidFuncs.IsDate, 'Must be a valid date'),
1116
+ IsEmail: createValidatorFunction(ValidFuncs.IsEmail, 'Must be a valid email'),
1117
+ IsIP: createValidatorFunction(ValidFuncs.IsIP, 'Must be a valid IP address'),
1118
+ IsPhoneNumber: createValidatorFunction(ValidFuncs.IsPhoneNumber, 'Must be a valid phone number'),
1119
+ IsUrl: createValidatorFunction(ValidFuncs.IsUrl, 'Must be a valid URL'),
1120
+ IsHash: createValidatorFunction(ValidFuncs.IsHash, 'Must be a valid hash'),
1121
+ IsCnName: createValidatorFunction(ValidFuncs.IsCnName, 'Must be a valid Chinese name'),
1122
+ IsIdNumber: createValidatorFunction(ValidFuncs.IsIdNumber, 'Must be a valid ID number'),
1123
+ IsZipCode: createValidatorFunction(ValidFuncs.IsZipCode, 'Must be a valid zip code'),
1124
+ IsMobile: createValidatorFunction(ValidFuncs.IsMobile, 'Must be a valid mobile number'),
1125
+ IsPlateNumber: createValidatorFunction(ValidFuncs.IsPlateNumber, 'Must be a valid plate number'),
1126
+ Equals: createValidatorFunction(ValidFuncs.Equals, 'Values must be equal'),
1127
+ NotEquals: createValidatorFunction(ValidFuncs.NotEquals, 'Values must not be equal'),
1128
+ Contains: createValidatorFunction(ValidFuncs.Contains, 'Value must contain specified substring'),
1129
+ IsIn: createValidatorFunction(ValidFuncs.IsIn, 'Value must be in the allowed list'),
1130
+ IsNotIn: createValidatorFunction(ValidFuncs.IsNotIn, 'Value must not be in the forbidden list'),
1131
+ Gt: createValidatorFunction(ValidFuncs.Gt, 'Value must be greater than threshold'),
1132
+ Lt: createValidatorFunction(ValidFuncs.Lt, 'Value must be less than threshold'),
1133
+ Gte: createValidatorFunction(ValidFuncs.Gte, 'Value must be greater than or equal to threshold'),
1134
+ Lte: createValidatorFunction(ValidFuncs.Lte, 'Value must be less than or equal to threshold'),
1135
+ };
1136
+
1137
+ /**
1138
+ * 装饰器工厂 - 消除装饰器代码重复
1139
+ * @author richen
1140
+ */
1141
+ /**
1142
+ * 创建验证装饰器的工厂函数
1143
+ * @param options 装饰器配置选项
1144
+ * @returns 装饰器工厂函数
1145
+ */
1146
+ function createValidationDecorator(options) {
1147
+ const { name, validator, defaultMessage, requiresValue = false } = options;
1148
+ return function decoratorFactory(...args) {
1149
+ // 处理参数:最后一个参数是ValidationOptions,前面是验证函数的参数
1150
+ const validationOptions = args[args.length - 1];
1151
+ const validatorArgs = requiresValue ? args.slice(0, -1) : [];
1152
+ return function propertyDecorator(object, propertyName) {
1153
+ // 设置属性为可导出
1154
+ setExpose(object, propertyName);
1155
+ // 注册验证装饰器
1156
+ classValidator.registerDecorator({
1157
+ name,
1158
+ target: object.constructor,
1159
+ propertyName,
1160
+ options: validationOptions,
1161
+ constraints: validatorArgs,
1162
+ validator: {
1163
+ validate(value) {
1164
+ try {
1165
+ return validator(value, ...validatorArgs);
1166
+ }
1167
+ catch {
1168
+ return false;
1169
+ }
1170
+ },
1171
+ defaultMessage(validationArguments) {
1172
+ const property = validationArguments.property;
1173
+ return defaultMessage
1174
+ ? defaultMessage.replace('$property', property)
1175
+ : `Invalid value for ${property}`;
1176
+ }
1177
+ }
1178
+ });
1258
1179
  };
1259
- }
1260
- /**
1261
- * 清空缓存
1262
- */
1263
- clear() {
1264
- this.cache.clear();
1265
- }
1180
+ };
1266
1181
  }
1267
1182
  /**
1268
- * 性能监控
1183
+ * 创建简单验证装饰器(不需要额外参数)
1184
+ * @param name 装饰器名称
1185
+ * @param validator 验证函数
1186
+ * @param defaultMessage 默认错误信息
1187
+ * @returns 装饰器函数
1269
1188
  */
1270
- class PerformanceMonitor {
1271
- constructor() {
1272
- this.metrics = new Map();
1273
- }
1274
- static getInstance() {
1275
- if (!PerformanceMonitor.instance) {
1276
- PerformanceMonitor.instance = new PerformanceMonitor();
1277
- }
1278
- return PerformanceMonitor.instance;
1189
+ function createSimpleDecorator(name, validator, defaultMessage) {
1190
+ return createValidationDecorator({
1191
+ name,
1192
+ validator,
1193
+ defaultMessage,
1194
+ requiresValue: false
1195
+ });
1196
+ }
1197
+ /**
1198
+ * 创建带参数的验证装饰器
1199
+ * @param name 装饰器名称
1200
+ * @param validator 验证函数
1201
+ * @param defaultMessage 默认错误信息
1202
+ * @returns 装饰器工厂函数
1203
+ */
1204
+ function createParameterizedDecorator(name, validator, defaultMessage) {
1205
+ return createValidationDecorator({
1206
+ name,
1207
+ validator,
1208
+ defaultMessage,
1209
+ requiresValue: true
1210
+ });
1211
+ }
1212
+
1213
+ /**
1214
+ * 改进的错误处理机制
1215
+ * @author richen
1216
+ */
1217
+ /**
1218
+ * 错误信息国际化
1219
+ */
1220
+ const ERROR_MESSAGES = {
1221
+ zh: {
1222
+ // 中国本土化验证
1223
+ IsCnName: '必须是有效的中文姓名',
1224
+ IsIdNumber: '必须是有效的身份证号码',
1225
+ IsZipCode: '必须是有效的邮政编码',
1226
+ IsMobile: '必须是有效的手机号码',
1227
+ IsPlateNumber: '必须是有效的车牌号码',
1228
+ // 基础验证
1229
+ IsNotEmpty: '不能为空',
1230
+ IsDate: '必须是有效的日期',
1231
+ IsEmail: '必须是有效的邮箱地址',
1232
+ IsIP: '必须是有效的IP地址',
1233
+ IsPhoneNumber: '必须是有效的电话号码',
1234
+ IsUrl: '必须是有效的URL地址',
1235
+ IsHash: '必须是有效的哈希值',
1236
+ // 比较验证
1237
+ Equals: '必须等于 {comparison}',
1238
+ NotEquals: '不能等于 {comparison}',
1239
+ Contains: '必须包含 {seed}',
1240
+ IsIn: '必须是以下值之一: {possibleValues}',
1241
+ IsNotIn: '不能是以下值之一: {possibleValues}',
1242
+ Gt: '必须大于 {min}',
1243
+ Gte: '必须大于或等于 {min}',
1244
+ Lt: '必须小于 {max}',
1245
+ Lte: '必须小于或等于 {max}',
1246
+ // 通用错误
1247
+ invalidParameter: '参数 {field} 无效',
1248
+ validationFailed: '验证失败',
1249
+ },
1250
+ en: {
1251
+ // Chinese localization validators
1252
+ IsCnName: 'must be a valid Chinese name',
1253
+ IsIdNumber: 'must be a valid ID number',
1254
+ IsZipCode: 'must be a valid zip code',
1255
+ IsMobile: 'must be a valid mobile number',
1256
+ IsPlateNumber: 'must be a valid plate number',
1257
+ // Basic validators
1258
+ IsNotEmpty: 'should not be empty',
1259
+ IsDate: 'must be a valid date',
1260
+ IsEmail: 'must be a valid email',
1261
+ IsIP: 'must be a valid IP address',
1262
+ IsPhoneNumber: 'must be a valid phone number',
1263
+ IsUrl: 'must be a valid URL',
1264
+ IsHash: 'must be a valid hash',
1265
+ // Comparison validators
1266
+ Equals: 'must equal to {comparison}',
1267
+ NotEquals: 'should not equal to {comparison}',
1268
+ Contains: 'must contain {seed}',
1269
+ IsIn: 'must be one of the following values: {possibleValues}',
1270
+ IsNotIn: 'should not be one of the following values: {possibleValues}',
1271
+ Gt: 'must be greater than {min}',
1272
+ Gte: 'must be greater than or equal to {min}',
1273
+ Lt: 'must be less than {max}',
1274
+ Lte: 'must be less than or equal to {max}',
1275
+ // Common errors
1276
+ invalidParameter: 'invalid parameter {field}',
1277
+ validationFailed: 'validation failed',
1279
1278
  }
1280
- /**
1281
- * 开始计时
1282
- */
1283
- startTimer(name) {
1284
- const start = performance.now();
1285
- return () => {
1286
- const duration = performance.now() - start;
1287
- this.recordMetric(name, duration);
1288
- };
1279
+ };
1280
+ /**
1281
+ * 增强的验证错误类
1282
+ */
1283
+ class KoattyValidationError extends Error {
1284
+ constructor(errors, message) {
1285
+ const errorMessage = message || 'Validation failed';
1286
+ super(errorMessage);
1287
+ this.name = 'KoattyValidationError';
1288
+ this.errors = errors;
1289
+ this.statusCode = 400;
1290
+ this.timestamp = new Date();
1291
+ // 确保正确的原型链
1292
+ Object.setPrototypeOf(this, KoattyValidationError.prototype);
1289
1293
  }
1290
1294
  /**
1291
- * 记录性能指标
1295
+ * 获取第一个错误信息
1292
1296
  */
1293
- recordMetric(name, duration) {
1294
- if (!this.metrics.has(name)) {
1295
- this.metrics.set(name, {
1296
- count: 0,
1297
- totalTime: 0,
1298
- avgTime: 0,
1299
- maxTime: 0,
1300
- minTime: Infinity,
1301
- lastExecutionTime: new Date(),
1302
- });
1303
- }
1304
- const metric = this.metrics.get(name);
1305
- metric.count++;
1306
- metric.totalTime += duration;
1307
- metric.avgTime = metric.totalTime / metric.count;
1308
- metric.maxTime = Math.max(metric.maxTime, duration);
1309
- metric.minTime = Math.min(metric.minTime, duration);
1310
- metric.lastExecutionTime = new Date();
1297
+ getFirstError() {
1298
+ return this.errors[0];
1311
1299
  }
1312
1300
  /**
1313
- * 获取性能报告
1301
+ * 获取指定字段的错误
1314
1302
  */
1315
- getReport() {
1316
- const report = {};
1317
- for (const [name, metric] of this.metrics) {
1318
- report[name] = {
1319
- ...metric,
1320
- minTime: metric.minTime === Infinity ? 0 : metric.minTime,
1321
- avgTimeFormatted: `${metric.avgTime.toFixed(2)}ms`,
1322
- totalTimeFormatted: `${metric.totalTime.toFixed(2)}ms`,
1323
- };
1324
- }
1325
- return report;
1303
+ getFieldErrors(field) {
1304
+ return this.errors.filter(error => error.field === field);
1326
1305
  }
1327
1306
  /**
1328
- * 获取热点分析(执行时间最长的操作)
1307
+ * 转换为JSON格式
1329
1308
  */
1330
- getHotspots(limit = 10) {
1331
- return Array.from(this.metrics.entries())
1332
- .map(([name, metric]) => ({
1333
- name,
1334
- avgTime: metric.avgTime,
1335
- count: metric.count,
1336
- }))
1337
- .sort((a, b) => b.avgTime - a.avgTime)
1338
- .slice(0, limit);
1309
+ toJSON() {
1310
+ return {
1311
+ name: this.name,
1312
+ message: this.message,
1313
+ statusCode: this.statusCode,
1314
+ timestamp: this.timestamp,
1315
+ errors: this.errors
1316
+ };
1317
+ }
1318
+ }
1319
+ /**
1320
+ * 错误信息格式化器
1321
+ */
1322
+ class ErrorMessageFormatter {
1323
+ constructor(language = 'zh') {
1324
+ this.language = 'zh';
1325
+ this.language = language;
1339
1326
  }
1340
1327
  /**
1341
- * 清空指标
1328
+ * 设置语言
1342
1329
  */
1343
- clear() {
1344
- this.metrics.clear();
1330
+ setLanguage(language) {
1331
+ this.language = language;
1345
1332
  }
1346
1333
  /**
1347
- * 导出性能数据为CSV格式
1334
+ * 格式化错误消息
1348
1335
  */
1349
- exportToCSV() {
1350
- const headers = ['Name', 'Count', 'Total Time (ms)', 'Avg Time (ms)', 'Max Time (ms)', 'Min Time (ms)', 'Last Execution'];
1351
- const rows = [headers.join(',')];
1352
- for (const [name, metric] of this.metrics) {
1353
- const row = [
1354
- name,
1355
- metric.count.toString(),
1356
- metric.totalTime.toFixed(2),
1357
- metric.avgTime.toFixed(2),
1358
- metric.maxTime.toFixed(2),
1359
- (metric.minTime === Infinity ? 0 : metric.minTime).toFixed(2),
1360
- metric.lastExecutionTime.toISOString(),
1361
- ];
1362
- rows.push(row.join(','));
1336
+ formatMessage(constraint, field, value, context) {
1337
+ const messages = ERROR_MESSAGES[this.language];
1338
+ let template = messages[constraint] || messages.invalidParameter;
1339
+ // 替换占位符
1340
+ template = template.replace('{field}', field);
1341
+ // 优先使用上下文中的值,然后是传入的value
1342
+ if (context) {
1343
+ Object.entries(context).forEach(([key, val]) => {
1344
+ template = template.replace(`{${key}}`, this.formatValue(val));
1345
+ });
1363
1346
  }
1364
- return rows.join('\n');
1347
+ // 如果还有{value}占位符且传入了value,则替换
1348
+ if (value !== undefined && template.includes('{value}')) {
1349
+ template = template.replace('{value}', this.formatValue(value));
1350
+ }
1351
+ return template;
1365
1352
  }
1366
- }
1367
- // 导出单例实例
1368
- const metadataCache = MetadataCache.getInstance();
1369
- const validationCache = ValidationCache.getInstance();
1370
- const regexCache = RegexCache.getInstance();
1371
- const performanceMonitor = PerformanceMonitor.getInstance();
1372
- /**
1373
- * 缓存装饰器 - 用于缓存验证函数结果
1374
- */
1375
- function cached(validator, ttl) {
1376
- return function (target, propertyName, descriptor) {
1377
- const originalMethod = descriptor.value;
1378
- descriptor.value = function (...args) {
1379
- const value = args[0];
1380
- const additionalArgs = args.slice(1);
1381
- // 尝试从缓存获取结果
1382
- const cachedResult = validationCache.get(validator, value, ...additionalArgs);
1383
- if (cachedResult !== undefined) {
1384
- return cachedResult;
1385
- }
1386
- // 执行验证并缓存结果
1387
- const endTimer = performanceMonitor.startTimer(validator);
1353
+ /**
1354
+ * 格式化值用于消息显示
1355
+ * @private
1356
+ */
1357
+ formatValue(value) {
1358
+ if (value === null)
1359
+ return 'null';
1360
+ if (value === undefined)
1361
+ return 'undefined';
1362
+ if (typeof value === 'number')
1363
+ return String(value);
1364
+ if (typeof value === 'string')
1365
+ return `"${value}"`;
1366
+ if (Array.isArray(value))
1367
+ return `[${value.map(v => this.formatValue(v)).join(', ')}]`;
1368
+ if (typeof value === 'object') {
1388
1369
  try {
1389
- const result = originalMethod.apply(this, args);
1390
- validationCache.set(validator, value, result, ...additionalArgs);
1391
- // 如果指定了TTL,设置过期时间
1392
- if (ttl && ttl > 0) {
1393
- validationCache.setTTL(validator, value, ttl, ...additionalArgs);
1394
- }
1395
- return result;
1370
+ return JSON.stringify(value);
1396
1371
  }
1397
- finally {
1398
- endTimer();
1372
+ catch {
1373
+ // 处理循环引用
1374
+ return '[Circular Reference]';
1399
1375
  }
1400
- };
1401
- return descriptor;
1402
- };
1376
+ }
1377
+ return String(value);
1378
+ }
1403
1379
  }
1404
1380
  /**
1405
- * 获取所有缓存统计信息
1381
+ * 全局错误信息格式化器实例
1406
1382
  */
1407
- function getAllCacheStats() {
1383
+ const errorFormatter = new ErrorMessageFormatter();
1384
+ /**
1385
+ * 设置全局语言
1386
+ */
1387
+ function setValidationLanguage(language) {
1388
+ errorFormatter.setLanguage(language);
1389
+ }
1390
+ /**
1391
+ * 创建验证错误
1392
+ */
1393
+ function createValidationError(field, value, constraint, customMessage, context) {
1394
+ const message = customMessage || errorFormatter.formatMessage(constraint, field, value, context);
1408
1395
  return {
1409
- validation: validationCache.getStats(),
1410
- regex: regexCache.getStats(),
1411
- performance: performanceMonitor.getReport(),
1412
- hotspots: performanceMonitor.getHotspots(),
1396
+ field,
1397
+ value,
1398
+ constraint,
1399
+ message,
1400
+ context
1413
1401
  };
1414
1402
  }
1415
1403
  /**
1416
- * 预热缓存 - 预编译常用正则表达式
1404
+ * 批量创建验证错误
1417
1405
  */
1418
- function warmupCaches() {
1419
- // 预编译中文验证相关的正则表达式
1420
- const commonPatterns = [
1421
- { pattern: '^[\u4e00-\u9fa5]{2,8}$' }, // 中文姓名
1422
- { pattern: '^1[3-9]\\d{9}$' }, // 手机号
1423
- { pattern: '^\\d{6}$' }, // 邮政编码
1424
- { pattern: '^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼]' }, // 车牌号开头
1425
- ];
1426
- regexCache.precompile(commonPatterns);
1406
+ function createValidationErrors(errors) {
1407
+ const validationErrors = errors.map(error => createValidationError(error.field, error.value, error.constraint, error.message, error.context));
1408
+ return new KoattyValidationError(validationErrors);
1427
1409
  }
1410
+
1428
1411
  /**
1429
- * 清空所有缓存
1412
+ * 重构后的装饰器定义 - 使用工厂函数消除重复
1413
+ * @author richen
1430
1414
  */
1431
- function clearAllCaches() {
1432
- validationCache.clear();
1433
- regexCache.clear();
1434
- performanceMonitor.clear();
1415
+ // 中国本土化验证装饰器
1416
+ const IsCnName = createSimpleDecorator('IsCnName', (value) => helper__namespace.isString(value) && cnName(value), 'must be a valid Chinese name');
1417
+ const IsIdNumber = createSimpleDecorator('IsIdNumber', (value) => helper__namespace.isString(value) && idNumber(value), 'must be a valid ID number');
1418
+ const IsZipCode = createSimpleDecorator('IsZipCode', (value) => helper__namespace.isString(value) && zipCode(value), 'must be a valid zip code');
1419
+ const IsMobile = createSimpleDecorator('IsMobile', (value) => helper__namespace.isString(value) && mobile(value), 'must be a valid mobile number');
1420
+ const IsPlateNumber = createSimpleDecorator('IsPlateNumber', (value) => helper__namespace.isString(value) && plateNumber(value), 'must be a valid plate number');
1421
+ // 基础验证装饰器
1422
+ const IsNotEmpty = createSimpleDecorator('IsNotEmpty', (value) => !helper__namespace.isEmpty(value), 'should not be empty');
1423
+ const IsDate = createSimpleDecorator('IsDate', (value) => helper__namespace.isDate(value), 'must be a valid date');
1424
+ // 带参数的验证装饰器
1425
+ const Equals = createParameterizedDecorator('Equals', (value, comparison) => value === comparison, 'must equal to $constraint1');
1426
+ const NotEquals = createParameterizedDecorator('NotEquals', (value, comparison) => value !== comparison, 'should not equal to $constraint1');
1427
+ const Contains = createParameterizedDecorator('Contains', (value, seed) => helper__namespace.isString(value) && value.includes(seed), 'must contain $constraint1');
1428
+ const IsIn = createParameterizedDecorator('IsIn', (value, possibleValues) => possibleValues.includes(value), 'must be one of the following values: $constraint1');
1429
+ const IsNotIn = createParameterizedDecorator('IsNotIn', (value, possibleValues) => !possibleValues.includes(value), 'should not be one of the following values: $constraint1');
1430
+ // 数值比较装饰器
1431
+ const Gt = createParameterizedDecorator('Gt', (value, min) => helper__namespace.toNumber(value) > min, 'must be greater than $constraint1');
1432
+ const Gte = createParameterizedDecorator('Gte', (value, min) => helper__namespace.toNumber(value) >= min, 'must be greater than or equal to $constraint1');
1433
+ const Lt = createParameterizedDecorator('Lt', (value, max) => helper__namespace.toNumber(value) < max, 'must be less than $constraint1');
1434
+ const Lte = createParameterizedDecorator('Lte', (value, max) => helper__namespace.toNumber(value) <= max, 'must be less than or equal to $constraint1');
1435
+ // 复杂验证装饰器(需要特殊处理)
1436
+ function IsEmail(options, validationOptions) {
1437
+ return createParameterizedDecorator('IsEmail', (value) => classValidator.isEmail(value, options), 'must be a valid email')(validationOptions);
1435
1438
  }
1439
+ function IsIP(version, validationOptions) {
1440
+ return createParameterizedDecorator('IsIP', (value) => classValidator.isIP(value, version), 'must be a valid IP address')(validationOptions);
1441
+ }
1442
+ function IsPhoneNumber(region, validationOptions) {
1443
+ return createParameterizedDecorator('IsPhoneNumber', (value) => classValidator.isPhoneNumber(value, region), 'must be a valid phone number')(validationOptions);
1444
+ }
1445
+ function IsUrl(options, validationOptions) {
1446
+ return createParameterizedDecorator('IsUrl', (value) => classValidator.isURL(value, options), 'must be a valid URL')(validationOptions);
1447
+ }
1448
+ function IsHash(algorithm, validationOptions) {
1449
+ return createParameterizedDecorator('IsHash', (value) => classValidator.isHash(value, algorithm), 'must be a valid hash')(validationOptions);
1450
+ }
1451
+ // 基础工具装饰器(从原始decorator.ts移植)
1436
1452
  /**
1437
- * 配置缓存设置
1453
+ * 标记属性为可导出
1438
1454
  */
1439
- function configureCaches(options) {
1440
- if (options.validation) {
1441
- // 重新创建验证缓存实例
1442
- ValidationCache.instance = new ValidationCache(options.validation);
1443
- }
1444
- if (options.regex) {
1445
- // 重新创建正则缓存实例
1446
- RegexCache.instance = new RegexCache(options.regex);
1447
- }
1455
+ function Expose() {
1456
+ return function (object, propertyName) {
1457
+ setExpose(object, propertyName);
1458
+ };
1459
+ }
1460
+ /**
1461
+ * Expose的别名
1462
+ */
1463
+ function IsDefined() {
1464
+ return function (object, propertyName) {
1465
+ setExpose(object, propertyName);
1466
+ };
1467
+ }
1468
+ /**
1469
+ * 参数验证装饰器
1470
+ */
1471
+ function Valid(rule, options) {
1472
+ return function (object, propertyName, parameterIndex) {
1473
+ // 这里保持与原始实现一致
1474
+ const existingRules = Reflect.getOwnMetadata("validate", object, propertyName) || {};
1475
+ existingRules[parameterIndex] = { rule, options };
1476
+ Reflect.defineMetadata("validate", existingRules, object, propertyName);
1477
+ };
1478
+ }
1479
+ /**
1480
+ * 方法验证装饰器
1481
+ * 自动验证方法参数中的 DTO 对象
1482
+ */
1483
+ function Validated() {
1484
+ return function (object, propertyName, descriptor) {
1485
+ const originalMethod = descriptor.value;
1486
+ descriptor.value = async function (...args) {
1487
+ // 获取参数类型元数据
1488
+ const paramTypes = Reflect.getMetadata('design:paramtypes', object, propertyName) || [];
1489
+ // 验证每个参数
1490
+ for (let i = 0; i < args.length; i++) {
1491
+ const arg = args[i];
1492
+ const paramType = paramTypes[i];
1493
+ // 如果是类类型且不是基础类型,执行验证
1494
+ if (paramType && typeof paramType === 'function' &&
1495
+ paramType !== String && paramType !== Number &&
1496
+ paramType !== Boolean && paramType !== Array &&
1497
+ paramType !== Object && paramType !== Date) {
1498
+ try {
1499
+ // 如果参数不是目标类型的实例,转换为实例
1500
+ let validationTarget = arg;
1501
+ if (!(arg instanceof paramType)) {
1502
+ validationTarget = Object.assign(new paramType(), arg);
1503
+ }
1504
+ const errors = await classValidator.validate(validationTarget);
1505
+ if (errors.length > 0) {
1506
+ throw createValidationErrors(errors.map(e => ({
1507
+ field: e.property,
1508
+ value: e.value,
1509
+ constraint: Object.keys(e.constraints || {})[0] || 'unknown',
1510
+ message: Object.values(e.constraints || {})[0] || 'Validation failed',
1511
+ context: e.constraints
1512
+ })));
1513
+ }
1514
+ }
1515
+ catch (error) {
1516
+ // 如果验证失败,重新抛出错误
1517
+ throw error;
1518
+ }
1519
+ }
1520
+ }
1521
+ // 执行原始方法
1522
+ return originalMethod.apply(this, args);
1523
+ };
1524
+ return descriptor;
1525
+ };
1448
1526
  }
1449
1527
 
1450
1528
  exports.ClassValidator = ClassValidator;
@@ -1478,6 +1556,7 @@ exports.Lte = Lte;
1478
1556
  exports.NotEquals = NotEquals;
1479
1557
  exports.PARAM_CHECK_KEY = PARAM_CHECK_KEY;
1480
1558
  exports.PARAM_RULE_KEY = PARAM_RULE_KEY;
1559
+ exports.PARAM_TYPE_KEY = PARAM_TYPE_KEY;
1481
1560
  exports.Valid = Valid;
1482
1561
  exports.ValidFuncs = ValidFuncs;
1483
1562
  exports.Validated = Validated;