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.
Files changed (145) hide show
  1. package/CHANGELOG.md +130 -113
  2. package/LICENSE +21 -21
  3. package/README.md +628 -628
  4. package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
  5. package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
  6. package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
  7. package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
  8. package/dist/index.cjs +75 -29
  9. package/dist/index.d.cts +10 -4
  10. package/dist/index.d.ts +10 -4
  11. package/dist/index.js +75 -29
  12. package/dist/plugins/custom-format.cjs +33 -17
  13. package/dist/plugins/custom-format.d.cts +1 -1
  14. package/dist/plugins/custom-format.d.ts +1 -1
  15. package/dist/plugins/custom-format.js +33 -17
  16. package/dist/plugins/custom-type-example.cjs +33 -17
  17. package/dist/plugins/custom-type-example.d.cts +1 -1
  18. package/dist/plugins/custom-type-example.d.ts +1 -1
  19. package/dist/plugins/custom-type-example.js +33 -17
  20. package/dist/plugins/custom-validator.cjs +0 -2
  21. package/dist/plugins/custom-validator.d.cts +1 -1
  22. package/dist/plugins/custom-validator.d.ts +1 -1
  23. package/dist/plugins/custom-validator.js +0 -2
  24. package/docs/FEATURE-INDEX.md +553 -553
  25. package/docs/add-custom-locale.md +496 -496
  26. package/docs/add-keyword.md +24 -24
  27. package/docs/api-reference.md +1047 -1047
  28. package/docs/api.md +13 -13
  29. package/docs/best-practices-project-structure.md +417 -417
  30. package/docs/best-practices.md +712 -712
  31. package/docs/cache-manager.md +344 -344
  32. package/docs/compile.md +45 -45
  33. package/docs/conditional-api.md +1307 -1307
  34. package/docs/custom-extensions-guide.md +339 -339
  35. package/docs/design-philosophy.md +606 -606
  36. package/docs/doc-index.md +324 -324
  37. package/docs/dsl-syntax.md +714 -714
  38. package/docs/dynamic-locale.md +608 -608
  39. package/docs/enum.md +482 -482
  40. package/docs/error-handling.md +1975 -1975
  41. package/docs/export-guide.md +501 -501
  42. package/docs/export-limitations.md +567 -567
  43. package/docs/faq.md +596 -596
  44. package/docs/frontend-i18n-guide.md +307 -307
  45. package/docs/i18n-user-guide.md +487 -487
  46. package/docs/i18n.md +476 -476
  47. package/docs/index.md +48 -48
  48. package/docs/json-schema-basics.md +40 -40
  49. package/docs/label-vs-description.md +271 -271
  50. package/docs/markdown-exporter.md +406 -406
  51. package/docs/mongodb-exporter.md +302 -302
  52. package/docs/multi-language.md +26 -26
  53. package/docs/multi-type-support.md +322 -322
  54. package/docs/mysql-exporter.md +280 -280
  55. package/docs/number-operators.md +449 -449
  56. package/docs/optional-marker-guide.md +326 -326
  57. package/docs/performance-guide.md +49 -49
  58. package/docs/plugin-system.md +381 -381
  59. package/docs/plugin-type-registration.md +34 -34
  60. package/docs/postgresql-exporter.md +311 -311
  61. package/docs/public/favicon.svg +4 -4
  62. package/docs/quick-start.md +435 -435
  63. package/docs/runtime-locale-support.md +532 -532
  64. package/docs/schema-helper.md +345 -345
  65. package/docs/schema-utils-advanced-issues.md +23 -23
  66. package/docs/schema-utils-best-practices.md +20 -20
  67. package/docs/schema-utils-chaining.md +150 -150
  68. package/docs/schema-utils.md +524 -524
  69. package/docs/security-checklist.md +20 -20
  70. package/docs/string-extensions.md +488 -488
  71. package/docs/troubleshooting.md +486 -486
  72. package/docs/type-converter.md +310 -310
  73. package/docs/type-reference.md +242 -242
  74. package/docs/typescript-guide.md +584 -584
  75. package/docs/union-type-guide.md +157 -157
  76. package/docs/union-types.md +284 -284
  77. package/docs/validate-async.md +491 -491
  78. package/docs/validate-batch.md +49 -49
  79. package/docs/validate-dsl-object-support.md +578 -578
  80. package/docs/validate.md +506 -506
  81. package/docs/validation-guide.md +502 -502
  82. package/docs/validator.md +39 -39
  83. package/package.json +131 -131
  84. package/plugins/custom-format.cjs +8 -8
  85. package/plugins/custom-type-example.cjs +8 -8
  86. package/plugins/custom-validator.cjs +8 -8
  87. package/src/adapters/DslAdapter.ts +111 -111
  88. package/src/adapters/index.ts +1 -1
  89. package/src/config/constants.ts +83 -83
  90. package/src/config/index.ts +2 -2
  91. package/src/config/patterns.ts +77 -77
  92. package/src/core/CacheManager.ts +169 -159
  93. package/src/core/ConditionalBuilder.ts +382 -382
  94. package/src/core/ConditionalRuntime.ts +27 -27
  95. package/src/core/ConditionalValidator.ts +254 -254
  96. package/src/core/DslBuilder.ts +687 -677
  97. package/src/core/ErrorCodes.ts +38 -38
  98. package/src/core/ErrorFormatter.ts +271 -271
  99. package/src/core/JSONSchemaCore.ts +65 -65
  100. package/src/core/Locale.ts +187 -187
  101. package/src/core/MessageTemplate.ts +42 -42
  102. package/src/core/ObjectDslBuilder.ts +64 -64
  103. package/src/core/PluginManager.ts +326 -326
  104. package/src/core/StringExtensions.ts +140 -140
  105. package/src/core/TemplateEngine.ts +44 -44
  106. package/src/core/Validator.ts +448 -448
  107. package/src/errors/I18nError.ts +159 -159
  108. package/src/errors/ValidationError.ts +105 -105
  109. package/src/exporters/BaseExporter.ts +60 -60
  110. package/src/exporters/MarkdownExporter.ts +305 -305
  111. package/src/exporters/MongoDBExporter.ts +126 -126
  112. package/src/exporters/MySQLExporter.ts +156 -155
  113. package/src/exporters/PostgreSQLExporter.ts +222 -222
  114. package/src/exporters/index.ts +18 -18
  115. package/src/index.ts +651 -633
  116. package/src/locales/en-US.ts +160 -160
  117. package/src/locales/es-ES.ts +160 -160
  118. package/src/locales/fr-FR.ts +160 -160
  119. package/src/locales/index.ts +103 -103
  120. package/src/locales/ja-JP.ts +160 -160
  121. package/src/locales/types.ts +156 -156
  122. package/src/locales/zh-CN.ts +160 -160
  123. package/src/parser/ConstraintParser.ts +101 -101
  124. package/src/parser/DslParser.ts +470 -470
  125. package/src/parser/SchemaCompiler.ts +66 -66
  126. package/src/parser/TypeRegistry.ts +250 -250
  127. package/src/parser/index.ts +6 -6
  128. package/src/plugins/custom-format.ts +124 -126
  129. package/src/plugins/custom-type-example.ts +106 -108
  130. package/src/plugins/custom-validator.ts +138 -140
  131. package/src/types/conditional.ts +28 -28
  132. package/src/types/config.ts +59 -59
  133. package/src/types/dsl.ts +131 -131
  134. package/src/types/error.ts +60 -60
  135. package/src/types/index.ts +17 -17
  136. package/src/types/infer.ts +127 -127
  137. package/src/types/plugin.ts +58 -58
  138. package/src/types/safe-regex.d.ts +9 -9
  139. package/src/types/schema.ts +66 -66
  140. package/src/types/validate.ts +71 -71
  141. package/src/utils/SchemaHelper.ts +196 -196
  142. package/src/utils/SchemaUtils.ts +365 -346
  143. package/src/utils/TypeConverter.ts +215 -215
  144. package/src/utils/index.ts +10 -10
  145. package/src/validators/CustomKeywords.ts +477 -477
@@ -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
@@ -1,2 +1,2 @@
1
- export * from './constants.js'
2
- export * from './patterns.js'
1
+ export * from './constants.js'
2
+ export * from './patterns.js'
@@ -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 }
@@ -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 readonly _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) this._maxSize = opts.maxSize
65
- if (opts.ttl !== undefined) this._ttl = opts.ttl
66
- if (opts.enabled !== undefined) this._enabled = opts.enabled
67
- if (opts.statsEnabled !== undefined) this._statsEnabled = opts.statsEnabled
68
- }
69
-
70
- /**
71
- * Retrieve a cached AJV compile function.
72
- * @returns cached compile function, or null on miss (BD-04: undefined → null)
73
- */
74
- get(key: string): CacheValue | null {
75
- if (!this._enabled || key == null) return null
76
- try {
77
- const result = this._cache.get(String(key)) as CacheValue | undefined
78
- return result !== undefined ? result : null
79
- } catch {
80
- return null
81
- }
82
- }
83
-
84
- /**
85
- * Write a value to the cache.
86
- */
87
- set(key: string, value: CacheValue, ttl?: number): void {
88
- if (!this._enabled || key == null) return
89
- try {
90
- this._cache.set(String(key), value, ttl ?? this._ttl)
91
- } catch {
92
- // Silently ignore invalid keys for v1 compat
93
- }
94
- }
95
-
96
- /**
97
- * Delete a single cache entry.
98
- */
99
- delete(key: string): boolean {
100
- return this._cache.del(key)
101
- }
102
-
103
- /**
104
- * Check whether a key exists in the cache.
105
- */
106
- has(key: string): boolean {
107
- if (!this._enabled) return false
108
- return this._cache.has(key)
109
- }
110
-
111
- /**
112
- * Clear all cache entries.
113
- */
114
- clear(): void {
115
- this._cache.clear()
116
- this._clears++
117
- }
118
-
119
- /**
120
- * Return the current number of cache entries.
121
- */
122
- size(): number {
123
- return this._cache.keys().length
124
- }
125
-
126
- /**
127
- * Return cache statistics.
128
- */
129
- getStats(): CacheStats {
130
- if (!this._statsEnabled) {
131
- return {
132
- hits: 0, misses: 0, sets: 0, deletes: 0, evictions: 0,
133
- clears: 0, hitRate: '0.00', size: 0, maxSize: this._maxSize, enabled: this._enabled,
134
- }
135
- }
136
- const inner = this._cache.getStats()
137
- const total = inner.hits + inner.misses
138
- return {
139
- hits: inner.hits,
140
- misses: inner.misses,
141
- sets: inner.sets,
142
- deletes: inner.deletes,
143
- evictions: (inner as unknown as Record<string, unknown>).evictions as number ?? 0,
144
- clears: this._clears,
145
- hitRate: total > 0 ? ((inner.hits / total) * 100).toFixed(2) : '0.00',
146
- size: inner.entries,
147
- maxSize: this._maxSize,
148
- enabled: this._enabled,
149
- }
150
- }
151
-
152
- /**
153
- * Reset all hit/miss/eviction counters.
154
- */
155
- resetStats(): void {
156
- this._cache.resetStats()
157
- this._clears = 0
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
+ }