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.
- package/CHANGELOG.md +87 -210
- package/README.md +391 -2249
- package/dist/DslBuilder-DQDN0ZxZ.d.cts +341 -0
- package/dist/DslBuilder-DkLaOo9Q.d.ts +341 -0
- package/dist/Validator-C7GsVQOH.d.cts +192 -0
- package/dist/Validator-hFWKGxir.d.ts +192 -0
- package/dist/index.cjs +6594 -0
- package/dist/index.d.cts +1145 -0
- package/dist/index.d.ts +1145 -0
- package/dist/index.js +6528 -0
- package/dist/plugin-CIKtTMtS.d.cts +246 -0
- package/dist/plugin-CIKtTMtS.d.ts +246 -0
- package/dist/plugins/custom-format.cjs +3802 -0
- package/dist/plugins/custom-format.d.cts +12 -0
- package/dist/plugins/custom-format.d.ts +12 -0
- package/dist/plugins/custom-format.js +3772 -0
- package/dist/plugins/custom-type-example.cjs +3795 -0
- package/dist/plugins/custom-type-example.d.cts +8 -0
- package/dist/plugins/custom-type-example.d.ts +8 -0
- package/dist/plugins/custom-type-example.js +3765 -0
- package/dist/plugins/custom-validator.cjs +146 -0
- package/dist/plugins/custom-validator.d.cts +10 -0
- package/dist/plugins/custom-validator.d.ts +10 -0
- package/dist/plugins/custom-validator.js +121 -0
- package/docs/FEATURE-INDEX.md +102 -68
- package/docs/add-custom-locale.md +48 -35
- package/docs/add-keyword.md +24 -0
- package/docs/api-reference.md +396 -154
- package/docs/api.md +13 -0
- package/docs/best-practices-project-structure.md +19 -10
- package/docs/best-practices.md +93 -53
- package/docs/cache-manager.md +23 -15
- package/docs/compile.md +45 -0
- package/docs/conditional-api.md +40 -11
- package/docs/custom-extensions-guide.md +80 -152
- package/docs/design-philosophy.md +76 -71
- package/docs/doc-index.md +324 -0
- package/docs/dsl-syntax.md +69 -19
- package/docs/dynamic-locale.md +24 -14
- package/docs/enum.md +12 -5
- package/docs/error-handling.md +53 -44
- package/docs/export-guide.md +47 -8
- package/docs/export-limitations.md +27 -11
- package/docs/faq.md +86 -67
- package/docs/frontend-i18n-guide.md +26 -12
- package/docs/i18n-user-guide.md +60 -47
- package/docs/i18n.md +51 -32
- package/docs/index.md +48 -0
- package/docs/json-schema-basics.md +40 -0
- package/docs/label-vs-description.md +12 -3
- package/docs/markdown-exporter.md +15 -6
- package/docs/mongodb-exporter.md +11 -4
- package/docs/multi-language.md +26 -0
- package/docs/multi-type-support.md +26 -33
- package/docs/mysql-exporter.md +9 -2
- package/docs/number-operators.md +12 -5
- package/docs/optional-marker-guide.md +28 -23
- package/docs/performance-guide.md +49 -0
- package/docs/plugin-system.md +205 -366
- package/docs/plugin-type-registration.md +34 -0
- package/docs/postgresql-exporter.md +9 -2
- package/docs/public/favicon.svg +5 -0
- package/docs/quick-start.md +37 -363
- package/docs/runtime-locale-support.md +20 -9
- package/docs/schema-helper.md +10 -5
- package/docs/schema-utils-advanced-issues.md +23 -0
- package/docs/schema-utils-best-practices.md +20 -0
- package/docs/schema-utils-chaining.md +7 -0
- package/docs/schema-utils.md +76 -42
- package/docs/security-checklist.md +20 -0
- package/docs/string-extensions.md +17 -9
- package/docs/troubleshooting.md +36 -21
- package/docs/type-converter.md +41 -50
- package/docs/type-reference.md +38 -15
- package/docs/typescript-guide.md +53 -42
- package/docs/union-type-guide.md +11 -1
- package/docs/union-types.md +10 -3
- package/docs/validate-async.md +36 -25
- package/docs/validate-batch.md +49 -0
- package/docs/validate-dsl-object-support.md +33 -28
- package/docs/validate.md +36 -16
- package/docs/validation-guide.md +25 -7
- package/docs/validator.md +39 -0
- package/package.json +85 -27
- package/plugins/custom-format.cjs +8 -0
- package/plugins/custom-type-example.cjs +8 -0
- package/plugins/custom-validator.cjs +8 -0
- package/src/adapters/DslAdapter.ts +111 -0
- package/src/adapters/index.ts +1 -0
- package/src/config/constants.ts +83 -0
- package/src/config/index.ts +2 -0
- package/src/config/patterns.ts +77 -0
- package/src/core/CacheManager.ts +159 -0
- package/src/core/ConditionalBuilder.ts +382 -0
- package/src/core/ConditionalRuntime.ts +28 -0
- package/src/core/ConditionalValidator.ts +255 -0
- package/src/core/DslBuilder.ts +677 -0
- package/src/core/ErrorCodes.ts +38 -0
- package/src/core/ErrorFormatter.ts +271 -0
- package/src/core/JSONSchemaCore.ts +65 -0
- package/src/core/Locale.ts +187 -0
- package/src/core/MessageTemplate.ts +42 -0
- package/src/core/ObjectDslBuilder.ts +64 -0
- package/src/core/PluginManager.ts +326 -0
- package/src/core/StringExtensions.ts +140 -0
- package/src/core/TemplateEngine.ts +44 -0
- package/src/core/Validator.ts +448 -0
- package/src/errors/I18nError.ts +159 -0
- package/src/errors/ValidationError.ts +105 -0
- package/src/exporters/BaseExporter.ts +60 -0
- package/src/exporters/MarkdownExporter.ts +305 -0
- package/src/exporters/MongoDBExporter.ts +126 -0
- package/src/exporters/MySQLExporter.ts +155 -0
- package/src/exporters/PostgreSQLExporter.ts +222 -0
- package/src/exporters/index.ts +18 -0
- package/src/index.ts +633 -0
- package/{lib/locales/en-US.js → src/locales/en-US.ts} +21 -37
- package/{lib/locales/es-ES.js → src/locales/es-ES.ts} +63 -16
- package/{lib/locales/fr-FR.js → src/locales/fr-FR.ts} +74 -27
- package/src/locales/index.ts +103 -0
- package/{lib/locales/ja-JP.js → src/locales/ja-JP.ts} +59 -17
- package/src/locales/types.ts +156 -0
- package/{lib/locales/zh-CN.js → src/locales/zh-CN.ts} +21 -38
- package/src/parser/ConstraintParser.ts +101 -0
- package/src/parser/DslParser.ts +470 -0
- package/src/parser/SchemaCompiler.ts +66 -0
- package/src/parser/TypeRegistry.ts +250 -0
- package/src/parser/index.ts +6 -0
- package/src/plugins/custom-format.ts +126 -0
- package/src/plugins/custom-type-example.ts +108 -0
- package/src/plugins/custom-validator.ts +140 -0
- package/src/types/conditional.ts +28 -0
- package/src/types/config.ts +59 -0
- package/src/types/dsl.ts +131 -0
- package/src/types/error.ts +60 -0
- package/src/types/index.ts +17 -0
- package/src/types/infer.ts +128 -0
- package/src/types/plugin.ts +58 -0
- package/src/types/safe-regex.d.ts +9 -0
- package/src/types/schema.ts +66 -0
- package/src/types/validate.ts +71 -0
- package/src/utils/SchemaHelper.ts +196 -0
- package/src/utils/SchemaUtils.ts +346 -0
- package/src/utils/TypeConverter.ts +215 -0
- package/src/utils/index.ts +10 -0
- package/src/validators/CustomKeywords.ts +477 -0
- package/.eslintignore +0 -11
- package/.eslintrc.json +0 -27
- package/CONTRIBUTING.md +0 -368
- package/STATUS.md +0 -491
- package/changelogs/v1.0.0.md +0 -328
- package/changelogs/v1.0.9.md +0 -367
- package/changelogs/v1.1.0.md +0 -389
- package/changelogs/v1.1.1.md +0 -308
- package/changelogs/v1.1.2.md +0 -183
- package/changelogs/v1.1.3.md +0 -161
- package/changelogs/v1.1.4.md +0 -432
- package/changelogs/v1.1.5.md +0 -493
- package/changelogs/v1.1.6.md +0 -211
- package/changelogs/v1.1.8.md +0 -376
- package/changelogs/v1.2.3.md +0 -124
- package/docs/INDEX.md +0 -252
- package/docs/issues-resolved-summary.md +0 -196
- package/docs/performance-benchmark-report.md +0 -179
- package/docs/performance-quick-reference.md +0 -123
- package/docs/user-questions-answered.md +0 -353
- package/docs/validation-rules-v1.0.2.md +0 -1608
- package/examples/README.md +0 -81
- package/examples/array-dsl-example.js +0 -227
- package/examples/conditional-example.js +0 -288
- package/examples/conditional-non-object.js +0 -129
- package/examples/conditional-validate-example.js +0 -321
- package/examples/custom-extension.js +0 -85
- package/examples/dsl-match-example.js +0 -74
- package/examples/dsl-style.js +0 -118
- package/examples/dynamic-locale-configuration.js +0 -348
- package/examples/dynamic-locale-example.js +0 -287
- package/examples/enum.examples.js +0 -324
- package/examples/export-demo.js +0 -130
- package/examples/express-integration.js +0 -376
- package/examples/i18n-error-handling-complete.js +0 -381
- package/examples/i18n-error-handling-quickstart.md +0 -0
- package/examples/i18n-error.examples.js +0 -181
- package/examples/i18n-full-demo.js +0 -301
- package/examples/i18n-memory-safety.examples.js +0 -268
- package/examples/markdown-export.js +0 -71
- package/examples/middleware-usage.js +0 -93
- package/examples/new-features-comparison.js +0 -315
- package/examples/password-reset/README.md +0 -153
- package/examples/password-reset/schema.js +0 -26
- package/examples/password-reset/test.js +0 -101
- package/examples/plugin-system.examples.js +0 -205
- package/examples/schema-utils-chaining.examples.js +0 -250
- package/examples/simple-example.js +0 -122
- package/examples/slug.examples.js +0 -179
- package/examples/string-extensions.js +0 -297
- package/examples/union-type-example.js +0 -127
- package/examples/union-types-example.js +0 -77
- package/examples/user-registration/README.md +0 -156
- package/examples/user-registration/routes.js +0 -92
- package/examples/user-registration/schema.js +0 -150
- package/examples/user-registration/server.js +0 -74
- package/index.d.ts +0 -3540
- package/index.js +0 -457
- package/index.mjs +0 -60
- package/lib/adapters/DslAdapter.js +0 -871
- package/lib/adapters/index.js +0 -20
- package/lib/config/constants.js +0 -286
- package/lib/config/patterns/common.js +0 -47
- package/lib/config/patterns/creditCard.js +0 -9
- package/lib/config/patterns/idCard.js +0 -9
- package/lib/config/patterns/index.js +0 -9
- package/lib/config/patterns/licensePlate.js +0 -4
- package/lib/config/patterns/passport.js +0 -4
- package/lib/config/patterns/phone.js +0 -9
- package/lib/config/patterns/postalCode.js +0 -5
- package/lib/core/CacheManager.js +0 -376
- package/lib/core/ConditionalBuilder.js +0 -503
- package/lib/core/DslBuilder.js +0 -1400
- package/lib/core/ErrorCodes.js +0 -233
- package/lib/core/ErrorFormatter.js +0 -445
- package/lib/core/JSONSchemaCore.js +0 -347
- package/lib/core/Locale.js +0 -130
- package/lib/core/MessageTemplate.js +0 -98
- package/lib/core/PluginManager.js +0 -448
- package/lib/core/StringExtensions.js +0 -240
- package/lib/core/Validator.js +0 -654
- package/lib/errors/I18nError.js +0 -328
- package/lib/errors/ValidationError.js +0 -191
- package/lib/exporters/MarkdownExporter.js +0 -420
- package/lib/exporters/MongoDBExporter.js +0 -162
- package/lib/exporters/MySQLExporter.js +0 -212
- package/lib/exporters/PostgreSQLExporter.js +0 -289
- package/lib/exporters/index.js +0 -24
- package/lib/locales/index.js +0 -8
- package/lib/utils/LRUCache.js +0 -174
- package/lib/utils/SchemaHelper.js +0 -240
- package/lib/utils/SchemaUtils.js +0 -445
- package/lib/utils/TypeConverter.js +0 -245
- package/lib/utils/index.js +0 -13
- package/lib/validators/CustomKeywords.js +0 -616
- package/lib/validators/index.js +0 -11
package/package.json
CHANGED
|
@@ -1,23 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "schema-dsl",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "index.
|
|
6
|
-
"
|
|
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
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"test
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
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": ">=
|
|
96
|
+
"node": ">=18.0.0"
|
|
55
97
|
},
|
|
56
98
|
"dependencies": {
|
|
57
|
-
"ajv": "^8.
|
|
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
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"eslint": "^8.
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
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.
|
|
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,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,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
|
+
}
|