koatty_validation 1.6.0 → 1.6.3

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-10-23 01:25:03
3
+ * @Date: 2026-01-28 12:40:10
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
@@ -33,22 +33,22 @@ function _interopNamespaceDefault(e) {
33
33
  var helper__namespace = /*#__PURE__*/_interopNamespaceDefault(helper);
34
34
 
35
35
  /**
36
- * koatty_validation 类型定义
36
+ * koatty_validation type definitions
37
37
  * @author richen
38
38
  * @copyright Copyright (c) - <richenlin(at)gmail.com>
39
39
  * @license MIT
40
40
  */
41
41
  /**
42
- * 参数类型键常量
42
+ * Parameter type key constant
43
43
  */
44
44
  const PARAM_TYPE_KEY = 'PARAM_TYPE_KEY';
45
45
 
46
46
  /**
47
- * 性能缓存模块 - 提供多层次缓存和性能监控
47
+ * Performance cache module - Provides multi-level caching and performance monitoring
48
48
  * @author richen
49
49
  */
50
50
  /**
51
- * 元数据缓存
51
+ * Metadata cache
52
52
  */
53
53
  class MetadataCache {
54
54
  constructor() {
@@ -61,7 +61,7 @@ class MetadataCache {
61
61
  return MetadataCache.instance;
62
62
  }
63
63
  /**
64
- * 获取类的元数据缓存
64
+ * Get metadata cache for a class
65
65
  */
66
66
  getClassCache(target) {
67
67
  if (!this.cache.has(target)) {
@@ -70,28 +70,28 @@ class MetadataCache {
70
70
  return this.cache.get(target);
71
71
  }
72
72
  /**
73
- * 缓存元数据
73
+ * Cache metadata
74
74
  */
75
75
  setMetadata(target, key, value) {
76
76
  const classCache = this.getClassCache(target);
77
77
  classCache.set(key, value);
78
78
  }
79
79
  /**
80
- * 获取缓存的元数据
80
+ * Get cached metadata
81
81
  */
82
82
  getMetadata(target, key) {
83
83
  const classCache = this.getClassCache(target);
84
84
  return classCache.get(key);
85
85
  }
86
86
  /**
87
- * 检查是否已缓存
87
+ * Check if metadata is cached
88
88
  */
89
89
  hasMetadata(target, key) {
90
90
  const classCache = this.getClassCache(target);
91
91
  return classCache.has(key);
92
92
  }
93
93
  /**
94
- * 清空指定类的缓存
94
+ * Clear cache for a specific class
95
95
  */
96
96
  clearClassCache(target) {
97
97
  if (this.cache.has(target)) {
@@ -100,7 +100,7 @@ class MetadataCache {
100
100
  }
101
101
  }
102
102
  /**
103
- * 验证结果缓存
103
+ * Validation result cache
104
104
  */
105
105
  class ValidationCache {
106
106
  constructor(options) {
@@ -108,7 +108,7 @@ class ValidationCache {
108
108
  this.misses = 0;
109
109
  this.cache = new lruCache.LRUCache({
110
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分钟
111
+ ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 10, // 10 minutes
112
112
  allowStale: (options === null || options === void 0 ? void 0 : options.allowStale) || false,
113
113
  updateAgeOnGet: (options === null || options === void 0 ? void 0 : options.updateAgeOnGet) || true,
114
114
  });
@@ -226,7 +226,7 @@ class RegexCache {
226
226
  constructor(options) {
227
227
  this.cache = new lruCache.LRUCache({
228
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分钟
229
+ ttl: (options === null || options === void 0 ? void 0 : options.ttl) || 1000 * 60 * 30, // 30 minutes
230
230
  allowStale: (options === null || options === void 0 ? void 0 : options.allowStale) || false,
231
231
  updateAgeOnGet: (options === null || options === void 0 ? void 0 : options.updateAgeOnGet) || true,
232
232
  });
@@ -410,7 +410,7 @@ function cached(validator, ttl) {
410
410
  try {
411
411
  const result = originalMethod.apply(this, args);
412
412
  validationCache.set(validator, value, result, ...additionalArgs);
413
- // 如果指定了TTL,设置过期时间
413
+ // If TTL is specified, set expiration time
414
414
  if (ttl && ttl > 0) {
415
415
  validationCache.setTTL(validator, value, ttl, ...additionalArgs);
416
416
  }
@@ -820,6 +820,209 @@ function plateNumber(value) {
820
820
  }
821
821
  }
822
822
 
823
+ /**
824
+ * Improved error handling mechanism
825
+ * @author richen
826
+ */
827
+ /**
828
+ * Error message internationalization
829
+ */
830
+ const ERROR_MESSAGES = {
831
+ zh: {
832
+ // Chinese localization validation
833
+ IsCnName: '必须是有效的中文姓名',
834
+ IsIdNumber: '必须是有效的身份证号码',
835
+ IsZipCode: '必须是有效的邮政编码',
836
+ IsMobile: '必须是有效的手机号码',
837
+ IsPlateNumber: '必须是有效的车牌号码',
838
+ // Basic validation
839
+ IsNotEmpty: '不能为空',
840
+ IsDate: '必须是有效的日期',
841
+ IsEmail: '必须是有效的邮箱地址',
842
+ IsIP: '必须是有效的IP地址',
843
+ IsPhoneNumber: '必须是有效的电话号码',
844
+ IsUrl: '必须是有效的URL地址',
845
+ IsHash: '必须是有效的哈希值',
846
+ // Comparison validation
847
+ Equals: '必须等于 {comparison}',
848
+ NotEquals: '不能等于 {comparison}',
849
+ Contains: '必须包含 {seed}',
850
+ IsIn: '必须是以下值之一: {possibleValues}',
851
+ IsNotIn: '不能是以下值之一: {possibleValues}',
852
+ Gt: '必须大于 {min}',
853
+ Gte: '必须大于或等于 {min}',
854
+ Lt: '必须小于 {max}',
855
+ Lte: '必须小于或等于 {max}',
856
+ // Common errors
857
+ invalidParameter: '参数 {field} 无效',
858
+ validationFailed: '验证失败',
859
+ },
860
+ en: {
861
+ // Chinese localization validators
862
+ IsCnName: 'must be a valid Chinese name',
863
+ IsIdNumber: 'must be a valid ID number',
864
+ IsZipCode: 'must be a valid zip code',
865
+ IsMobile: 'must be a valid mobile number',
866
+ IsPlateNumber: 'must be a valid plate number',
867
+ // Basic validators
868
+ IsNotEmpty: 'should not be empty',
869
+ IsDate: 'must be a valid date',
870
+ IsEmail: 'must be a valid email',
871
+ IsIP: 'must be a valid IP address',
872
+ IsPhoneNumber: 'must be a valid phone number',
873
+ IsUrl: 'must be a valid URL',
874
+ IsHash: 'must be a valid hash',
875
+ // Comparison validators
876
+ Equals: 'must equal to {comparison}',
877
+ NotEquals: 'should not equal to {comparison}',
878
+ Contains: 'must contain {seed}',
879
+ IsIn: 'must be one of the following values: {possibleValues}',
880
+ IsNotIn: 'should not be one of the following values: {possibleValues}',
881
+ Gt: 'must be greater than {min}',
882
+ Gte: 'must be greater than or equal to {min}',
883
+ Lt: 'must be less than {max}',
884
+ Lte: 'must be less than or equal to {max}',
885
+ // Common errors
886
+ invalidParameter: 'invalid parameter {field}',
887
+ validationFailed: 'validation failed',
888
+ }
889
+ };
890
+ /**
891
+ * Enhanced validation error class
892
+ */
893
+ class KoattyValidationError extends Error {
894
+ constructor(errors, message) {
895
+ const errorMessage = message || 'Validation failed';
896
+ super(errorMessage);
897
+ this.name = 'KoattyValidationError';
898
+ this.errors = errors;
899
+ this.statusCode = 400;
900
+ this.timestamp = new Date();
901
+ // Ensure correct prototype chain
902
+ Object.setPrototypeOf(this, KoattyValidationError.prototype);
903
+ }
904
+ /**
905
+ * Get the first error message
906
+ */
907
+ getFirstError() {
908
+ return this.errors[0];
909
+ }
910
+ /**
911
+ * Get errors for a specific field
912
+ */
913
+ getFieldErrors(field) {
914
+ return this.errors.filter(error => error.field === field);
915
+ }
916
+ /**
917
+ * Convert to JSON format
918
+ */
919
+ toJSON() {
920
+ return {
921
+ name: this.name,
922
+ message: this.message,
923
+ statusCode: this.statusCode,
924
+ timestamp: this.timestamp,
925
+ errors: this.errors
926
+ };
927
+ }
928
+ }
929
+ /**
930
+ * Error message formatter
931
+ */
932
+ class ErrorMessageFormatter {
933
+ constructor(language = 'zh') {
934
+ this.language = 'zh';
935
+ this.language = language;
936
+ }
937
+ /**
938
+ * Set language
939
+ */
940
+ setLanguage(language) {
941
+ this.language = language;
942
+ }
943
+ /**
944
+ * Format error message
945
+ */
946
+ formatMessage(constraint, field, value, context) {
947
+ const messages = ERROR_MESSAGES[this.language];
948
+ let template = messages[constraint] || messages.invalidParameter;
949
+ // Replace placeholders
950
+ template = template.replace('{field}', field);
951
+ // Prioritize values from context, then use passed value
952
+ if (context) {
953
+ Object.entries(context).forEach(([key, val]) => {
954
+ template = template.replace(`{${key}}`, this.formatValue(val));
955
+ });
956
+ }
957
+ // If there's still a {value} placeholder and value was passed, replace it
958
+ if (value !== undefined && template.includes('{value}')) {
959
+ template = template.replace('{value}', this.formatValue(value));
960
+ }
961
+ return template;
962
+ }
963
+ /**
964
+ * Format value for message display
965
+ * @private
966
+ */
967
+ formatValue(value) {
968
+ if (value === null)
969
+ return 'null';
970
+ if (value === undefined)
971
+ return 'undefined';
972
+ if (typeof value === 'number')
973
+ return String(value);
974
+ if (typeof value === 'string')
975
+ return `"${value}"`;
976
+ if (Array.isArray(value))
977
+ return `[${value.map(v => this.formatValue(v)).join(', ')}]`;
978
+ if (typeof value === 'object') {
979
+ try {
980
+ return JSON.stringify(value);
981
+ }
982
+ catch {
983
+ // Handle circular references
984
+ return '[Circular Reference]';
985
+ }
986
+ }
987
+ return String(value);
988
+ }
989
+ }
990
+ /**
991
+ * Global error message formatter instance
992
+ */
993
+ const errorFormatter = new ErrorMessageFormatter();
994
+ /**
995
+ * Set global language
996
+ */
997
+ function setValidationLanguage(language) {
998
+ errorFormatter.setLanguage(language);
999
+ }
1000
+ /**
1001
+ * Create validation error
1002
+ */
1003
+ function createValidationError(field, value, constraint, customMessage, context) {
1004
+ const message = customMessage || errorFormatter.formatMessage(constraint, field, value, context);
1005
+ return {
1006
+ field,
1007
+ value,
1008
+ constraint,
1009
+ message,
1010
+ context
1011
+ };
1012
+ }
1013
+ /**
1014
+ * Create validation errors in batch
1015
+ */
1016
+ function createValidationErrors(errors, separator = '; ') {
1017
+ const validationErrors = errors.map(error => createValidationError(error.field, error.value, error.constraint, error.message, error.context));
1018
+ // Generate combined message from all errors
1019
+ const combinedMessage = validationErrors
1020
+ .map(err => err.message)
1021
+ .filter(msg => msg && msg.trim())
1022
+ .join(separator) || 'Validation failed';
1023
+ return new KoattyValidationError(validationErrors, combinedMessage);
1024
+ }
1025
+
823
1026
  /*
824
1027
  * @Description:
825
1028
  * @Usage:
@@ -879,10 +1082,11 @@ class ValidateClass {
879
1082
  * @param {*} Clazz
880
1083
  * @param {*} data
881
1084
  * @param {boolean} [convert=false] auto convert parameters type
1085
+ * @param {ValidationOptions} [options] validation options (returnAllErrors, errorSeparator)
882
1086
  * @returns {Promise<any>}
883
1087
  * @memberof ValidateClass
884
1088
  */
885
- async valid(Clazz, data, convert = false) {
1089
+ async valid(Clazz, data, convert = false, options) {
886
1090
  let obj = {};
887
1091
  if (data instanceof Clazz) {
888
1092
  obj = data;
@@ -898,7 +1102,21 @@ class ValidateClass {
898
1102
  errors = await classValidator.validate(obj, { skipMissingProperties: true });
899
1103
  }
900
1104
  if (errors.length > 0) {
901
- throw new Error(Object.values(errors[0].constraints)[0]);
1105
+ // Check if user wants all errors or just the first one
1106
+ if (options === null || options === void 0 ? void 0 : options.returnAllErrors) {
1107
+ // Throw KoattyValidationError with all error details
1108
+ throw createValidationErrors(errors.map(e => ({
1109
+ field: e.property,
1110
+ value: e.value,
1111
+ constraint: Object.keys(e.constraints || {})[0] || 'unknown',
1112
+ message: Object.values(e.constraints || {})[0] || 'Validation failed',
1113
+ context: e.constraints
1114
+ })), options.errorSeparator || '; ');
1115
+ }
1116
+ else {
1117
+ // Default behavior (backward compatible): return only first error
1118
+ throw new Error(Object.values(errors[0].constraints)[0]);
1119
+ }
902
1120
  }
903
1121
  return obj;
904
1122
  }
@@ -1135,24 +1353,24 @@ const FunctionValidator = {
1135
1353
  };
1136
1354
 
1137
1355
  /**
1138
- * 装饰器工厂 - 消除装饰器代码重复
1356
+ * Decorator Factory - Eliminate decorator code duplication
1139
1357
  * @author richen
1140
1358
  */
1141
1359
  /**
1142
- * 创建验证装饰器的工厂函数
1143
- * @param options 装饰器配置选项
1144
- * @returns 装饰器工厂函数
1360
+ * Factory function to create validation decorators
1361
+ * @param options Decorator configuration options
1362
+ * @returns Decorator factory function
1145
1363
  */
1146
1364
  function createValidationDecorator(options) {
1147
1365
  const { name, validator, defaultMessage, requiresValue = false } = options;
1148
1366
  return function decoratorFactory(...args) {
1149
- // 处理参数:最后一个参数是ValidationOptions,前面是验证函数的参数
1367
+ // Handle parameters: last parameter is ValidationOptions, previous ones are validator function parameters
1150
1368
  const validationOptions = args[args.length - 1];
1151
1369
  const validatorArgs = requiresValue ? args.slice(0, -1) : [];
1152
1370
  return function propertyDecorator(object, propertyName) {
1153
- // 设置属性为可导出
1371
+ // Set property as exportable
1154
1372
  setExpose(object, propertyName);
1155
- // 注册验证装饰器
1373
+ // Register validation decorator
1156
1374
  classValidator.registerDecorator({
1157
1375
  name,
1158
1376
  target: object.constructor,
@@ -1170,8 +1388,9 @@ function createValidationDecorator(options) {
1170
1388
  },
1171
1389
  defaultMessage(validationArguments) {
1172
1390
  const property = validationArguments.property;
1173
- return defaultMessage
1174
- ? defaultMessage.replace('$property', property)
1391
+ const customMessage = (validationOptions === null || validationOptions === void 0 ? void 0 : validationOptions.message) || defaultMessage;
1392
+ return customMessage
1393
+ ? customMessage.replace('$property', property)
1175
1394
  : `Invalid value for ${property}`;
1176
1395
  }
1177
1396
  }
@@ -1180,11 +1399,11 @@ function createValidationDecorator(options) {
1180
1399
  };
1181
1400
  }
1182
1401
  /**
1183
- * 创建简单验证装饰器(不需要额外参数)
1184
- * @param name 装饰器名称
1185
- * @param validator 验证函数
1186
- * @param defaultMessage 默认错误信息
1187
- * @returns 装饰器函数
1402
+ * Create simple validation decorator (no additional parameters required)
1403
+ * @param name Decorator name
1404
+ * @param validator Validation function
1405
+ * @param defaultMessage Default error message
1406
+ * @returns Decorator function
1188
1407
  */
1189
1408
  function createSimpleDecorator(name, validator, defaultMessage) {
1190
1409
  return createValidationDecorator({
@@ -1195,11 +1414,11 @@ function createSimpleDecorator(name, validator, defaultMessage) {
1195
1414
  });
1196
1415
  }
1197
1416
  /**
1198
- * 创建带参数的验证装饰器
1199
- * @param name 装饰器名称
1200
- * @param validator 验证函数
1201
- * @param defaultMessage 默认错误信息
1202
- * @returns 装饰器工厂函数
1417
+ * Create parameterized validation decorator
1418
+ * @param name Decorator name
1419
+ * @param validator Validation function
1420
+ * @param defaultMessage Default error message
1421
+ * @returns Decorator factory function
1203
1422
  */
1204
1423
  function createParameterizedDecorator(name, validator, defaultMessage) {
1205
1424
  return createValidationDecorator({
@@ -1211,217 +1430,19 @@ function createParameterizedDecorator(name, validator, defaultMessage) {
1211
1430
  }
1212
1431
 
1213
1432
  /**
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',
1278
- }
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);
1293
- }
1294
- /**
1295
- * 获取第一个错误信息
1296
- */
1297
- getFirstError() {
1298
- return this.errors[0];
1299
- }
1300
- /**
1301
- * 获取指定字段的错误
1302
- */
1303
- getFieldErrors(field) {
1304
- return this.errors.filter(error => error.field === field);
1305
- }
1306
- /**
1307
- * 转换为JSON格式
1308
- */
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;
1326
- }
1327
- /**
1328
- * 设置语言
1329
- */
1330
- setLanguage(language) {
1331
- this.language = language;
1332
- }
1333
- /**
1334
- * 格式化错误消息
1335
- */
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
- });
1346
- }
1347
- // 如果还有{value}占位符且传入了value,则替换
1348
- if (value !== undefined && template.includes('{value}')) {
1349
- template = template.replace('{value}', this.formatValue(value));
1350
- }
1351
- return template;
1352
- }
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') {
1369
- try {
1370
- return JSON.stringify(value);
1371
- }
1372
- catch {
1373
- // 处理循环引用
1374
- return '[Circular Reference]';
1375
- }
1376
- }
1377
- return String(value);
1378
- }
1379
- }
1380
- /**
1381
- * 全局错误信息格式化器实例
1382
- */
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);
1395
- return {
1396
- field,
1397
- value,
1398
- constraint,
1399
- message,
1400
- context
1401
- };
1402
- }
1403
- /**
1404
- * 批量创建验证错误
1405
- */
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);
1409
- }
1410
-
1411
- /**
1412
- * 重构后的装饰器定义 - 使用工厂函数消除重复
1433
+ * Refactored decorator definitions - Using factory functions to eliminate duplication
1413
1434
  * @author richen
1414
1435
  */
1415
- // 中国本土化验证装饰器
1436
+ // Chinese localization validation decorators
1416
1437
  const IsCnName = createSimpleDecorator('IsCnName', (value) => helper__namespace.isString(value) && cnName(value), 'must be a valid Chinese name');
1417
1438
  const IsIdNumber = createSimpleDecorator('IsIdNumber', (value) => helper__namespace.isString(value) && idNumber(value), 'must be a valid ID number');
1418
1439
  const IsZipCode = createSimpleDecorator('IsZipCode', (value) => helper__namespace.isString(value) && zipCode(value), 'must be a valid zip code');
1419
1440
  const IsMobile = createSimpleDecorator('IsMobile', (value) => helper__namespace.isString(value) && mobile(value), 'must be a valid mobile number');
1420
1441
  const IsPlateNumber = createSimpleDecorator('IsPlateNumber', (value) => helper__namespace.isString(value) && plateNumber(value), 'must be a valid plate number');
1421
- // 基础验证装饰器
1442
+ // Basic validation decorators
1422
1443
  const IsNotEmpty = createSimpleDecorator('IsNotEmpty', (value) => !helper__namespace.isEmpty(value), 'should not be empty');
1423
1444
  const IsDate = createSimpleDecorator('IsDate', (value) => helper__namespace.isDate(value), 'must be a valid date');
1424
- // 带参数的验证装饰器
1445
+ // Parameterized validation decorators
1425
1446
  const Equals = createParameterizedDecorator('Equals', (value, comparison) => value === comparison, 'must equal to $constraint1');
1426
1447
  const NotEquals = createParameterizedDecorator('NotEquals', (value, comparison) => value !== comparison, 'should not equal to $constraint1');
1427
1448
  const Contains = createParameterizedDecorator('Contains', (value, seed) => helper__namespace.isString(value) && value.includes(seed), 'must contain $constraint1');
@@ -1434,7 +1455,10 @@ const Lt = createParameterizedDecorator('Lt', (value, max) => helper__namespace.
1434
1455
  const Lte = createParameterizedDecorator('Lte', (value, max) => helper__namespace.toNumber(value) <= max, 'must be less than or equal to $constraint1');
1435
1456
  // 复杂验证装饰器(需要特殊处理)
1436
1457
  function IsEmail(options, validationOptions) {
1437
- return createParameterizedDecorator('IsEmail', (value) => classValidator.isEmail(value, options), 'must be a valid email')(validationOptions);
1458
+ // Handle case where options is actually ValidationOptions (message only)
1459
+ const actualOptions = (options === null || options === void 0 ? void 0 : options.message) ? {} : options;
1460
+ const actualValidationOptions = (options === null || options === void 0 ? void 0 : options.message) ? options : validationOptions;
1461
+ return createParameterizedDecorator('IsEmail', (value) => classValidator.isEmail(value, actualOptions), 'must be a valid email')(actualValidationOptions);
1438
1462
  }
1439
1463
  function IsIP(version, validationOptions) {
1440
1464
  return createParameterizedDecorator('IsIP', (value) => classValidator.isIP(value, version), 'must be a valid IP address')(validationOptions);
@@ -1448,9 +1472,9 @@ function IsUrl(options, validationOptions) {
1448
1472
  function IsHash(algorithm, validationOptions) {
1449
1473
  return createParameterizedDecorator('IsHash', (value) => classValidator.isHash(value, algorithm), 'must be a valid hash')(validationOptions);
1450
1474
  }
1451
- // 基础工具装饰器(从原始decorator.ts移植)
1475
+ // Basic utility decorators (migrated from original decorator.ts)
1452
1476
  /**
1453
- * 标记属性为可导出
1477
+ * Mark property as exportable
1454
1478
  */
1455
1479
  function Expose() {
1456
1480
  return function (object, propertyName) {
@@ -1458,7 +1482,7 @@ function Expose() {
1458
1482
  };
1459
1483
  }
1460
1484
  /**
1461
- * Expose的别名
1485
+ * Alias for Expose
1462
1486
  */
1463
1487
  function IsDefined() {
1464
1488
  return function (object, propertyName) {
@@ -1466,61 +1490,89 @@ function IsDefined() {
1466
1490
  };
1467
1491
  }
1468
1492
  /**
1469
- * 参数验证装饰器
1493
+ * Parameter validation decorator
1470
1494
  */
1471
1495
  function Valid(rule, options) {
1472
1496
  return function (object, propertyName, parameterIndex) {
1473
- // 这里保持与原始实现一致
1497
+ // Keep consistent with original implementation
1474
1498
  const existingRules = Reflect.getOwnMetadata("validate", object, propertyName) || {};
1475
1499
  existingRules[parameterIndex] = { rule, options };
1476
1500
  Reflect.defineMetadata("validate", existingRules, object, propertyName);
1477
1501
  };
1478
1502
  }
1479
1503
  /**
1480
- * 方法验证装饰器
1481
- * 自动验证方法参数中的 DTO 对象
1504
+ * Synchronous validation function - Executes the actual validation logic
1505
+ * @param args Method parameters
1506
+ * @param paramTypes Parameter type metadata
1507
+ * @returns Validated parameters and validation targets
1482
1508
  */
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
- }
1509
+ async function checkValidated(args, paramTypes) {
1510
+ const validationTargets = [];
1511
+ // Validate each parameter
1512
+ for (let i = 0; i < args.length; i++) {
1513
+ const arg = args[i];
1514
+ const paramType = paramTypes[i];
1515
+ // If it's a class type and not a basic type, perform validation
1516
+ if (paramType && typeof paramType === 'function' &&
1517
+ paramType !== String && paramType !== Number &&
1518
+ paramType !== Boolean && paramType !== Array &&
1519
+ paramType !== Object && paramType !== Date) {
1520
+ try {
1521
+ // If parameter is not an instance of the target type, convert it to an instance
1522
+ let validationTarget = arg;
1523
+ if (!(arg instanceof paramType)) {
1524
+ validationTarget = Object.assign(new paramType(), arg);
1525
+ }
1526
+ const errors = await classValidator.validate(validationTarget);
1527
+ if (errors.length > 0) {
1528
+ throw createValidationErrors(errors.map(e => ({
1529
+ field: e.property,
1530
+ value: e.value,
1531
+ constraint: Object.keys(e.constraints || {})[0] || 'unknown',
1532
+ message: Object.values(e.constraints || {})[0] || 'Validation failed',
1533
+ context: e.constraints
1534
+ })));
1519
1535
  }
1536
+ validationTargets.push(validationTarget);
1520
1537
  }
1521
- // 执行原始方法
1522
- return originalMethod.apply(this, args);
1523
- };
1538
+ catch (error) {
1539
+ // If validation fails, rethrow the error
1540
+ throw error;
1541
+ }
1542
+ }
1543
+ else {
1544
+ validationTargets.push(arg);
1545
+ }
1546
+ }
1547
+ return { validatedArgs: args, validationTargets };
1548
+ }
1549
+ /**
1550
+ * Method validation decorator
1551
+ * Automatically validates DTO objects in method parameters
1552
+ * @param isAsync Whether to use async validation mode, default is true
1553
+ * - true: Async mode, validation is handled by IOC container in the framework (suitable for scenarios where parameter values need to be obtained asynchronously)
1554
+ * - false: Sync mode, validation is performed immediately when the method is called (suitable for scenarios where parameter values are already prepared)
1555
+ */
1556
+ function Validated(isAsync = true) {
1557
+ return function (target, propertyKey, descriptor) {
1558
+ if (isAsync) {
1559
+ // Async mode: Save metadata, validation will be performed by the framework after async parameter retrieval
1560
+ koatty_container.IOCContainer.savePropertyData(PARAM_CHECK_KEY, {
1561
+ dtoCheck: 1
1562
+ }, target, propertyKey);
1563
+ }
1564
+ else {
1565
+ // Sync mode: Perform validation immediately when the method is called
1566
+ const originalMethod = descriptor.value;
1567
+ descriptor.value = async function (...args) {
1568
+ // Get parameter type metadata
1569
+ const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey) || [];
1570
+ // Execute validation
1571
+ await checkValidated(args, paramTypes);
1572
+ // Execute original method
1573
+ return originalMethod.apply(this, args);
1574
+ };
1575
+ }
1524
1576
  return descriptor;
1525
1577
  };
1526
1578
  }
@@ -1562,6 +1614,7 @@ exports.ValidFuncs = ValidFuncs;
1562
1614
  exports.Validated = Validated;
1563
1615
  exports.cached = cached;
1564
1616
  exports.checkParamsType = checkParamsType;
1617
+ exports.checkValidated = checkValidated;
1565
1618
  exports.clearAllCaches = clearAllCaches;
1566
1619
  exports.configureCaches = configureCaches;
1567
1620
  exports.convertDtoParamsType = convertDtoParamsType;