schema-dsl 1.0.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/.eslintignore +10 -0
- package/.eslintrc.json +27 -0
- package/.github/CODE_OF_CONDUCT.md +45 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +57 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +45 -0
- package/.github/ISSUE_TEMPLATE/question.md +31 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +70 -0
- package/.github/SECURITY.md +184 -0
- package/.github/workflows/ci.yml +33 -0
- package/CHANGELOG.md +633 -0
- package/CONTRIBUTING.md +368 -0
- package/LICENSE +21 -0
- package/README.md +1184 -0
- package/STATUS.md +101 -0
- package/docs/FEATURE-INDEX.md +519 -0
- package/docs/INDEX.md +253 -0
- package/docs/api-reference.md +1096 -0
- package/docs/best-practices.md +672 -0
- package/docs/cache-manager.md +336 -0
- package/docs/design-philosophy.md +601 -0
- package/docs/dsl-syntax.md +653 -0
- package/docs/dynamic-locale.md +552 -0
- package/docs/error-handling.md +703 -0
- package/docs/export-guide.md +462 -0
- package/docs/export-limitations.md +551 -0
- package/docs/faq.md +577 -0
- package/docs/frontend-i18n-guide.md +290 -0
- package/docs/i18n-user-guide.md +476 -0
- package/docs/label-vs-description.md +262 -0
- package/docs/markdown-exporter.md +397 -0
- package/docs/mongodb-exporter.md +295 -0
- package/docs/multi-type-support.md +319 -0
- package/docs/mysql-exporter.md +273 -0
- package/docs/plugin-system.md +542 -0
- package/docs/postgresql-exporter.md +304 -0
- package/docs/quick-start.md +761 -0
- package/docs/schema-helper.md +340 -0
- package/docs/schema-utils-chaining.md +143 -0
- package/docs/schema-utils.md +490 -0
- package/docs/string-extensions.md +480 -0
- package/docs/troubleshooting.md +471 -0
- package/docs/type-converter.md +319 -0
- package/docs/type-reference.md +219 -0
- package/docs/validate-async.md +480 -0
- package/docs/validate.md +486 -0
- package/docs/validation-guide.md +484 -0
- package/examples/array-dsl-example.js +227 -0
- package/examples/custom-extension.js +85 -0
- package/examples/dsl-match-example.js +74 -0
- package/examples/dsl-style.js +118 -0
- package/examples/dynamic-locale-configuration.js +348 -0
- package/examples/dynamic-locale-example.js +287 -0
- package/examples/export-demo.js +130 -0
- package/examples/express-integration.js +376 -0
- package/examples/i18n-full-demo.js +310 -0
- package/examples/i18n-memory-safety.examples.js +268 -0
- package/examples/markdown-export.js +71 -0
- package/examples/middleware-usage.js +93 -0
- package/examples/new-features-comparison.js +315 -0
- package/examples/password-reset/README.md +153 -0
- package/examples/password-reset/schema.js +26 -0
- package/examples/password-reset/test.js +101 -0
- package/examples/plugin-system.examples.js +205 -0
- package/examples/schema-utils-chaining.examples.js +250 -0
- package/examples/simple-example.js +122 -0
- package/examples/string-extensions.js +297 -0
- package/examples/user-registration/README.md +156 -0
- package/examples/user-registration/routes.js +92 -0
- package/examples/user-registration/schema.js +150 -0
- package/examples/user-registration/server.js +74 -0
- package/index.d.ts +1999 -0
- package/index.js +282 -0
- package/index.mjs +30 -0
- package/lib/adapters/DslAdapter.js +699 -0
- package/lib/adapters/index.js +20 -0
- package/lib/config/constants.js +286 -0
- package/lib/config/patterns/creditCard.js +9 -0
- package/lib/config/patterns/idCard.js +9 -0
- package/lib/config/patterns/index.js +8 -0
- package/lib/config/patterns/licensePlate.js +4 -0
- package/lib/config/patterns/passport.js +4 -0
- package/lib/config/patterns/phone.js +9 -0
- package/lib/config/patterns/postalCode.js +5 -0
- package/lib/core/CacheManager.js +376 -0
- package/lib/core/DslBuilder.js +740 -0
- package/lib/core/ErrorCodes.js +233 -0
- package/lib/core/ErrorFormatter.js +342 -0
- package/lib/core/JSONSchemaCore.js +347 -0
- package/lib/core/Locale.js +119 -0
- package/lib/core/MessageTemplate.js +89 -0
- package/lib/core/PluginManager.js +448 -0
- package/lib/core/StringExtensions.js +209 -0
- package/lib/core/Validator.js +376 -0
- package/lib/errors/ValidationError.js +191 -0
- package/lib/exporters/MarkdownExporter.js +420 -0
- package/lib/exporters/MongoDBExporter.js +162 -0
- package/lib/exporters/MySQLExporter.js +212 -0
- package/lib/exporters/PostgreSQLExporter.js +289 -0
- package/lib/exporters/index.js +24 -0
- package/lib/locales/en-US.js +65 -0
- package/lib/locales/es-ES.js +66 -0
- package/lib/locales/fr-FR.js +66 -0
- package/lib/locales/index.js +8 -0
- package/lib/locales/ja-JP.js +66 -0
- package/lib/locales/zh-CN.js +93 -0
- package/lib/utils/LRUCache.js +174 -0
- package/lib/utils/SchemaHelper.js +240 -0
- package/lib/utils/SchemaUtils.js +445 -0
- package/lib/utils/TypeConverter.js +245 -0
- package/lib/utils/index.js +13 -0
- package/lib/validators/CustomKeywords.js +203 -0
- package/lib/validators/index.js +11 -0
- package/package.json +70 -0
- package/plugins/custom-format.js +101 -0
- package/plugins/custom-validator.js +200 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
required: '{{#label}} est requis',
|
|
3
|
+
type: '{{#label}} doit être de type {{#expected}}, mais a obtenu {{#actual}}',
|
|
4
|
+
min: 'La longueur de {{#label}} doit être d\'au moins {{#limit}}',
|
|
5
|
+
max: 'La longueur de {{#label}} doit être au maximum de {{#limit}}',
|
|
6
|
+
length: 'La longueur de {{#label}} doit être exactement {{#expected}}',
|
|
7
|
+
pattern: 'Le format de {{#label}} est invalide',
|
|
8
|
+
enum: '{{#label}} doit être l\'un des suivants: {{#allowed}}',
|
|
9
|
+
custom: 'Échec de la validation pour {{#label}}: {{#message}}',
|
|
10
|
+
circular: 'Référence circulaire détectée dans {{#label}}',
|
|
11
|
+
'max-depth': 'Profondeur de récursion maximale ({{#depth}}) dépassée dans {{#label}}',
|
|
12
|
+
exception: 'Exception de validation dans {{#label}}: {{#message}}',
|
|
13
|
+
|
|
14
|
+
// Patterns
|
|
15
|
+
'pattern.phone': 'Numéro de téléphone invalide',
|
|
16
|
+
'pattern.phone.international': 'Numéro de téléphone international invalide',
|
|
17
|
+
|
|
18
|
+
'pattern.idCard': 'Numéro de carte d\'identité invalide',
|
|
19
|
+
|
|
20
|
+
'pattern.creditCard': 'Numéro de carte de crédit invalide',
|
|
21
|
+
'pattern.creditCard.visa': 'Numéro de carte Visa invalide',
|
|
22
|
+
'pattern.creditCard.mastercard': 'Numéro Mastercard invalide',
|
|
23
|
+
'pattern.creditCard.amex': 'Numéro de carte American Express invalide',
|
|
24
|
+
'pattern.creditCard.discover': 'Numéro de carte Discover invalide',
|
|
25
|
+
'pattern.creditCard.jcb': 'Numéro de carte JCB invalide',
|
|
26
|
+
'pattern.creditCard.unionpay': 'Numéro de carte UnionPay invalide',
|
|
27
|
+
|
|
28
|
+
'pattern.licensePlate': 'Numéro de plaque d\'immatriculation invalide',
|
|
29
|
+
|
|
30
|
+
'pattern.postalCode': 'Code postal invalide',
|
|
31
|
+
|
|
32
|
+
'pattern.passport': 'Numéro de passeport invalide',
|
|
33
|
+
|
|
34
|
+
// New Types
|
|
35
|
+
'pattern.objectId': 'ObjectId invalide',
|
|
36
|
+
'pattern.hexColor': 'Couleur hexadécimale invalide',
|
|
37
|
+
'pattern.macAddress': 'Adresse MAC invalide',
|
|
38
|
+
'pattern.cron': 'Expression Cron invalide',
|
|
39
|
+
'pattern.slug': 'Le slug d\'URL ne peut contenir que des lettres minuscules, des chiffres et des traits d\'union',
|
|
40
|
+
|
|
41
|
+
// Username & Password
|
|
42
|
+
'pattern.username': 'Le nom d\'utilisateur doit commencer par une lettre et ne contenir que des lettres, des chiffres et des traits de soulignement',
|
|
43
|
+
'pattern.password.weak': 'Le mot de passe doit contenir au moins 6 caractères',
|
|
44
|
+
'pattern.password.medium': 'Le mot de passe doit contenir au moins 8 caractères et contenir des lettres et des chiffres',
|
|
45
|
+
'pattern.password.strong': 'Le mot de passe doit contenir au moins 8 caractères et contenir des lettres majuscules, minuscules et des chiffres',
|
|
46
|
+
'pattern.password.veryStrong': 'Le mot de passe doit contenir au moins 10 caractères et contenir des lettres majuscules, minuscules, des chiffres et des caractères spéciaux',
|
|
47
|
+
|
|
48
|
+
// Unknown error fallback
|
|
49
|
+
'UNKNOWN_ERROR': 'Erreur de validation inconnue',
|
|
50
|
+
|
|
51
|
+
// Custom validation
|
|
52
|
+
'CUSTOM_VALIDATION_FAILED': 'Échec de la validation',
|
|
53
|
+
'ASYNC_VALIDATION_NOT_SUPPORTED': 'La validation asynchrone n\'est pas prise en charge dans validate() synchrone',
|
|
54
|
+
'VALIDATE_MUST_BE_FUNCTION': 'validate doit être une fonction',
|
|
55
|
+
|
|
56
|
+
// Formats
|
|
57
|
+
'format.email': 'Adresse e-mail invalide',
|
|
58
|
+
'format.url': 'URL invalide',
|
|
59
|
+
'format.uuid': 'UUID invalide',
|
|
60
|
+
'format.date': 'Format de date invalide (YYYY-MM-DD)',
|
|
61
|
+
'format.datetime': 'Format de date et heure invalide (ISO 8601)',
|
|
62
|
+
'format.time': 'Format de l\'heure invalide (HH:mm:ss)',
|
|
63
|
+
'format.ipv4': 'Adresse IPv4 invalide',
|
|
64
|
+
'format.ipv6': 'Adresse IPv6 invalide'
|
|
65
|
+
};
|
|
66
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
required: '{{#label}}は必須です',
|
|
3
|
+
type: '{{#label}}は {{#expected}} 型である必要がありますが、{{#actual}} でした',
|
|
4
|
+
min: '{{#label}}の長さは少なくとも {{#limit}} である必要があります',
|
|
5
|
+
max: '{{#label}}の長さは最大 {{#limit}} である必要があります',
|
|
6
|
+
length: '{{#label}}の長さは {{#expected}} である必要があります',
|
|
7
|
+
pattern: '{{#label}}の形式が無効です',
|
|
8
|
+
enum: '{{#label}}は次のいずれかである必要があります: {{#allowed}}',
|
|
9
|
+
custom: '{{#label}}の検証に失敗しました: {{#message}}',
|
|
10
|
+
circular: '{{#label}}で循環参照が検出されました',
|
|
11
|
+
'max-depth': '{{#label}}で最大再帰深度 ({{#depth}}) を超えました',
|
|
12
|
+
exception: '{{#label}}検証例外: {{#message}}',
|
|
13
|
+
|
|
14
|
+
// Patterns
|
|
15
|
+
'pattern.phone': '無効な電話番号です',
|
|
16
|
+
'pattern.phone.international': '無効な国際電話番号です',
|
|
17
|
+
|
|
18
|
+
'pattern.idCard': '無効なIDカード番号です',
|
|
19
|
+
|
|
20
|
+
'pattern.creditCard': '無効なクレジットカード番号です',
|
|
21
|
+
'pattern.creditCard.visa': '無効なVisaカード番号です',
|
|
22
|
+
'pattern.creditCard.mastercard': '無効なMastercard番号です',
|
|
23
|
+
'pattern.creditCard.amex': '無効なAmerican Expressカード番号です',
|
|
24
|
+
'pattern.creditCard.discover': '無効なDiscoverカード番号です',
|
|
25
|
+
'pattern.creditCard.jcb': '無効なJCBカード番号です',
|
|
26
|
+
'pattern.creditCard.unionpay': '無効な銀聯カード番号です',
|
|
27
|
+
|
|
28
|
+
'pattern.licensePlate': '無効なナンバープレート番号です',
|
|
29
|
+
|
|
30
|
+
'pattern.postalCode': '無効な郵便番号です',
|
|
31
|
+
|
|
32
|
+
'pattern.passport': '無効なパスポート番号です',
|
|
33
|
+
|
|
34
|
+
// New Types
|
|
35
|
+
'pattern.objectId': '無効なObjectIdです',
|
|
36
|
+
'pattern.hexColor': '無効な16進数カラーコードです',
|
|
37
|
+
'pattern.macAddress': '無効なMACアドレスです',
|
|
38
|
+
'pattern.cron': '無効なCron式です',
|
|
39
|
+
'pattern.slug': 'URLスラッグには、小文字、数字、ハイフンのみを含めることができます',
|
|
40
|
+
|
|
41
|
+
// Username & Password
|
|
42
|
+
'pattern.username': 'ユーザー名は文字で始まり、文字、数字、アンダースコアのみを含める必要があります',
|
|
43
|
+
'pattern.password.weak': 'パスワードは少なくとも6文字である必要があります',
|
|
44
|
+
'pattern.password.medium': 'パスワードは少なくとも8文字で、文字と数字を含める必要があります',
|
|
45
|
+
'pattern.password.strong': 'パスワードは少なくとも8文字で、大文字、小文字、数字を含める必要があります',
|
|
46
|
+
'pattern.password.veryStrong': 'パスワードは少なくとも10文字で、大文字、小文字、数字、特殊文字を含める必要があります',
|
|
47
|
+
|
|
48
|
+
// Unknown error fallback
|
|
49
|
+
'UNKNOWN_ERROR': '未知の検証エラー',
|
|
50
|
+
|
|
51
|
+
// Custom validation
|
|
52
|
+
'CUSTOM_VALIDATION_FAILED': '検証に失敗しました',
|
|
53
|
+
'ASYNC_VALIDATION_NOT_SUPPORTED': '同期検証では非同期操作はサポートされていません',
|
|
54
|
+
'VALIDATE_MUST_BE_FUNCTION': 'validateは関数である必要があります',
|
|
55
|
+
|
|
56
|
+
// Formats
|
|
57
|
+
'format.email': '無効なメールアドレスです',
|
|
58
|
+
'format.url': '無効なURLです',
|
|
59
|
+
'format.uuid': '無効なUUIDです',
|
|
60
|
+
'format.date': '無効な日付形式です (YYYY-MM-DD)',
|
|
61
|
+
'format.datetime': '無効な日時形式です (ISO 8601)',
|
|
62
|
+
'format.time': '無効な時間形式です (HH:mm:ss)',
|
|
63
|
+
'format.ipv4': '無効なIPv4アドレスです',
|
|
64
|
+
'format.ipv6': '無効なIPv6アドレスです'
|
|
65
|
+
};
|
|
66
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
// Generic
|
|
3
|
+
required: '{{#label}}不能为空',
|
|
4
|
+
type: '{{#label}}应该是 {{#expected}} 类型',
|
|
5
|
+
min: '{{#label}}长度不能少于{{#limit}}个字符',
|
|
6
|
+
max: '{{#label}}长度不能超过{{#limit}}个字符',
|
|
7
|
+
length: '{{#label}}长度必须是{{#limit}}个字符',
|
|
8
|
+
pattern: '{{#label}}格式不正确',
|
|
9
|
+
enum: '{{#label}}必须是以下值之一: {{#allowed}}',
|
|
10
|
+
custom: '{{#label}}验证失败: {{#message}}',
|
|
11
|
+
circular: '{{#label}}检测到循环引用',
|
|
12
|
+
'max-depth': '超过最大递归深度 ({{#depth}}) at {{#label}}',
|
|
13
|
+
exception: '{{#label}}验证异常: {{#message}}',
|
|
14
|
+
|
|
15
|
+
// Formats
|
|
16
|
+
'format.email': '{{#label}}必须是有效的邮箱地址',
|
|
17
|
+
'format.url': '{{#label}}必须是有效的URL地址',
|
|
18
|
+
'format.uuid': '{{#label}}必须是有效的UUID',
|
|
19
|
+
'format.date': '{{#label}}必须是有效的日期格式 (YYYY-MM-DD)',
|
|
20
|
+
'format.datetime': '{{#label}}必须是有效的日期时间格式 (ISO 8601)',
|
|
21
|
+
'format.time': '{{#label}}必须是有效的时间格式 (HH:mm:ss)',
|
|
22
|
+
'format.ipv4': '{{#label}}必须是有效的IPv4地址',
|
|
23
|
+
'format.ipv6': '{{#label}}必须是有效的IPv6地址',
|
|
24
|
+
|
|
25
|
+
// String
|
|
26
|
+
'string.hostname': '{{#label}}必须是有效的主机名',
|
|
27
|
+
'string.pattern': '{{#label}}格式不符合要求',
|
|
28
|
+
'string.enum': '{{#label}}必须是以下值之一: {{#valids}}',
|
|
29
|
+
|
|
30
|
+
// Number
|
|
31
|
+
'number.base': '{{#label}}必须是数字类型',
|
|
32
|
+
'number.min': '{{#label}}不能小于{{#limit}}',
|
|
33
|
+
'number.max': '{{#label}}不能大于{{#limit}}',
|
|
34
|
+
'number.integer': '{{#label}}必须是整数',
|
|
35
|
+
'number.positive': '{{#label}}必须是正数',
|
|
36
|
+
'number.negative': '{{#label}}必须是负数',
|
|
37
|
+
|
|
38
|
+
// Boolean
|
|
39
|
+
'boolean.base': '{{#label}}必须是布尔类型',
|
|
40
|
+
|
|
41
|
+
// Object
|
|
42
|
+
'object.base': '{{#label}}必须是对象类型',
|
|
43
|
+
'object.min': '{{#label}}至少需要{{#limit}}个属性',
|
|
44
|
+
'object.max': '{{#label}}最多只能有{{#limit}}个属性',
|
|
45
|
+
'object.unknown': '{{#label}}包含未知属性: {{#key}}',
|
|
46
|
+
|
|
47
|
+
// Array
|
|
48
|
+
'array.base': '{{#label}}必须是数组类型',
|
|
49
|
+
'array.min': '{{#label}}至少需要{{#limit}}个元素',
|
|
50
|
+
'array.max': '{{#label}}最多只能有{{#limit}}个元素',
|
|
51
|
+
'array.length': '{{#label}}必须有{{#limit}}个元素',
|
|
52
|
+
'array.unique': '{{#label}}不能包含重复元素',
|
|
53
|
+
|
|
54
|
+
// Date
|
|
55
|
+
'date.base': '{{#label}}必须是有效的日期',
|
|
56
|
+
'date.min': '{{#label}}不能早于{{#limit}}',
|
|
57
|
+
'date.max': '{{#label}}不能晚于{{#limit}}',
|
|
58
|
+
|
|
59
|
+
// Any
|
|
60
|
+
'any.required': '{{#label}}是必填项',
|
|
61
|
+
'any.invalid': '{{#label}}包含无效值',
|
|
62
|
+
'any.only': '{{#label}}必须匹配{{#valids}}',
|
|
63
|
+
'any.unknown': '不允许字段{{#key}}',
|
|
64
|
+
|
|
65
|
+
// Patterns (Legacy/Specific)
|
|
66
|
+
'pattern.phone': '请输入有效的手机号',
|
|
67
|
+
'pattern.phone.international': '请输入有效的国际手机号',
|
|
68
|
+
'pattern.idCard': '请输入有效的身份证号码',
|
|
69
|
+
'pattern.creditCard': '无效的信用卡号',
|
|
70
|
+
'pattern.licensePlate': '请输入有效的车牌号',
|
|
71
|
+
'pattern.postalCode': '请输入有效的邮政编码',
|
|
72
|
+
'pattern.passport': '请输入有效的护照号码',
|
|
73
|
+
'pattern.objectId': '无效的 ObjectId',
|
|
74
|
+
'pattern.hexColor': '无效的十六进制颜色值',
|
|
75
|
+
'pattern.macAddress': '无效的 MAC 地址',
|
|
76
|
+
'pattern.cron': '无效的 Cron 表达式',
|
|
77
|
+
'pattern.slug': 'URL别名只能包含小写字母、数字和连字符',
|
|
78
|
+
|
|
79
|
+
// Username & Password
|
|
80
|
+
'pattern.username': '用户名必须以字母开头,只能包含字母、数字和下划线',
|
|
81
|
+
'pattern.password.weak': '密码至少6位',
|
|
82
|
+
'pattern.password.medium': '密码至少8位,需包含字母和数字',
|
|
83
|
+
'pattern.password.strong': '密码至少8位,需包含大小写字母和数字',
|
|
84
|
+
'pattern.password.veryStrong': '密码至少10位,需包含大小写字母、数字和特殊字符',
|
|
85
|
+
|
|
86
|
+
// Unknown error fallback
|
|
87
|
+
'UNKNOWN_ERROR': '未知的验证错误',
|
|
88
|
+
|
|
89
|
+
// Custom validation
|
|
90
|
+
'CUSTOM_VALIDATION_FAILED': '自定义验证失败',
|
|
91
|
+
'ASYNC_VALIDATION_NOT_SUPPORTED': '同步验证不支持异步操作',
|
|
92
|
+
'VALIDATE_MUST_BE_FUNCTION': 'validate 必须是一个函数'
|
|
93
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 带内存管理的 LRU 缓存实现
|
|
3
|
+
* 用于安全缓存语言包消息,防止内存泄漏
|
|
4
|
+
*
|
|
5
|
+
* @module lib/utils/LRUCache
|
|
6
|
+
* @version 2.2.1
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* LRU (Least Recently Used) 缓存
|
|
11
|
+
* 自动清理最少使用的条目,确保内存占用可控
|
|
12
|
+
*/
|
|
13
|
+
class LRUCache {
|
|
14
|
+
/**
|
|
15
|
+
* 创建 LRU 缓存实例
|
|
16
|
+
* @param {Object} options - 配置选项
|
|
17
|
+
* @param {number} [options.maxSize=10] - 最大缓存数量
|
|
18
|
+
* @param {boolean} [options.enableStats=false] - 是否启用统计
|
|
19
|
+
*/
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.maxSize = options.maxSize || 10;
|
|
22
|
+
this.enableStats = options.enableStats || false;
|
|
23
|
+
this.cache = new Map();
|
|
24
|
+
|
|
25
|
+
if (this.enableStats) {
|
|
26
|
+
this.stats = {
|
|
27
|
+
hits: 0,
|
|
28
|
+
misses: 0,
|
|
29
|
+
evictions: 0,
|
|
30
|
+
sets: 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 获取缓存值
|
|
37
|
+
* @param {string} key - 键
|
|
38
|
+
* @returns {*} 缓存的值,不存在则返回 undefined
|
|
39
|
+
*/
|
|
40
|
+
get(key) {
|
|
41
|
+
if (!this.cache.has(key)) {
|
|
42
|
+
if (this.enableStats) this.stats.misses++;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this.enableStats) this.stats.hits++;
|
|
47
|
+
|
|
48
|
+
// LRU: 移到最后(标记为最近使用)
|
|
49
|
+
const value = this.cache.get(key);
|
|
50
|
+
this.cache.delete(key);
|
|
51
|
+
this.cache.set(key, value);
|
|
52
|
+
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 设置缓存值
|
|
58
|
+
* @param {string} key - 键
|
|
59
|
+
* @param {*} value - 值
|
|
60
|
+
*/
|
|
61
|
+
set(key, value) {
|
|
62
|
+
if (this.enableStats) this.stats.sets++;
|
|
63
|
+
|
|
64
|
+
// 如果已存在,先删除(更新顺序)
|
|
65
|
+
if (this.cache.has(key)) {
|
|
66
|
+
this.cache.delete(key);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 如果超过容量,删除最旧的(最少使用)
|
|
70
|
+
if (this.cache.size >= this.maxSize) {
|
|
71
|
+
const firstKey = this.cache.keys().next().value;
|
|
72
|
+
this.cache.delete(firstKey);
|
|
73
|
+
|
|
74
|
+
if (this.enableStats) this.stats.evictions++;
|
|
75
|
+
|
|
76
|
+
// 开发环境警告
|
|
77
|
+
if (process.env.NODE_ENV === 'development') {
|
|
78
|
+
console.warn(`[LRUCache] Evicted key: ${firstKey} (cache full: ${this.maxSize})`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.cache.set(key, value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 检查键是否存在
|
|
87
|
+
* @param {string} key - 键
|
|
88
|
+
* @returns {boolean} 是否存在
|
|
89
|
+
*/
|
|
90
|
+
has(key) {
|
|
91
|
+
return this.cache.has(key);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 删除指定键
|
|
96
|
+
* @param {string} key - 键
|
|
97
|
+
* @returns {boolean} 是否删除成功
|
|
98
|
+
*/
|
|
99
|
+
delete(key) {
|
|
100
|
+
return this.cache.delete(key);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 清空所有缓存
|
|
105
|
+
*/
|
|
106
|
+
clear() {
|
|
107
|
+
this.cache.clear();
|
|
108
|
+
|
|
109
|
+
if (this.enableStats) {
|
|
110
|
+
this.stats = {
|
|
111
|
+
hits: 0,
|
|
112
|
+
misses: 0,
|
|
113
|
+
evictions: 0,
|
|
114
|
+
sets: 0
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 获取当前缓存大小
|
|
121
|
+
* @returns {number} 缓存条目数量
|
|
122
|
+
*/
|
|
123
|
+
get size() {
|
|
124
|
+
return this.cache.size;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 获取所有键
|
|
129
|
+
* @returns {Array<string>} 键数组
|
|
130
|
+
*/
|
|
131
|
+
keys() {
|
|
132
|
+
return Array.from(this.cache.keys());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 获取缓存统计信息
|
|
137
|
+
* @returns {Object} 统计信息
|
|
138
|
+
*/
|
|
139
|
+
getStats() {
|
|
140
|
+
if (!this.enableStats) {
|
|
141
|
+
return { message: 'Stats not enabled. Set enableStats: true in constructor.' };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const total = this.stats.hits + this.stats.misses;
|
|
145
|
+
const hitRate = total > 0 ? (this.stats.hits / total * 100).toFixed(2) : 0;
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
hits: this.stats.hits,
|
|
149
|
+
misses: this.stats.misses,
|
|
150
|
+
evictions: this.stats.evictions,
|
|
151
|
+
sets: this.stats.sets,
|
|
152
|
+
hitRate: `${hitRate}%`,
|
|
153
|
+
size: this.cache.size,
|
|
154
|
+
maxSize: this.maxSize,
|
|
155
|
+
efficiency: hitRate >= 80 ? '优秀' : hitRate >= 60 ? '良好' : '需优化'
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 重置统计信息
|
|
161
|
+
*/
|
|
162
|
+
resetStats() {
|
|
163
|
+
if (this.enableStats) {
|
|
164
|
+
this.stats = {
|
|
165
|
+
hits: 0,
|
|
166
|
+
misses: 0,
|
|
167
|
+
evictions: 0,
|
|
168
|
+
sets: 0
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = LRUCache;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SchemaHelper - Schema辅助函数
|
|
3
|
+
*
|
|
4
|
+
* 提供Schema操作的常用辅助方法
|
|
5
|
+
*
|
|
6
|
+
* @module lib/utils/SchemaHelper
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Schema辅助工具类
|
|
12
|
+
* @class SchemaHelper
|
|
13
|
+
*/
|
|
14
|
+
class SchemaHelper {
|
|
15
|
+
/**
|
|
16
|
+
* 检查是否为有效的JSON Schema
|
|
17
|
+
* @static
|
|
18
|
+
* @param {Object} schema - 待检查的Schema对象
|
|
19
|
+
* @returns {boolean} 是否有效
|
|
20
|
+
*/
|
|
21
|
+
static isValidSchema(schema) {
|
|
22
|
+
if (!schema || typeof schema !== 'object') {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 至少要有type或properties或items之一
|
|
27
|
+
return !!(schema.type || schema.properties || schema.items || schema.$ref);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 生成Schema的唯一ID
|
|
32
|
+
* @static
|
|
33
|
+
* @param {Object} schema - JSON Schema对象
|
|
34
|
+
* @returns {string} 唯一ID
|
|
35
|
+
*/
|
|
36
|
+
static generateSchemaId(schema) {
|
|
37
|
+
// 使用简单hash生成ID
|
|
38
|
+
const str = JSON.stringify(schema);
|
|
39
|
+
let hash = 0;
|
|
40
|
+
for (let i = 0; i < str.length; i++) {
|
|
41
|
+
const char = str.charCodeAt(i);
|
|
42
|
+
hash = ((hash << 5) - hash) + char;
|
|
43
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
44
|
+
}
|
|
45
|
+
return `schema_${Math.abs(hash).toString(36)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 深度克隆Schema对象
|
|
50
|
+
* @static
|
|
51
|
+
* @param {Object} schema - 原Schema对象
|
|
52
|
+
* @returns {Object} 克隆的Schema对象
|
|
53
|
+
*/
|
|
54
|
+
static cloneSchema(schema) {
|
|
55
|
+
return JSON.parse(JSON.stringify(schema));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 扁平化嵌套Schema
|
|
60
|
+
* @static
|
|
61
|
+
* @param {Object} schema - 嵌套的Schema对象
|
|
62
|
+
* @param {string} prefix - 属性前缀
|
|
63
|
+
* @returns {Object} 扁平化的Schema对象
|
|
64
|
+
*/
|
|
65
|
+
static flattenSchema(schema, prefix = '') {
|
|
66
|
+
const result = {};
|
|
67
|
+
|
|
68
|
+
if (schema.properties) {
|
|
69
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
70
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
71
|
+
|
|
72
|
+
if (value.type === 'object' && value.properties) {
|
|
73
|
+
// 递归扁平化嵌套对象
|
|
74
|
+
Object.assign(result, this.flattenSchema(value, fullKey));
|
|
75
|
+
} else {
|
|
76
|
+
result[fullKey] = value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 获取Schema中所有字段路径
|
|
86
|
+
* @static
|
|
87
|
+
* @param {Object} schema - JSON Schema对象
|
|
88
|
+
* @returns {Array} 字段路径数组
|
|
89
|
+
*/
|
|
90
|
+
static getFieldPaths(schema) {
|
|
91
|
+
const paths = [];
|
|
92
|
+
|
|
93
|
+
function traverse(obj, currentPath = '') {
|
|
94
|
+
if (obj.properties) {
|
|
95
|
+
for (const [key, value] of Object.entries(obj.properties)) {
|
|
96
|
+
const path = currentPath ? `${currentPath}.${key}` : key;
|
|
97
|
+
paths.push(path);
|
|
98
|
+
|
|
99
|
+
if (value.type === 'object') {
|
|
100
|
+
traverse(value, path);
|
|
101
|
+
} else if (value.type === 'array' && value.items) {
|
|
102
|
+
traverse(value.items, `${path}[]`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
traverse(schema);
|
|
109
|
+
return paths;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 提取Schema中的所有required字段
|
|
114
|
+
* @static
|
|
115
|
+
* @param {Object} schema - JSON Schema对象
|
|
116
|
+
* @returns {Array} required字段数组
|
|
117
|
+
*/
|
|
118
|
+
static extractRequiredFields(schema) {
|
|
119
|
+
const required = [];
|
|
120
|
+
|
|
121
|
+
function traverse(obj, prefix = '') {
|
|
122
|
+
if (obj.required && Array.isArray(obj.required)) {
|
|
123
|
+
obj.required.forEach(field => {
|
|
124
|
+
const fullPath = prefix ? `${prefix}.${field}` : field;
|
|
125
|
+
required.push(fullPath);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (obj.properties) {
|
|
130
|
+
for (const [key, value] of Object.entries(obj.properties)) {
|
|
131
|
+
if (value.type === 'object') {
|
|
132
|
+
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
133
|
+
traverse(value, newPrefix);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
traverse(schema);
|
|
140
|
+
return required;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 比较两个Schema是否相同
|
|
145
|
+
* @static
|
|
146
|
+
* @param {Object} schema1 - Schema对象1
|
|
147
|
+
* @param {Object} schema2 - Schema对象2
|
|
148
|
+
* @returns {boolean} 是否相同
|
|
149
|
+
*/
|
|
150
|
+
static compareSchemas(schema1, schema2) {
|
|
151
|
+
return JSON.stringify(schema1) === JSON.stringify(schema2);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 简化Schema(移除不必要的字段)
|
|
156
|
+
* @static
|
|
157
|
+
* @param {Object} schema - JSON Schema对象
|
|
158
|
+
* @returns {Object} 简化后的Schema
|
|
159
|
+
*/
|
|
160
|
+
static simplifySchema(schema) {
|
|
161
|
+
const simplified = this.cloneSchema(schema);
|
|
162
|
+
|
|
163
|
+
// 移除$schema
|
|
164
|
+
delete simplified.$schema;
|
|
165
|
+
|
|
166
|
+
// 移除空的properties
|
|
167
|
+
if (simplified.properties && Object.keys(simplified.properties).length === 0) {
|
|
168
|
+
delete simplified.properties;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 移除空的required
|
|
172
|
+
if (simplified.required && simplified.required.length === 0) {
|
|
173
|
+
delete simplified.required;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return simplified;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 验证属性名是否合法
|
|
181
|
+
* @static
|
|
182
|
+
* @param {string} name - 属性名
|
|
183
|
+
* @returns {boolean} 是否合法
|
|
184
|
+
*/
|
|
185
|
+
static isValidPropertyName(name) {
|
|
186
|
+
// 属性名只能包含字母、数字、下划线、连字符
|
|
187
|
+
return /^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 获取Schema的复杂度(嵌套层级)
|
|
192
|
+
* @static
|
|
193
|
+
* @param {Object} schema - JSON Schema对象
|
|
194
|
+
* @returns {number} 复杂度(最大嵌套层级)
|
|
195
|
+
*/
|
|
196
|
+
static getSchemaComplexity(schema) {
|
|
197
|
+
let maxDepth = 0;
|
|
198
|
+
|
|
199
|
+
function traverse(obj, depth = 0) {
|
|
200
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
201
|
+
|
|
202
|
+
if (obj.properties) {
|
|
203
|
+
for (const value of Object.values(obj.properties)) {
|
|
204
|
+
if (value.type === 'object') {
|
|
205
|
+
traverse(value, depth + 1);
|
|
206
|
+
} else if (value.type === 'array' && value.items) {
|
|
207
|
+
traverse(value.items, depth + 1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
traverse(schema);
|
|
214
|
+
return maxDepth;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 生成Schema摘要信息
|
|
219
|
+
* @static
|
|
220
|
+
* @param {Object} schema - JSON Schema对象
|
|
221
|
+
* @returns {Object} 摘要信息
|
|
222
|
+
*/
|
|
223
|
+
static summarizeSchema(schema) {
|
|
224
|
+
const paths = this.getFieldPaths(schema);
|
|
225
|
+
const required = this.extractRequiredFields(schema);
|
|
226
|
+
const complexity = this.getSchemaComplexity(schema);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
type: schema.type || 'unknown',
|
|
230
|
+
fieldCount: paths.length,
|
|
231
|
+
requiredCount: required.length,
|
|
232
|
+
complexity,
|
|
233
|
+
hasNested: complexity > 0,
|
|
234
|
+
fields: paths
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = SchemaHelper;
|
|
240
|
+
|