schema-dsl 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +130 -113
- package/LICENSE +21 -21
- package/README.md +628 -628
- package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
- package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
- package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
- package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
- package/dist/index.cjs +75 -29
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +75 -29
- package/dist/plugins/custom-format.cjs +33 -17
- package/dist/plugins/custom-format.d.cts +1 -1
- package/dist/plugins/custom-format.d.ts +1 -1
- package/dist/plugins/custom-format.js +33 -17
- package/dist/plugins/custom-type-example.cjs +33 -17
- package/dist/plugins/custom-type-example.d.cts +1 -1
- package/dist/plugins/custom-type-example.d.ts +1 -1
- package/dist/plugins/custom-type-example.js +33 -17
- package/dist/plugins/custom-validator.cjs +0 -2
- package/dist/plugins/custom-validator.d.cts +1 -1
- package/dist/plugins/custom-validator.d.ts +1 -1
- package/dist/plugins/custom-validator.js +0 -2
- package/docs/FEATURE-INDEX.md +553 -553
- package/docs/add-custom-locale.md +496 -496
- package/docs/add-keyword.md +24 -24
- package/docs/api-reference.md +1047 -1047
- package/docs/api.md +13 -13
- package/docs/best-practices-project-structure.md +417 -417
- package/docs/best-practices.md +712 -712
- package/docs/cache-manager.md +344 -344
- package/docs/compile.md +45 -45
- package/docs/conditional-api.md +1307 -1307
- package/docs/custom-extensions-guide.md +339 -339
- package/docs/design-philosophy.md +606 -606
- package/docs/doc-index.md +324 -324
- package/docs/dsl-syntax.md +714 -714
- package/docs/dynamic-locale.md +608 -608
- package/docs/enum.md +482 -482
- package/docs/error-handling.md +1975 -1975
- package/docs/export-guide.md +501 -501
- package/docs/export-limitations.md +567 -567
- package/docs/faq.md +596 -596
- package/docs/frontend-i18n-guide.md +307 -307
- package/docs/i18n-user-guide.md +487 -487
- package/docs/i18n.md +476 -476
- package/docs/index.md +48 -48
- package/docs/json-schema-basics.md +40 -40
- package/docs/label-vs-description.md +271 -271
- package/docs/markdown-exporter.md +406 -406
- package/docs/mongodb-exporter.md +302 -302
- package/docs/multi-language.md +26 -26
- package/docs/multi-type-support.md +322 -322
- package/docs/mysql-exporter.md +280 -280
- package/docs/number-operators.md +449 -449
- package/docs/optional-marker-guide.md +326 -326
- package/docs/performance-guide.md +49 -49
- package/docs/plugin-system.md +381 -381
- package/docs/plugin-type-registration.md +34 -34
- package/docs/postgresql-exporter.md +311 -311
- package/docs/public/favicon.svg +4 -4
- package/docs/quick-start.md +435 -435
- package/docs/runtime-locale-support.md +532 -532
- package/docs/schema-helper.md +345 -345
- package/docs/schema-utils-advanced-issues.md +23 -23
- package/docs/schema-utils-best-practices.md +20 -20
- package/docs/schema-utils-chaining.md +150 -150
- package/docs/schema-utils.md +524 -524
- package/docs/security-checklist.md +20 -20
- package/docs/string-extensions.md +488 -488
- package/docs/troubleshooting.md +486 -486
- package/docs/type-converter.md +310 -310
- package/docs/type-reference.md +242 -242
- package/docs/typescript-guide.md +584 -584
- package/docs/union-type-guide.md +157 -157
- package/docs/union-types.md +284 -284
- package/docs/validate-async.md +491 -491
- package/docs/validate-batch.md +49 -49
- package/docs/validate-dsl-object-support.md +578 -578
- package/docs/validate.md +506 -506
- package/docs/validation-guide.md +502 -502
- package/docs/validator.md +39 -39
- package/package.json +131 -131
- package/plugins/custom-format.cjs +8 -8
- package/plugins/custom-type-example.cjs +8 -8
- package/plugins/custom-validator.cjs +8 -8
- package/src/adapters/DslAdapter.ts +111 -111
- package/src/adapters/index.ts +1 -1
- package/src/config/constants.ts +83 -83
- package/src/config/index.ts +2 -2
- package/src/config/patterns.ts +77 -77
- package/src/core/CacheManager.ts +169 -159
- package/src/core/ConditionalBuilder.ts +382 -382
- package/src/core/ConditionalRuntime.ts +27 -27
- package/src/core/ConditionalValidator.ts +254 -254
- package/src/core/DslBuilder.ts +687 -677
- package/src/core/ErrorCodes.ts +38 -38
- package/src/core/ErrorFormatter.ts +271 -271
- package/src/core/JSONSchemaCore.ts +65 -65
- package/src/core/Locale.ts +187 -187
- package/src/core/MessageTemplate.ts +42 -42
- package/src/core/ObjectDslBuilder.ts +64 -64
- package/src/core/PluginManager.ts +326 -326
- package/src/core/StringExtensions.ts +140 -140
- package/src/core/TemplateEngine.ts +44 -44
- package/src/core/Validator.ts +448 -448
- package/src/errors/I18nError.ts +159 -159
- package/src/errors/ValidationError.ts +105 -105
- package/src/exporters/BaseExporter.ts +60 -60
- package/src/exporters/MarkdownExporter.ts +305 -305
- package/src/exporters/MongoDBExporter.ts +126 -126
- package/src/exporters/MySQLExporter.ts +156 -155
- package/src/exporters/PostgreSQLExporter.ts +222 -222
- package/src/exporters/index.ts +18 -18
- package/src/index.ts +651 -633
- package/src/locales/en-US.ts +160 -160
- package/src/locales/es-ES.ts +160 -160
- package/src/locales/fr-FR.ts +160 -160
- package/src/locales/index.ts +103 -103
- package/src/locales/ja-JP.ts +160 -160
- package/src/locales/types.ts +156 -156
- package/src/locales/zh-CN.ts +160 -160
- package/src/parser/ConstraintParser.ts +101 -101
- package/src/parser/DslParser.ts +470 -470
- package/src/parser/SchemaCompiler.ts +66 -66
- package/src/parser/TypeRegistry.ts +250 -250
- package/src/parser/index.ts +6 -6
- package/src/plugins/custom-format.ts +124 -126
- package/src/plugins/custom-type-example.ts +106 -108
- package/src/plugins/custom-validator.ts +138 -140
- package/src/types/conditional.ts +28 -28
- package/src/types/config.ts +59 -59
- package/src/types/dsl.ts +131 -131
- package/src/types/error.ts +60 -60
- package/src/types/index.ts +17 -17
- package/src/types/infer.ts +127 -127
- package/src/types/plugin.ts +58 -58
- package/src/types/safe-regex.d.ts +9 -9
- package/src/types/schema.ts +66 -66
- package/src/types/validate.ts +71 -71
- package/src/utils/SchemaHelper.ts +196 -196
- package/src/utils/SchemaUtils.ts +365 -346
- package/src/utils/TypeConverter.ts +215 -215
- package/src/utils/index.ts +10 -10
- package/src/validators/CustomKeywords.ts +477 -477
package/src/config/constants.ts
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Global constants.
|
|
3
|
-
* Fixes:
|
|
4
|
-
* CF-01 IPv4 regex too permissive → replaced with RFC-compliant standard regex
|
|
5
|
-
* CF-02 IPv6 regex did not support compressed notation → replaced with full RFC 5952 compatible regex
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// ========== Validation config ==========
|
|
9
|
-
export const VALIDATION = {
|
|
10
|
-
MAX_RECURSION_DEPTH: 100,
|
|
11
|
-
MAX_ARRAY_SIZE: 100_000,
|
|
12
|
-
MAX_STRING_LENGTH: 1_000_000,
|
|
13
|
-
MAX_OBJECT_KEYS: 10_000,
|
|
14
|
-
DEFAULT_TIMEOUT: 5_000,
|
|
15
|
-
REGEX_TIMEOUT: 100,
|
|
16
|
-
CUSTOM_VALIDATOR_TIMEOUT: 1_000,
|
|
17
|
-
} as const
|
|
18
|
-
|
|
19
|
-
// ========== Cache config ==========
|
|
20
|
-
export const CACHE = {
|
|
21
|
-
ENABLED: true,
|
|
22
|
-
SCHEMA_CACHE: {
|
|
23
|
-
MAX_SIZE: 5_000,
|
|
24
|
-
TTL: 3_600_000, // 1 hour
|
|
25
|
-
STRATEGY: 'LRU',
|
|
26
|
-
},
|
|
27
|
-
STATS_ENABLED: true,
|
|
28
|
-
} as const
|
|
29
|
-
|
|
30
|
-
// ========== Format validation regex ==========
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* CF-01 fix: RFC-compliant IPv4 regex.
|
|
34
|
-
* Each octet 0-255, four groups, full-string match.
|
|
35
|
-
* Rejects invalid addresses such as 999.999.999.999.
|
|
36
|
-
*/
|
|
37
|
-
const IPV4_OCTET = '(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)'
|
|
38
|
-
export const PATTERN_IPV4 = new RegExp(`^(?:${IPV4_OCTET}\\.){3}${IPV4_OCTET}$`)
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* CF-02 fix: Full IPv6 regex (RFC 5952 compatible).
|
|
42
|
-
* Covers:
|
|
43
|
-
* - Fully-expanded: 8 groups of hex
|
|
44
|
-
* - :: compressed: prefix / suffix / standalone :: variants
|
|
45
|
-
* No nested quantifiers (avoids ReDoS).
|
|
46
|
-
*/
|
|
47
|
-
const HEX4 = '[0-9a-fA-F]{1,4}'
|
|
48
|
-
const IPV6_FULL = `(?:${HEX4}:){7}${HEX4}`
|
|
49
|
-
const IPV6_COMPRESS = [
|
|
50
|
-
`(?:${HEX4}:){1,7}:`, // n:...:
|
|
51
|
-
`(?:${HEX4}:){1,6}:${HEX4}`, // n:...:n
|
|
52
|
-
`(?:${HEX4}:){1,5}(?::${HEX4}){1,2}`,
|
|
53
|
-
`(?:${HEX4}:){1,4}(?::${HEX4}){1,3}`,
|
|
54
|
-
`(?:${HEX4}:){1,3}(?::${HEX4}){1,4}`,
|
|
55
|
-
`(?:${HEX4}:){1,2}(?::${HEX4}){1,5}`,
|
|
56
|
-
`${HEX4}:(?::${HEX4}){1,6}`,
|
|
57
|
-
`:(?::${HEX4}){1,7}`, // ::n...
|
|
58
|
-
`::`, // standalone :: (all-zeros)
|
|
59
|
-
].join('|')
|
|
60
|
-
export const PATTERN_IPV6 = new RegExp(`^(?:${IPV6_FULL}|${IPV6_COMPRESS})$`)
|
|
61
|
-
|
|
62
|
-
export const FORMATS = {
|
|
63
|
-
BUILT_IN: [
|
|
64
|
-
'email', 'url', 'uri', 'uuid', 'ipv4', 'ipv6',
|
|
65
|
-
'hostname', 'date', 'date-time', 'time', 'regex', 'json',
|
|
66
|
-
] as const,
|
|
67
|
-
PATTERNS: {
|
|
68
|
-
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
69
|
-
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
70
|
-
ipv4: PATTERN_IPV4,
|
|
71
|
-
ipv6: PATTERN_IPV6,
|
|
72
|
-
hostname: /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i,
|
|
73
|
-
dateTime: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z?$/,
|
|
74
|
-
date: /^\d{4}-\d{2}-\d{2}$/,
|
|
75
|
-
time: /^\d{2}:\d{2}:\d{2}$/,
|
|
76
|
-
},
|
|
77
|
-
} as const
|
|
78
|
-
|
|
79
|
-
// ========== Plugin config ==========
|
|
80
|
-
export const PLUGINS = {
|
|
81
|
-
MAX_PLUGINS: 50,
|
|
82
|
-
NAMING_CONVENTION: /^schema-dsl-plugin-/,
|
|
83
|
-
} as const
|
|
1
|
+
/**
|
|
2
|
+
* Global constants.
|
|
3
|
+
* Fixes:
|
|
4
|
+
* CF-01 IPv4 regex too permissive → replaced with RFC-compliant standard regex
|
|
5
|
+
* CF-02 IPv6 regex did not support compressed notation → replaced with full RFC 5952 compatible regex
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ========== Validation config ==========
|
|
9
|
+
export const VALIDATION = {
|
|
10
|
+
MAX_RECURSION_DEPTH: 100,
|
|
11
|
+
MAX_ARRAY_SIZE: 100_000,
|
|
12
|
+
MAX_STRING_LENGTH: 1_000_000,
|
|
13
|
+
MAX_OBJECT_KEYS: 10_000,
|
|
14
|
+
DEFAULT_TIMEOUT: 5_000,
|
|
15
|
+
REGEX_TIMEOUT: 100,
|
|
16
|
+
CUSTOM_VALIDATOR_TIMEOUT: 1_000,
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
// ========== Cache config ==========
|
|
20
|
+
export const CACHE = {
|
|
21
|
+
ENABLED: true,
|
|
22
|
+
SCHEMA_CACHE: {
|
|
23
|
+
MAX_SIZE: 5_000,
|
|
24
|
+
TTL: 3_600_000, // 1 hour
|
|
25
|
+
STRATEGY: 'LRU',
|
|
26
|
+
},
|
|
27
|
+
STATS_ENABLED: true,
|
|
28
|
+
} as const
|
|
29
|
+
|
|
30
|
+
// ========== Format validation regex ==========
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* CF-01 fix: RFC-compliant IPv4 regex.
|
|
34
|
+
* Each octet 0-255, four groups, full-string match.
|
|
35
|
+
* Rejects invalid addresses such as 999.999.999.999.
|
|
36
|
+
*/
|
|
37
|
+
const IPV4_OCTET = '(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)'
|
|
38
|
+
export const PATTERN_IPV4 = new RegExp(`^(?:${IPV4_OCTET}\\.){3}${IPV4_OCTET}$`)
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* CF-02 fix: Full IPv6 regex (RFC 5952 compatible).
|
|
42
|
+
* Covers:
|
|
43
|
+
* - Fully-expanded: 8 groups of hex
|
|
44
|
+
* - :: compressed: prefix / suffix / standalone :: variants
|
|
45
|
+
* No nested quantifiers (avoids ReDoS).
|
|
46
|
+
*/
|
|
47
|
+
const HEX4 = '[0-9a-fA-F]{1,4}'
|
|
48
|
+
const IPV6_FULL = `(?:${HEX4}:){7}${HEX4}`
|
|
49
|
+
const IPV6_COMPRESS = [
|
|
50
|
+
`(?:${HEX4}:){1,7}:`, // n:...:
|
|
51
|
+
`(?:${HEX4}:){1,6}:${HEX4}`, // n:...:n
|
|
52
|
+
`(?:${HEX4}:){1,5}(?::${HEX4}){1,2}`,
|
|
53
|
+
`(?:${HEX4}:){1,4}(?::${HEX4}){1,3}`,
|
|
54
|
+
`(?:${HEX4}:){1,3}(?::${HEX4}){1,4}`,
|
|
55
|
+
`(?:${HEX4}:){1,2}(?::${HEX4}){1,5}`,
|
|
56
|
+
`${HEX4}:(?::${HEX4}){1,6}`,
|
|
57
|
+
`:(?::${HEX4}){1,7}`, // ::n...
|
|
58
|
+
`::`, // standalone :: (all-zeros)
|
|
59
|
+
].join('|')
|
|
60
|
+
export const PATTERN_IPV6 = new RegExp(`^(?:${IPV6_FULL}|${IPV6_COMPRESS})$`)
|
|
61
|
+
|
|
62
|
+
export const FORMATS = {
|
|
63
|
+
BUILT_IN: [
|
|
64
|
+
'email', 'url', 'uri', 'uuid', 'ipv4', 'ipv6',
|
|
65
|
+
'hostname', 'date', 'date-time', 'time', 'regex', 'json',
|
|
66
|
+
] as const,
|
|
67
|
+
PATTERNS: {
|
|
68
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
69
|
+
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
70
|
+
ipv4: PATTERN_IPV4,
|
|
71
|
+
ipv6: PATTERN_IPV6,
|
|
72
|
+
hostname: /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i,
|
|
73
|
+
dateTime: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z?$/,
|
|
74
|
+
date: /^\d{4}-\d{2}-\d{2}$/,
|
|
75
|
+
time: /^\d{2}:\d{2}:\d{2}$/,
|
|
76
|
+
},
|
|
77
|
+
} as const
|
|
78
|
+
|
|
79
|
+
// ========== Plugin config ==========
|
|
80
|
+
export const PLUGINS = {
|
|
81
|
+
MAX_PLUGINS: 50,
|
|
82
|
+
NAMING_CONVENTION: /^schema-dsl-plugin-/,
|
|
83
|
+
} as const
|
package/src/config/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './constants.js'
|
|
2
|
-
export * from './patterns.js'
|
|
1
|
+
export * from './constants.js'
|
|
2
|
+
export * from './patterns.js'
|
package/src/config/patterns.ts
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Built-in regex validation patterns (migrated from v1 lib/config/patterns/ to a single TypeScript file)
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface PatternConfig {
|
|
6
|
-
pattern: RegExp
|
|
7
|
-
min?: number
|
|
8
|
-
max?: number
|
|
9
|
-
key: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const phone: Record<string, PatternConfig> = {
|
|
13
|
-
cn: { pattern: /^1[3-9]\d{9}$/, min: 11, max: 11, key: 'pattern.phone.cn' },
|
|
14
|
-
us: { pattern: /^\d{10}$/, min: 10, max: 10, key: 'pattern.phone.us' },
|
|
15
|
-
uk: { pattern: /^(\+44\s?)?0?\d{10}$/, min: 10, max: 15, key: 'pattern.phone.uk' },
|
|
16
|
-
hk: { pattern: /^[5-9]\d{7}$/, min: 8, max: 8, key: 'pattern.phone.hk' },
|
|
17
|
-
tw: { pattern: /^09\d{8}$/, min: 10, max: 10, key: 'pattern.phone.tw' },
|
|
18
|
-
international: { pattern: /^\+[1-9]\d{1,14}$/, min: 8, max: 15, key: 'pattern.phone.international' },
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const idCard: Record<string, PatternConfig> = {
|
|
22
|
-
cn: {
|
|
23
|
-
pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
|
|
24
|
-
min: 18, max: 18, key: 'pattern.idCard.cn',
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const creditCard: Record<string, PatternConfig> = {
|
|
29
|
-
visa: { pattern: /^4[0-9]{12}(?:[0-9]{3})?$/, key: 'pattern.creditCard.visa' },
|
|
30
|
-
mastercard: { pattern: /^5[1-5][0-9]{14}$/, key: 'pattern.creditCard.mastercard' },
|
|
31
|
-
amex: { pattern: /^3[47][0-9]{13}$/, key: 'pattern.creditCard.amex' },
|
|
32
|
-
discover: { pattern: /^6(?:011|5[0-9]{2})[0-9]{12}$/, key: 'pattern.creditCard.discover' },
|
|
33
|
-
jcb: { pattern: /^(?:2131|1800|35\d{3})\d{11}$/, key: 'pattern.creditCard.jcb' },
|
|
34
|
-
unionpay: { pattern: /^62[0-9]{14,17}$/, key: 'pattern.creditCard.unionpay' },
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const licensePlate: Record<string, PatternConfig> = {
|
|
38
|
-
cn: {
|
|
39
|
-
pattern: /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]$/,
|
|
40
|
-
key: 'pattern.licensePlate.cn',
|
|
41
|
-
},
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const postalCode: Record<string, PatternConfig> = {
|
|
45
|
-
cn: { pattern: /^[1-9]\d{5}$/, key: 'pattern.postalCode.cn' },
|
|
46
|
-
us: { pattern: /^\d{5}(-\d{4})?$/, key: 'pattern.postalCode.us' },
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const passport: Record<string, PatternConfig> = {
|
|
50
|
-
cn: {
|
|
51
|
-
pattern: /(^[EeKkGgDdHhMQqSs][0-9]{8}$)|(^(([Ee][a-fA-F])|([DdSPp][Ee])|([Kk][Jj])|([Mm][Aa])|(1[45]))[0-9]{7}$)/,
|
|
52
|
-
key: 'pattern.passport.cn',
|
|
53
|
-
},
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const common: Record<string, PatternConfig> = {
|
|
57
|
-
domain: {
|
|
58
|
-
pattern: /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i,
|
|
59
|
-
key: 'pattern.domain', min: 3, max: 253,
|
|
60
|
-
},
|
|
61
|
-
ip: {
|
|
62
|
-
// Composite IPv4 + IPv6 pattern (same as v1)
|
|
63
|
-
pattern:
|
|
64
|
-
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/,
|
|
65
|
-
key: 'pattern.ip',
|
|
66
|
-
},
|
|
67
|
-
base64: {
|
|
68
|
-
pattern: /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,
|
|
69
|
-
key: 'pattern.base64',
|
|
70
|
-
},
|
|
71
|
-
jwt: {
|
|
72
|
-
pattern: /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/,
|
|
73
|
-
key: 'pattern.jwt',
|
|
74
|
-
},
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export const PATTERNS = { phone, idCard, creditCard, licensePlate, postalCode, passport, common }
|
|
1
|
+
/**
|
|
2
|
+
* Built-in regex validation patterns (migrated from v1 lib/config/patterns/ to a single TypeScript file)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface PatternConfig {
|
|
6
|
+
pattern: RegExp
|
|
7
|
+
min?: number
|
|
8
|
+
max?: number
|
|
9
|
+
key: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const phone: Record<string, PatternConfig> = {
|
|
13
|
+
cn: { pattern: /^1[3-9]\d{9}$/, min: 11, max: 11, key: 'pattern.phone.cn' },
|
|
14
|
+
us: { pattern: /^\d{10}$/, min: 10, max: 10, key: 'pattern.phone.us' },
|
|
15
|
+
uk: { pattern: /^(\+44\s?)?0?\d{10}$/, min: 10, max: 15, key: 'pattern.phone.uk' },
|
|
16
|
+
hk: { pattern: /^[5-9]\d{7}$/, min: 8, max: 8, key: 'pattern.phone.hk' },
|
|
17
|
+
tw: { pattern: /^09\d{8}$/, min: 10, max: 10, key: 'pattern.phone.tw' },
|
|
18
|
+
international: { pattern: /^\+[1-9]\d{1,14}$/, min: 8, max: 15, key: 'pattern.phone.international' },
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const idCard: Record<string, PatternConfig> = {
|
|
22
|
+
cn: {
|
|
23
|
+
pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
|
|
24
|
+
min: 18, max: 18, key: 'pattern.idCard.cn',
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const creditCard: Record<string, PatternConfig> = {
|
|
29
|
+
visa: { pattern: /^4[0-9]{12}(?:[0-9]{3})?$/, key: 'pattern.creditCard.visa' },
|
|
30
|
+
mastercard: { pattern: /^5[1-5][0-9]{14}$/, key: 'pattern.creditCard.mastercard' },
|
|
31
|
+
amex: { pattern: /^3[47][0-9]{13}$/, key: 'pattern.creditCard.amex' },
|
|
32
|
+
discover: { pattern: /^6(?:011|5[0-9]{2})[0-9]{12}$/, key: 'pattern.creditCard.discover' },
|
|
33
|
+
jcb: { pattern: /^(?:2131|1800|35\d{3})\d{11}$/, key: 'pattern.creditCard.jcb' },
|
|
34
|
+
unionpay: { pattern: /^62[0-9]{14,17}$/, key: 'pattern.creditCard.unionpay' },
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const licensePlate: Record<string, PatternConfig> = {
|
|
38
|
+
cn: {
|
|
39
|
+
pattern: /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]$/,
|
|
40
|
+
key: 'pattern.licensePlate.cn',
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const postalCode: Record<string, PatternConfig> = {
|
|
45
|
+
cn: { pattern: /^[1-9]\d{5}$/, key: 'pattern.postalCode.cn' },
|
|
46
|
+
us: { pattern: /^\d{5}(-\d{4})?$/, key: 'pattern.postalCode.us' },
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const passport: Record<string, PatternConfig> = {
|
|
50
|
+
cn: {
|
|
51
|
+
pattern: /(^[EeKkGgDdHhMQqSs][0-9]{8}$)|(^(([Ee][a-fA-F])|([DdSPp][Ee])|([Kk][Jj])|([Mm][Aa])|(1[45]))[0-9]{7}$)/,
|
|
52
|
+
key: 'pattern.passport.cn',
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const common: Record<string, PatternConfig> = {
|
|
57
|
+
domain: {
|
|
58
|
+
pattern: /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i,
|
|
59
|
+
key: 'pattern.domain', min: 3, max: 253,
|
|
60
|
+
},
|
|
61
|
+
ip: {
|
|
62
|
+
// Composite IPv4 + IPv6 pattern (same as v1)
|
|
63
|
+
pattern:
|
|
64
|
+
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/,
|
|
65
|
+
key: 'pattern.ip',
|
|
66
|
+
},
|
|
67
|
+
base64: {
|
|
68
|
+
pattern: /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,
|
|
69
|
+
key: 'pattern.base64',
|
|
70
|
+
},
|
|
71
|
+
jwt: {
|
|
72
|
+
pattern: /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/,
|
|
73
|
+
key: 'pattern.jwt',
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const PATTERNS = { phone, idCard, creditCard, licensePlate, postalCode, passport, common }
|
package/src/core/CacheManager.ts
CHANGED
|
@@ -1,159 +1,169 @@
|
|
|
1
|
-
import { MemoryCache } from 'cache-hub'
|
|
2
|
-
import { CACHE } from '../config/constants.js'
|
|
3
|
-
|
|
4
|
-
type CacheValue = unknown
|
|
5
|
-
|
|
6
|
-
export interface CacheStats {
|
|
7
|
-
hits: number
|
|
8
|
-
misses: number
|
|
9
|
-
sets: number
|
|
10
|
-
deletes: number
|
|
11
|
-
evictions: number
|
|
12
|
-
clears: number
|
|
13
|
-
hitRate: string
|
|
14
|
-
size: number
|
|
15
|
-
maxSize: number
|
|
16
|
-
enabled: boolean
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* CacheManager — LRU cache for compiled AJV schemas.
|
|
21
|
-
*
|
|
22
|
-
* v2 delegates to cache-hub's MemoryCache (fix BD-04: miss returns undefined → normalized to null).
|
|
23
|
-
*
|
|
24
|
-
* cache-hub MemoryCache actual API:
|
|
25
|
-
* get(key) → value | undefined
|
|
26
|
-
* set(key, value, opts?) — opts.ttl in ms
|
|
27
|
-
* del(key) → boolean ← note: del, not delete
|
|
28
|
-
* has(key) → boolean
|
|
29
|
-
* clear() → void
|
|
30
|
-
* keys() → string[]
|
|
31
|
-
* getStats() → { hits, misses, hitRate, entries, sets, deletes, evictions, memoryUsage }
|
|
32
|
-
*/
|
|
33
|
-
export class CacheManager {
|
|
34
|
-
private _enabled: boolean
|
|
35
|
-
private _maxSize: number
|
|
36
|
-
private _ttl: number
|
|
37
|
-
private
|
|
38
|
-
private _statsEnabled: boolean = true
|
|
39
|
-
private _clears = 0
|
|
40
|
-
|
|
41
|
-
constructor(options: {
|
|
42
|
-
maxSize?: number
|
|
43
|
-
ttl?: number
|
|
44
|
-
enabled?: boolean
|
|
45
|
-
statsEnabled?: boolean
|
|
46
|
-
} = {}) {
|
|
47
|
-
this._maxSize = options.maxSize ?? CACHE.SCHEMA_CACHE.MAX_SIZE
|
|
48
|
-
this._ttl = options.ttl ?? CACHE.SCHEMA_CACHE.TTL
|
|
49
|
-
this._enabled = options.enabled !== false
|
|
50
|
-
this._cache = new MemoryCache({ maxEntries: this._maxSize })
|
|
51
|
-
this._statsEnabled = options.statsEnabled !== false
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get options(): { maxSize: number; ttl: number; enabled: boolean; statsEnabled: boolean } {
|
|
55
|
-
return {
|
|
56
|
-
maxSize: this._maxSize,
|
|
57
|
-
ttl: this._ttl,
|
|
58
|
-
enabled: this._enabled,
|
|
59
|
-
statsEnabled: this._statsEnabled,
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
set options(opts: Partial<{ maxSize: number; ttl: number; enabled: boolean; statsEnabled: boolean }>) {
|
|
64
|
-
if (opts.maxSize !== undefined
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
1
|
+
import { MemoryCache } from 'cache-hub'
|
|
2
|
+
import { CACHE } from '../config/constants.js'
|
|
3
|
+
|
|
4
|
+
type CacheValue = unknown
|
|
5
|
+
|
|
6
|
+
export interface CacheStats {
|
|
7
|
+
hits: number
|
|
8
|
+
misses: number
|
|
9
|
+
sets: number
|
|
10
|
+
deletes: number
|
|
11
|
+
evictions: number
|
|
12
|
+
clears: number
|
|
13
|
+
hitRate: string
|
|
14
|
+
size: number
|
|
15
|
+
maxSize: number
|
|
16
|
+
enabled: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* CacheManager — LRU cache for compiled AJV schemas.
|
|
21
|
+
*
|
|
22
|
+
* v2 delegates to cache-hub's MemoryCache (fix BD-04: miss returns undefined → normalized to null).
|
|
23
|
+
*
|
|
24
|
+
* cache-hub MemoryCache actual API:
|
|
25
|
+
* get(key) → value | undefined
|
|
26
|
+
* set(key, value, opts?) — opts.ttl in ms
|
|
27
|
+
* del(key) → boolean ← note: del, not delete
|
|
28
|
+
* has(key) → boolean
|
|
29
|
+
* clear() → void
|
|
30
|
+
* keys() → string[]
|
|
31
|
+
* getStats() → { hits, misses, hitRate, entries, sets, deletes, evictions, memoryUsage }
|
|
32
|
+
*/
|
|
33
|
+
export class CacheManager {
|
|
34
|
+
private _enabled: boolean
|
|
35
|
+
private _maxSize: number
|
|
36
|
+
private _ttl: number
|
|
37
|
+
private _cache: MemoryCache
|
|
38
|
+
private _statsEnabled: boolean = true
|
|
39
|
+
private _clears = 0
|
|
40
|
+
|
|
41
|
+
constructor(options: {
|
|
42
|
+
maxSize?: number
|
|
43
|
+
ttl?: number
|
|
44
|
+
enabled?: boolean
|
|
45
|
+
statsEnabled?: boolean
|
|
46
|
+
} = {}) {
|
|
47
|
+
this._maxSize = options.maxSize ?? CACHE.SCHEMA_CACHE.MAX_SIZE
|
|
48
|
+
this._ttl = options.ttl ?? CACHE.SCHEMA_CACHE.TTL
|
|
49
|
+
this._enabled = options.enabled !== false
|
|
50
|
+
this._cache = new MemoryCache({ maxEntries: this._maxSize })
|
|
51
|
+
this._statsEnabled = options.statsEnabled !== false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get options(): { maxSize: number; ttl: number; enabled: boolean; statsEnabled: boolean } {
|
|
55
|
+
return {
|
|
56
|
+
maxSize: this._maxSize,
|
|
57
|
+
ttl: this._ttl,
|
|
58
|
+
enabled: this._enabled,
|
|
59
|
+
statsEnabled: this._statsEnabled,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
set options(opts: Partial<{ maxSize: number; ttl: number; enabled: boolean; statsEnabled: boolean }>) {
|
|
64
|
+
if (opts.maxSize !== undefined && opts.maxSize !== this._maxSize) {
|
|
65
|
+
this._maxSize = opts.maxSize
|
|
66
|
+
// Rebuild MemoryCache so the new capacity actually takes effect
|
|
67
|
+
const oldKeys = this._cache.keys()
|
|
68
|
+
const newCache = new MemoryCache({ maxEntries: this._maxSize })
|
|
69
|
+
for (const key of oldKeys) {
|
|
70
|
+
const val = this._cache.get(key)
|
|
71
|
+
if (val !== undefined) newCache.set(key, val)
|
|
72
|
+
}
|
|
73
|
+
this._cache = newCache
|
|
74
|
+
}
|
|
75
|
+
if (opts.ttl !== undefined) this._ttl = opts.ttl
|
|
76
|
+
if (opts.enabled !== undefined) this._enabled = opts.enabled
|
|
77
|
+
if (opts.statsEnabled !== undefined) this._statsEnabled = opts.statsEnabled
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Retrieve a cached AJV compile function.
|
|
82
|
+
* @returns cached compile function, or null on miss (BD-04: undefined → null)
|
|
83
|
+
*/
|
|
84
|
+
get(key: string): CacheValue | null {
|
|
85
|
+
if (!this._enabled || key == null) return null
|
|
86
|
+
try {
|
|
87
|
+
const result = this._cache.get(String(key)) as CacheValue | undefined
|
|
88
|
+
return result !== undefined ? result : null
|
|
89
|
+
} catch {
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Write a value to the cache.
|
|
96
|
+
*/
|
|
97
|
+
set(key: string, value: CacheValue, ttl?: number): void {
|
|
98
|
+
if (!this._enabled || key == null) return
|
|
99
|
+
try {
|
|
100
|
+
this._cache.set(String(key), value, ttl ?? this._ttl)
|
|
101
|
+
} catch {
|
|
102
|
+
// Silently ignore invalid keys for v1 compat
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Delete a single cache entry.
|
|
108
|
+
*/
|
|
109
|
+
delete(key: string): boolean {
|
|
110
|
+
return this._cache.del(key)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check whether a key exists in the cache.
|
|
115
|
+
*/
|
|
116
|
+
has(key: string): boolean {
|
|
117
|
+
if (!this._enabled) return false
|
|
118
|
+
return this._cache.has(key)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Clear all cache entries.
|
|
123
|
+
*/
|
|
124
|
+
clear(): void {
|
|
125
|
+
this._cache.clear()
|
|
126
|
+
this._clears++
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Return the current number of cache entries.
|
|
131
|
+
*/
|
|
132
|
+
size(): number {
|
|
133
|
+
return this._cache.keys().length
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Return cache statistics.
|
|
138
|
+
*/
|
|
139
|
+
getStats(): CacheStats {
|
|
140
|
+
if (!this._statsEnabled) {
|
|
141
|
+
return {
|
|
142
|
+
hits: 0, misses: 0, sets: 0, deletes: 0, evictions: 0,
|
|
143
|
+
clears: 0, hitRate: '0.00', size: 0, maxSize: this._maxSize, enabled: this._enabled,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const inner = this._cache.getStats()
|
|
147
|
+
const total = inner.hits + inner.misses
|
|
148
|
+
return {
|
|
149
|
+
hits: inner.hits,
|
|
150
|
+
misses: inner.misses,
|
|
151
|
+
sets: inner.sets,
|
|
152
|
+
deletes: inner.deletes,
|
|
153
|
+
evictions: (inner as unknown as Record<string, unknown>).evictions as number ?? 0,
|
|
154
|
+
clears: this._clears,
|
|
155
|
+
hitRate: total > 0 ? ((inner.hits / total) * 100).toFixed(2) : '0.00',
|
|
156
|
+
size: inner.entries,
|
|
157
|
+
maxSize: this._maxSize,
|
|
158
|
+
enabled: this._enabled,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Reset all hit/miss/eviction counters.
|
|
164
|
+
*/
|
|
165
|
+
resetStats(): void {
|
|
166
|
+
this._cache.resetStats()
|
|
167
|
+
this._clears = 0
|
|
168
|
+
}
|
|
169
|
+
}
|