schema-dsl 1.2.4 → 2.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.
Files changed (242) hide show
  1. package/CHANGELOG.md +87 -210
  2. package/README.md +391 -2249
  3. package/dist/DslBuilder-DQDN0ZxZ.d.cts +341 -0
  4. package/dist/DslBuilder-DkLaOo9Q.d.ts +341 -0
  5. package/dist/Validator-C7GsVQOH.d.cts +192 -0
  6. package/dist/Validator-hFWKGxir.d.ts +192 -0
  7. package/dist/index.cjs +6594 -0
  8. package/dist/index.d.cts +1145 -0
  9. package/dist/index.d.ts +1145 -0
  10. package/dist/index.js +6528 -0
  11. package/dist/plugin-CIKtTMtS.d.cts +246 -0
  12. package/dist/plugin-CIKtTMtS.d.ts +246 -0
  13. package/dist/plugins/custom-format.cjs +3802 -0
  14. package/dist/plugins/custom-format.d.cts +12 -0
  15. package/dist/plugins/custom-format.d.ts +12 -0
  16. package/dist/plugins/custom-format.js +3772 -0
  17. package/dist/plugins/custom-type-example.cjs +3795 -0
  18. package/dist/plugins/custom-type-example.d.cts +8 -0
  19. package/dist/plugins/custom-type-example.d.ts +8 -0
  20. package/dist/plugins/custom-type-example.js +3765 -0
  21. package/dist/plugins/custom-validator.cjs +146 -0
  22. package/dist/plugins/custom-validator.d.cts +10 -0
  23. package/dist/plugins/custom-validator.d.ts +10 -0
  24. package/dist/plugins/custom-validator.js +121 -0
  25. package/docs/FEATURE-INDEX.md +102 -68
  26. package/docs/add-custom-locale.md +48 -35
  27. package/docs/add-keyword.md +24 -0
  28. package/docs/api-reference.md +396 -154
  29. package/docs/api.md +13 -0
  30. package/docs/best-practices-project-structure.md +19 -10
  31. package/docs/best-practices.md +93 -53
  32. package/docs/cache-manager.md +23 -15
  33. package/docs/compile.md +45 -0
  34. package/docs/conditional-api.md +40 -11
  35. package/docs/custom-extensions-guide.md +80 -152
  36. package/docs/design-philosophy.md +76 -71
  37. package/docs/doc-index.md +324 -0
  38. package/docs/dsl-syntax.md +69 -19
  39. package/docs/dynamic-locale.md +24 -14
  40. package/docs/enum.md +12 -5
  41. package/docs/error-handling.md +53 -44
  42. package/docs/export-guide.md +47 -8
  43. package/docs/export-limitations.md +27 -11
  44. package/docs/faq.md +86 -67
  45. package/docs/frontend-i18n-guide.md +26 -12
  46. package/docs/i18n-user-guide.md +60 -47
  47. package/docs/i18n.md +51 -32
  48. package/docs/index.md +48 -0
  49. package/docs/json-schema-basics.md +40 -0
  50. package/docs/label-vs-description.md +12 -3
  51. package/docs/markdown-exporter.md +15 -6
  52. package/docs/mongodb-exporter.md +11 -4
  53. package/docs/multi-language.md +26 -0
  54. package/docs/multi-type-support.md +26 -33
  55. package/docs/mysql-exporter.md +9 -2
  56. package/docs/number-operators.md +12 -5
  57. package/docs/optional-marker-guide.md +28 -23
  58. package/docs/performance-guide.md +49 -0
  59. package/docs/plugin-system.md +205 -366
  60. package/docs/plugin-type-registration.md +34 -0
  61. package/docs/postgresql-exporter.md +9 -2
  62. package/docs/public/favicon.svg +5 -0
  63. package/docs/quick-start.md +37 -363
  64. package/docs/runtime-locale-support.md +20 -9
  65. package/docs/schema-helper.md +10 -5
  66. package/docs/schema-utils-advanced-issues.md +23 -0
  67. package/docs/schema-utils-best-practices.md +20 -0
  68. package/docs/schema-utils-chaining.md +7 -0
  69. package/docs/schema-utils.md +76 -42
  70. package/docs/security-checklist.md +20 -0
  71. package/docs/string-extensions.md +17 -9
  72. package/docs/troubleshooting.md +36 -21
  73. package/docs/type-converter.md +41 -50
  74. package/docs/type-reference.md +38 -15
  75. package/docs/typescript-guide.md +53 -42
  76. package/docs/union-type-guide.md +11 -1
  77. package/docs/union-types.md +10 -3
  78. package/docs/validate-async.md +36 -25
  79. package/docs/validate-batch.md +49 -0
  80. package/docs/validate-dsl-object-support.md +33 -28
  81. package/docs/validate.md +36 -16
  82. package/docs/validation-guide.md +25 -7
  83. package/docs/validator.md +39 -0
  84. package/package.json +85 -27
  85. package/plugins/custom-format.cjs +8 -0
  86. package/plugins/custom-type-example.cjs +8 -0
  87. package/plugins/custom-validator.cjs +8 -0
  88. package/src/adapters/DslAdapter.ts +111 -0
  89. package/src/adapters/index.ts +1 -0
  90. package/src/config/constants.ts +83 -0
  91. package/src/config/index.ts +2 -0
  92. package/src/config/patterns.ts +77 -0
  93. package/src/core/CacheManager.ts +159 -0
  94. package/src/core/ConditionalBuilder.ts +382 -0
  95. package/src/core/ConditionalRuntime.ts +28 -0
  96. package/src/core/ConditionalValidator.ts +255 -0
  97. package/src/core/DslBuilder.ts +677 -0
  98. package/src/core/ErrorCodes.ts +38 -0
  99. package/src/core/ErrorFormatter.ts +271 -0
  100. package/src/core/JSONSchemaCore.ts +65 -0
  101. package/src/core/Locale.ts +187 -0
  102. package/src/core/MessageTemplate.ts +42 -0
  103. package/src/core/ObjectDslBuilder.ts +64 -0
  104. package/src/core/PluginManager.ts +326 -0
  105. package/src/core/StringExtensions.ts +140 -0
  106. package/src/core/TemplateEngine.ts +44 -0
  107. package/src/core/Validator.ts +448 -0
  108. package/src/errors/I18nError.ts +159 -0
  109. package/src/errors/ValidationError.ts +105 -0
  110. package/src/exporters/BaseExporter.ts +60 -0
  111. package/src/exporters/MarkdownExporter.ts +305 -0
  112. package/src/exporters/MongoDBExporter.ts +126 -0
  113. package/src/exporters/MySQLExporter.ts +155 -0
  114. package/src/exporters/PostgreSQLExporter.ts +222 -0
  115. package/src/exporters/index.ts +18 -0
  116. package/src/index.ts +633 -0
  117. package/{lib/locales/en-US.js → src/locales/en-US.ts} +21 -37
  118. package/{lib/locales/es-ES.js → src/locales/es-ES.ts} +63 -16
  119. package/{lib/locales/fr-FR.js → src/locales/fr-FR.ts} +74 -27
  120. package/src/locales/index.ts +103 -0
  121. package/{lib/locales/ja-JP.js → src/locales/ja-JP.ts} +59 -17
  122. package/src/locales/types.ts +156 -0
  123. package/{lib/locales/zh-CN.js → src/locales/zh-CN.ts} +21 -38
  124. package/src/parser/ConstraintParser.ts +101 -0
  125. package/src/parser/DslParser.ts +470 -0
  126. package/src/parser/SchemaCompiler.ts +66 -0
  127. package/src/parser/TypeRegistry.ts +250 -0
  128. package/src/parser/index.ts +6 -0
  129. package/src/plugins/custom-format.ts +126 -0
  130. package/src/plugins/custom-type-example.ts +108 -0
  131. package/src/plugins/custom-validator.ts +140 -0
  132. package/src/types/conditional.ts +28 -0
  133. package/src/types/config.ts +59 -0
  134. package/src/types/dsl.ts +131 -0
  135. package/src/types/error.ts +60 -0
  136. package/src/types/index.ts +17 -0
  137. package/src/types/infer.ts +128 -0
  138. package/src/types/plugin.ts +58 -0
  139. package/src/types/safe-regex.d.ts +9 -0
  140. package/src/types/schema.ts +66 -0
  141. package/src/types/validate.ts +71 -0
  142. package/src/utils/SchemaHelper.ts +196 -0
  143. package/src/utils/SchemaUtils.ts +346 -0
  144. package/src/utils/TypeConverter.ts +215 -0
  145. package/src/utils/index.ts +10 -0
  146. package/src/validators/CustomKeywords.ts +477 -0
  147. package/.eslintignore +0 -11
  148. package/.eslintrc.json +0 -27
  149. package/CONTRIBUTING.md +0 -368
  150. package/STATUS.md +0 -491
  151. package/changelogs/v1.0.0.md +0 -328
  152. package/changelogs/v1.0.9.md +0 -367
  153. package/changelogs/v1.1.0.md +0 -389
  154. package/changelogs/v1.1.1.md +0 -308
  155. package/changelogs/v1.1.2.md +0 -183
  156. package/changelogs/v1.1.3.md +0 -161
  157. package/changelogs/v1.1.4.md +0 -432
  158. package/changelogs/v1.1.5.md +0 -493
  159. package/changelogs/v1.1.6.md +0 -211
  160. package/changelogs/v1.1.8.md +0 -376
  161. package/changelogs/v1.2.3.md +0 -124
  162. package/docs/INDEX.md +0 -252
  163. package/docs/issues-resolved-summary.md +0 -196
  164. package/docs/performance-benchmark-report.md +0 -179
  165. package/docs/performance-quick-reference.md +0 -123
  166. package/docs/user-questions-answered.md +0 -353
  167. package/docs/validation-rules-v1.0.2.md +0 -1608
  168. package/examples/README.md +0 -81
  169. package/examples/array-dsl-example.js +0 -227
  170. package/examples/conditional-example.js +0 -288
  171. package/examples/conditional-non-object.js +0 -129
  172. package/examples/conditional-validate-example.js +0 -321
  173. package/examples/custom-extension.js +0 -85
  174. package/examples/dsl-match-example.js +0 -74
  175. package/examples/dsl-style.js +0 -118
  176. package/examples/dynamic-locale-configuration.js +0 -348
  177. package/examples/dynamic-locale-example.js +0 -287
  178. package/examples/enum.examples.js +0 -324
  179. package/examples/export-demo.js +0 -130
  180. package/examples/express-integration.js +0 -376
  181. package/examples/i18n-error-handling-complete.js +0 -381
  182. package/examples/i18n-error-handling-quickstart.md +0 -0
  183. package/examples/i18n-error.examples.js +0 -181
  184. package/examples/i18n-full-demo.js +0 -301
  185. package/examples/i18n-memory-safety.examples.js +0 -268
  186. package/examples/markdown-export.js +0 -71
  187. package/examples/middleware-usage.js +0 -93
  188. package/examples/new-features-comparison.js +0 -315
  189. package/examples/password-reset/README.md +0 -153
  190. package/examples/password-reset/schema.js +0 -26
  191. package/examples/password-reset/test.js +0 -101
  192. package/examples/plugin-system.examples.js +0 -205
  193. package/examples/schema-utils-chaining.examples.js +0 -250
  194. package/examples/simple-example.js +0 -122
  195. package/examples/slug.examples.js +0 -179
  196. package/examples/string-extensions.js +0 -297
  197. package/examples/union-type-example.js +0 -127
  198. package/examples/union-types-example.js +0 -77
  199. package/examples/user-registration/README.md +0 -156
  200. package/examples/user-registration/routes.js +0 -92
  201. package/examples/user-registration/schema.js +0 -150
  202. package/examples/user-registration/server.js +0 -74
  203. package/index.d.ts +0 -3540
  204. package/index.js +0 -457
  205. package/index.mjs +0 -60
  206. package/lib/adapters/DslAdapter.js +0 -871
  207. package/lib/adapters/index.js +0 -20
  208. package/lib/config/constants.js +0 -286
  209. package/lib/config/patterns/common.js +0 -47
  210. package/lib/config/patterns/creditCard.js +0 -9
  211. package/lib/config/patterns/idCard.js +0 -9
  212. package/lib/config/patterns/index.js +0 -9
  213. package/lib/config/patterns/licensePlate.js +0 -4
  214. package/lib/config/patterns/passport.js +0 -4
  215. package/lib/config/patterns/phone.js +0 -9
  216. package/lib/config/patterns/postalCode.js +0 -5
  217. package/lib/core/CacheManager.js +0 -376
  218. package/lib/core/ConditionalBuilder.js +0 -503
  219. package/lib/core/DslBuilder.js +0 -1400
  220. package/lib/core/ErrorCodes.js +0 -233
  221. package/lib/core/ErrorFormatter.js +0 -445
  222. package/lib/core/JSONSchemaCore.js +0 -347
  223. package/lib/core/Locale.js +0 -130
  224. package/lib/core/MessageTemplate.js +0 -98
  225. package/lib/core/PluginManager.js +0 -448
  226. package/lib/core/StringExtensions.js +0 -240
  227. package/lib/core/Validator.js +0 -654
  228. package/lib/errors/I18nError.js +0 -328
  229. package/lib/errors/ValidationError.js +0 -191
  230. package/lib/exporters/MarkdownExporter.js +0 -420
  231. package/lib/exporters/MongoDBExporter.js +0 -162
  232. package/lib/exporters/MySQLExporter.js +0 -212
  233. package/lib/exporters/PostgreSQLExporter.js +0 -289
  234. package/lib/exporters/index.js +0 -24
  235. package/lib/locales/index.js +0 -8
  236. package/lib/utils/LRUCache.js +0 -174
  237. package/lib/utils/SchemaHelper.js +0 -240
  238. package/lib/utils/SchemaUtils.js +0 -445
  239. package/lib/utils/TypeConverter.js +0 -245
  240. package/lib/utils/index.js +0 -13
  241. package/lib/validators/CustomKeywords.js +0 -616
  242. package/lib/validators/index.js +0 -11
package/package.json CHANGED
@@ -1,23 +1,65 @@
1
1
  {
2
2
  "name": "schema-dsl",
3
- "version": "1.2.4",
4
- "description": "简洁强大的JSON Schema验证库 - DSL语法 + String扩展 + 便捷validate",
5
- "main": "index.js",
6
- "types": "index.d.ts",
3
+ "version": "2.0.0",
4
+ "description": "A concise and powerful JSON Schema validation library - DSL syntax + String extensions + convenient validate API",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
7
8
  "exports": {
8
9
  ".": {
9
- "require": "./index.js",
10
- "import": "./index.mjs",
11
- "types": "./index.d.ts"
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "./plugins/custom-format": {
20
+ "import": {
21
+ "types": "./dist/plugins/custom-format.d.ts",
22
+ "default": "./dist/plugins/custom-format.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/plugins/custom-format.d.cts",
26
+ "default": "./dist/plugins/custom-format.cjs"
27
+ }
28
+ },
29
+ "./plugins/custom-validator": {
30
+ "import": {
31
+ "types": "./dist/plugins/custom-validator.d.ts",
32
+ "default": "./dist/plugins/custom-validator.js"
33
+ },
34
+ "require": {
35
+ "types": "./dist/plugins/custom-validator.d.cts",
36
+ "default": "./dist/plugins/custom-validator.cjs"
37
+ }
38
+ },
39
+ "./plugins/custom-type-example": {
40
+ "import": {
41
+ "types": "./dist/plugins/custom-type-example.d.ts",
42
+ "default": "./dist/plugins/custom-type-example.js"
43
+ },
44
+ "require": {
45
+ "types": "./dist/plugins/custom-type-example.d.cts",
46
+ "default": "./dist/plugins/custom-type-example.cjs"
47
+ }
12
48
  }
13
49
  },
14
50
  "scripts": {
15
- "test": "mocha test/unit/**/*.test.js",
16
- "test:integration": "mocha test/integration/**/*.test.js",
17
- "test:all": "mocha test/**/*.test.js",
18
- "coverage": "nyc npm test",
19
- "lint": "eslint lib/**/*.js",
20
- "example": "node examples/dsl-style.js"
51
+ "build": "tsup",
52
+ "dev": "tsup --watch",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "test:coverage": "vitest run --coverage",
56
+ "typecheck": "tsc --noEmit",
57
+ "examples:typecheck": "tsc -p tsconfig.examples.json --noEmit",
58
+ "examples:build": "tsc -p tsconfig.examples.json",
59
+ "lint": "eslint src/**/*.ts",
60
+ "test:version": "node -e \"const p=require('./package.json');const {VERSION}=require('./dist/index.cjs');if(p.version!==VERSION)throw new Error('Version mismatch: '+p.version+' vs '+VERSION);console.log('Version OK:',p.version)\"",
61
+ "bench": "node test/benchmarks/library-comparison.js",
62
+ "prepublishOnly": "npm run build && npm run typecheck && npm test"
21
63
  },
22
64
  "keywords": [
23
65
  "schema",
@@ -51,23 +93,39 @@
51
93
  },
52
94
  "homepage": "https://github.com/vextjs/schema-dsl#readme",
53
95
  "engines": {
54
- "node": ">=12.0.0"
96
+ "node": ">=18.0.0"
55
97
  },
56
98
  "dependencies": {
57
- "ajv": "^8.17.1",
58
- "ajv-formats": "^2.1.1"
99
+ "ajv": "^8.18.0",
100
+ "ajv-formats": "^2.1.1",
101
+ "cache-hub": "^1.0.0",
102
+ "json5": "^2.2.3",
103
+ "safe-regex": "^2.1.1"
59
104
  },
60
105
  "devDependencies": {
61
- "benchmark": "^2.1.4",
62
- "chai": "^4.5.0",
63
- "eslint": "^8.57.1",
64
- "express": "^5.2.1",
65
- "joi": "^18.0.2",
66
- "mocha": "^10.8.2",
67
- "monsqlize": "^1.0.1",
68
- "nyc": "^15.1.0",
69
- "sinon": "^17.0.1",
106
+ "@types/node": "^25.6.0",
107
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
108
+ "@typescript-eslint/parser": "^8.0.0",
109
+ "@vitest/coverage-v8": "^3.0.0",
110
+ "eslint": "^9.0.0",
111
+ "fastest-validator": "^1.19.1",
112
+ "joi": "^18.1.2",
113
+ "tsup": "^8.0.0",
114
+ "typescript": "^5.5.0",
115
+ "vitest": "^3.0.0",
70
116
  "yup": "^1.7.1",
71
- "zod": "^4.2.1"
72
- }
117
+ "zod": "^4.3.6"
118
+ },
119
+ "type": "module",
120
+ "files": [
121
+ "dist/**/*.js",
122
+ "dist/**/*.cjs",
123
+ "dist/**/*.d.ts",
124
+ "dist/**/*.d.cts",
125
+ "plugins",
126
+ "src",
127
+ "docs",
128
+ "README.md",
129
+ "CHANGELOG.md"
130
+ ]
73
131
  }
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const mod = require('../dist/plugins/custom-format.cjs');
4
+ const plugin = mod.default ?? mod.customFormatPlugin ?? mod;
5
+
6
+ module.exports = plugin;
7
+ module.exports.default = plugin;
8
+
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const mod = require('../dist/plugins/custom-type-example.cjs');
4
+ const plugin = mod.default ?? mod.customTypeExamplePlugin ?? mod;
5
+
6
+ module.exports = plugin;
7
+ module.exports.default = plugin;
8
+
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const mod = require('../dist/plugins/custom-validator.cjs');
4
+ const plugin = mod.default ?? mod.customValidatorPlugin ?? mod;
5
+
6
+ module.exports = plugin;
7
+ module.exports.default = plugin;
8
+
@@ -0,0 +1,111 @@
1
+
2
+ /**
3
+ * DslAdapter — DSL parsing adapter (thin wrapper layer).
4
+ *
5
+ * v2 changes:
6
+ * - All parsing logic delegated to DslParser (replaces v1 DslAdapter._parseType duplication)
7
+ * - Fixes DA-01/DA-02/DA-03 (handled uniformly by the DslParser pipeline)
8
+ * - parseObject delegates to DslParser.parseObject (replaces JSONSchemaCore)
9
+ * - BC-2 fix: parseObject() returns ObjectDslBuilder (supports chain .strict()/.requireAll())
10
+ * - BC-4 fix: typeMap getter exposes all registered types; registerType() convenience entry point
11
+ */
12
+
13
+ import type { JSONSchema } from '../types/schema.js'
14
+ import type { DslDefinition } from '../types/dsl.js'
15
+ import { DslParser } from '../parser/DslParser.js'
16
+ import { TypeRegistry } from '../parser/TypeRegistry.js'
17
+ import { ObjectDslBuilder } from '../core/ObjectDslBuilder.js'
18
+
19
+ type DslMarker = Record<string, unknown>
20
+
21
+ export const DslAdapter = {
22
+ /**
23
+ * Parse a DSL string into a JSON Schema.
24
+ * Equivalent to v1 DslAdapter.parseString(), but delegates to the unified DslParser.
25
+ */
26
+ parseString(dslString: string): JSONSchema {
27
+ if (!dslString || typeof dslString !== 'string') {
28
+ throw new Error('[schema-dsl] DslAdapter.parseString: DSL must be a string')
29
+ }
30
+ return DslParser.parseString(dslString)
31
+ },
32
+
33
+ /**
34
+ * parse() — alias for parseString() (backwards compat with v1).
35
+ */
36
+ parse(dslString: string): JSONSchema {
37
+ if (!dslString || typeof dslString !== 'string') {
38
+ throw new Error('[schema-dsl] DslAdapter.parse: DSL must be a string')
39
+ }
40
+ const schema = DslParser.parseString(dslString)
41
+ // v1 compat: always set _required (false if not set)
42
+ if ((schema as Record<string, unknown>)['_required'] === undefined) {
43
+ (schema as Record<string, unknown>)['_required'] = false
44
+ }
45
+ return schema
46
+ },
47
+
48
+ /**
49
+ * Parse an object-form DSL definition → ObjectDslBuilder (BC-2 fix).
50
+ * v1: parseObject() returned a chainable builder (.strict()/.requireAll()).
51
+ * v2 fix: returns ObjectDslBuilder wrapping the compiled JSONSchema.
52
+ */
53
+ parseObject(dslObj: DslDefinition): ObjectDslBuilder {
54
+ const schema = DslParser.parseObject(dslObj)
55
+ return new ObjectDslBuilder(schema)
56
+ },
57
+
58
+ /**
59
+ * Create a Match marker (v1 compat). The actual JSON Schema is built per target field in parseObject.
60
+ */
61
+ match(field: string, map: Record<string, unknown>): DslMarker {
62
+ return { _isMatch: true, field, map }
63
+ },
64
+
65
+ /**
66
+ * Create an If marker (v1 compat). The actual JSON Schema is built per target field in parseObject.
67
+ */
68
+ if(condition: string, thenSchema: unknown, elseSchema?: unknown): DslMarker {
69
+ return { _isIf: true, condition, then: thenSchema, else: elseSchema }
70
+ },
71
+
72
+ /**
73
+ * toCore() — v1 compat: returns { schema } wrapper
74
+ */
75
+ toCore(dslInput: string | DslDefinition): { schema: JSONSchema } {
76
+ let schema: JSONSchema
77
+ if (typeof dslInput === 'string') {
78
+ schema = this.parse(dslInput)
79
+ } else {
80
+ schema = DslParser.parseObject(dslInput)
81
+ }
82
+ return { schema }
83
+ },
84
+
85
+ /**
86
+ * typeMap getter — v1 compat: DslAdapter.typeMap exposes all registered types (BC-4 fix).
87
+ * Assigning typeMap[name] = schema is equivalent to calling TypeRegistry.register(name, schema).
88
+ */
89
+ get typeMap(): Record<string, JSONSchema> {
90
+ const map: Record<string, JSONSchema> = {}
91
+ for (const [name, def] of TypeRegistry.entries()) {
92
+ map[name] = def.baseSchema as JSONSchema
93
+ }
94
+ return new Proxy(map, {
95
+ set(_target: Record<string, JSONSchema>, key: string, value: JSONSchema) {
96
+ TypeRegistry.register(key, { baseSchema: value })
97
+ return true
98
+ },
99
+ })
100
+ },
101
+
102
+ /**
103
+ * registerType() — v1 compat: register a custom type into TypeRegistry (BC-4 fix).
104
+ */
105
+ registerType(name: string, schema: JSONSchema): void {
106
+ TypeRegistry.register(name, { baseSchema: schema })
107
+ },
108
+ }
109
+
110
+ export type DslAdapterType = typeof DslAdapter
111
+
@@ -0,0 +1 @@
1
+ export * from './DslAdapter.js'
@@ -0,0 +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
@@ -0,0 +1,2 @@
1
+ export * from './constants.js'
2
+ export * from './patterns.js'
@@ -0,0 +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 }
@@ -0,0 +1,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 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
+ }