schema-dsl 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +130 -113
- package/LICENSE +21 -21
- package/README.md +628 -628
- package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
- package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
- package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
- package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
- package/dist/index.cjs +75 -29
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +75 -29
- package/dist/plugins/custom-format.cjs +33 -17
- package/dist/plugins/custom-format.d.cts +1 -1
- package/dist/plugins/custom-format.d.ts +1 -1
- package/dist/plugins/custom-format.js +33 -17
- package/dist/plugins/custom-type-example.cjs +33 -17
- package/dist/plugins/custom-type-example.d.cts +1 -1
- package/dist/plugins/custom-type-example.d.ts +1 -1
- package/dist/plugins/custom-type-example.js +33 -17
- package/dist/plugins/custom-validator.cjs +0 -2
- package/dist/plugins/custom-validator.d.cts +1 -1
- package/dist/plugins/custom-validator.d.ts +1 -1
- package/dist/plugins/custom-validator.js +0 -2
- package/docs/FEATURE-INDEX.md +553 -553
- package/docs/add-custom-locale.md +496 -496
- package/docs/add-keyword.md +24 -24
- package/docs/api-reference.md +1047 -1047
- package/docs/api.md +13 -13
- package/docs/best-practices-project-structure.md +417 -417
- package/docs/best-practices.md +712 -712
- package/docs/cache-manager.md +344 -344
- package/docs/compile.md +45 -45
- package/docs/conditional-api.md +1307 -1307
- package/docs/custom-extensions-guide.md +339 -339
- package/docs/design-philosophy.md +606 -606
- package/docs/doc-index.md +324 -324
- package/docs/dsl-syntax.md +714 -714
- package/docs/dynamic-locale.md +608 -608
- package/docs/enum.md +482 -482
- package/docs/error-handling.md +1975 -1975
- package/docs/export-guide.md +501 -501
- package/docs/export-limitations.md +567 -567
- package/docs/faq.md +596 -596
- package/docs/frontend-i18n-guide.md +307 -307
- package/docs/i18n-user-guide.md +487 -487
- package/docs/i18n.md +476 -476
- package/docs/index.md +48 -48
- package/docs/json-schema-basics.md +40 -40
- package/docs/label-vs-description.md +271 -271
- package/docs/markdown-exporter.md +406 -406
- package/docs/mongodb-exporter.md +302 -302
- package/docs/multi-language.md +26 -26
- package/docs/multi-type-support.md +322 -322
- package/docs/mysql-exporter.md +280 -280
- package/docs/number-operators.md +449 -449
- package/docs/optional-marker-guide.md +326 -326
- package/docs/performance-guide.md +49 -49
- package/docs/plugin-system.md +381 -381
- package/docs/plugin-type-registration.md +34 -34
- package/docs/postgresql-exporter.md +311 -311
- package/docs/public/favicon.svg +4 -4
- package/docs/quick-start.md +435 -435
- package/docs/runtime-locale-support.md +532 -532
- package/docs/schema-helper.md +345 -345
- package/docs/schema-utils-advanced-issues.md +23 -23
- package/docs/schema-utils-best-practices.md +20 -20
- package/docs/schema-utils-chaining.md +150 -150
- package/docs/schema-utils.md +524 -524
- package/docs/security-checklist.md +20 -20
- package/docs/string-extensions.md +488 -488
- package/docs/troubleshooting.md +486 -486
- package/docs/type-converter.md +310 -310
- package/docs/type-reference.md +242 -242
- package/docs/typescript-guide.md +584 -584
- package/docs/union-type-guide.md +157 -157
- package/docs/union-types.md +284 -284
- package/docs/validate-async.md +491 -491
- package/docs/validate-batch.md +49 -49
- package/docs/validate-dsl-object-support.md +578 -578
- package/docs/validate.md +506 -506
- package/docs/validation-guide.md +502 -502
- package/docs/validator.md +39 -39
- package/package.json +131 -131
- package/plugins/custom-format.cjs +8 -8
- package/plugins/custom-type-example.cjs +8 -8
- package/plugins/custom-validator.cjs +8 -8
- package/src/adapters/DslAdapter.ts +111 -111
- package/src/adapters/index.ts +1 -1
- package/src/config/constants.ts +83 -83
- package/src/config/index.ts +2 -2
- package/src/config/patterns.ts +77 -77
- package/src/core/CacheManager.ts +169 -159
- package/src/core/ConditionalBuilder.ts +382 -382
- package/src/core/ConditionalRuntime.ts +27 -27
- package/src/core/ConditionalValidator.ts +254 -254
- package/src/core/DslBuilder.ts +687 -677
- package/src/core/ErrorCodes.ts +38 -38
- package/src/core/ErrorFormatter.ts +271 -271
- package/src/core/JSONSchemaCore.ts +65 -65
- package/src/core/Locale.ts +187 -187
- package/src/core/MessageTemplate.ts +42 -42
- package/src/core/ObjectDslBuilder.ts +64 -64
- package/src/core/PluginManager.ts +326 -326
- package/src/core/StringExtensions.ts +140 -140
- package/src/core/TemplateEngine.ts +44 -44
- package/src/core/Validator.ts +448 -448
- package/src/errors/I18nError.ts +159 -159
- package/src/errors/ValidationError.ts +105 -105
- package/src/exporters/BaseExporter.ts +60 -60
- package/src/exporters/MarkdownExporter.ts +305 -305
- package/src/exporters/MongoDBExporter.ts +126 -126
- package/src/exporters/MySQLExporter.ts +156 -155
- package/src/exporters/PostgreSQLExporter.ts +222 -222
- package/src/exporters/index.ts +18 -18
- package/src/index.ts +651 -633
- package/src/locales/en-US.ts +160 -160
- package/src/locales/es-ES.ts +160 -160
- package/src/locales/fr-FR.ts +160 -160
- package/src/locales/index.ts +103 -103
- package/src/locales/ja-JP.ts +160 -160
- package/src/locales/types.ts +156 -156
- package/src/locales/zh-CN.ts +160 -160
- package/src/parser/ConstraintParser.ts +101 -101
- package/src/parser/DslParser.ts +470 -470
- package/src/parser/SchemaCompiler.ts +66 -66
- package/src/parser/TypeRegistry.ts +250 -250
- package/src/parser/index.ts +6 -6
- package/src/plugins/custom-format.ts +124 -126
- package/src/plugins/custom-type-example.ts +106 -108
- package/src/plugins/custom-validator.ts +138 -140
- package/src/types/conditional.ts +28 -28
- package/src/types/config.ts +59 -59
- package/src/types/dsl.ts +131 -131
- package/src/types/error.ts +60 -60
- package/src/types/index.ts +17 -17
- package/src/types/infer.ts +127 -127
- package/src/types/plugin.ts +58 -58
- package/src/types/safe-regex.d.ts +9 -9
- package/src/types/schema.ts +66 -66
- package/src/types/validate.ts +71 -71
- package/src/utils/SchemaHelper.ts +196 -196
- package/src/utils/SchemaUtils.ts +365 -346
- package/src/utils/TypeConverter.ts +215 -215
- package/src/utils/index.ts +10 -10
- package/src/validators/CustomKeywords.ts +477 -477
package/src/errors/I18nError.ts
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
import { renderTemplate } from '../core/TemplateEngine.js'
|
|
2
|
-
import { Locale } from '../core/Locale.js'
|
|
3
|
-
|
|
4
|
-
// V8/Node.js extension
|
|
5
|
-
type ErrorWithCaptureStackTrace = typeof Error & {
|
|
6
|
-
captureStackTrace?: (target: object, ctor: unknown) => void
|
|
7
|
-
}
|
|
8
|
-
const ErrorCtor = Error as ErrorWithCaptureStackTrace
|
|
9
|
-
|
|
10
|
-
type ParamsOrLocale = Record<string, unknown> | string | null | undefined
|
|
11
|
-
|
|
12
|
-
function normalizeParams(
|
|
13
|
-
paramsOrLocale: ParamsOrLocale,
|
|
14
|
-
statusCode?: unknown,
|
|
15
|
-
locale?: unknown
|
|
16
|
-
): { params: Record<string, unknown>; statusCode: number; locale: string | null } {
|
|
17
|
-
let params: Record<string, unknown> = {}
|
|
18
|
-
let actualStatusCode = 400
|
|
19
|
-
let actualLocale: string | null = null
|
|
20
|
-
|
|
21
|
-
if (typeof paramsOrLocale === 'string') {
|
|
22
|
-
actualLocale = paramsOrLocale
|
|
23
|
-
actualStatusCode = typeof statusCode === 'number' ? statusCode : 400
|
|
24
|
-
} else if (paramsOrLocale && typeof paramsOrLocale === 'object' && !Array.isArray(paramsOrLocale)) {
|
|
25
|
-
params = paramsOrLocale
|
|
26
|
-
actualStatusCode = typeof statusCode === 'number' ? statusCode : 400
|
|
27
|
-
actualLocale = typeof locale === 'string' ? locale : null
|
|
28
|
-
} else {
|
|
29
|
-
actualStatusCode = typeof statusCode === 'number' ? statusCode : 400
|
|
30
|
-
actualLocale = typeof locale === 'string' ? locale : null
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return { params, statusCode: actualStatusCode, locale: actualLocale }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Internationalised error class.
|
|
38
|
-
* Maintains full v1 API compatibility: create / throw / assert / is / toJSON / toString
|
|
39
|
-
*/
|
|
40
|
-
export class I18nError extends Error {
|
|
41
|
-
readonly name = 'I18nError' as const
|
|
42
|
-
readonly originalKey: string
|
|
43
|
-
readonly code: string | number
|
|
44
|
-
readonly params: Record<string, unknown>
|
|
45
|
-
readonly statusCode: number
|
|
46
|
-
readonly locale: string | null
|
|
47
|
-
|
|
48
|
-
constructor(
|
|
49
|
-
key: string,
|
|
50
|
-
params: Record<string, unknown> = {},
|
|
51
|
-
statusCode = 400,
|
|
52
|
-
locale: string | null = null,
|
|
53
|
-
/** Internal: pre-resolved message template, bypasses Locale lookup (used to decouple init ordering). */
|
|
54
|
-
_resolvedTemplate?: string,
|
|
55
|
-
_resolvedCode?: string | number
|
|
56
|
-
) {
|
|
57
|
-
const targetLocale = locale ?? Locale.getLocale()
|
|
58
|
-
const normalizedParams: Record<string, unknown> = (params !== null && params !== undefined) ? params : {}
|
|
59
|
-
|
|
60
|
-
// Look up locale message config if not pre-resolved
|
|
61
|
-
let template: string
|
|
62
|
-
let resolvedCode: string | number
|
|
63
|
-
if (_resolvedTemplate !== undefined) {
|
|
64
|
-
template = _resolvedTemplate
|
|
65
|
-
resolvedCode = _resolvedCode ?? key
|
|
66
|
-
} else {
|
|
67
|
-
const msgConfig = Locale.getMessageConfig(key, {}, targetLocale)
|
|
68
|
-
if (typeof msgConfig === 'object' && msgConfig !== null && 'message' in msgConfig) {
|
|
69
|
-
template = (msgConfig as { message: string }).message
|
|
70
|
-
resolvedCode = (msgConfig as { code?: string | number }).code ?? key
|
|
71
|
-
} else if (typeof msgConfig === 'string') {
|
|
72
|
-
template = msgConfig
|
|
73
|
-
resolvedCode = key
|
|
74
|
-
} else {
|
|
75
|
-
template = key
|
|
76
|
-
resolvedCode = key
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const message = renderTemplate(template, normalizedParams)
|
|
81
|
-
|
|
82
|
-
super(message)
|
|
83
|
-
|
|
84
|
-
this.originalKey = key
|
|
85
|
-
this.code = resolvedCode
|
|
86
|
-
this.params = normalizedParams
|
|
87
|
-
this.statusCode = statusCode
|
|
88
|
-
this.locale = targetLocale
|
|
89
|
-
|
|
90
|
-
if (ErrorCtor.captureStackTrace) {
|
|
91
|
-
ErrorCtor.captureStackTrace(this, I18nError)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** Factory method — create an error instance. */
|
|
96
|
-
static create(
|
|
97
|
-
code: string,
|
|
98
|
-
paramsOrLocale?: ParamsOrLocale,
|
|
99
|
-
statusCode?: number,
|
|
100
|
-
locale?: string
|
|
101
|
-
): I18nError {
|
|
102
|
-
const normalized = normalizeParams(paramsOrLocale, statusCode, locale)
|
|
103
|
-
return new I18nError(code, normalized.params, normalized.statusCode, normalized.locale)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Factory method — create and throw an error. */
|
|
107
|
-
static throw(
|
|
108
|
-
code: string,
|
|
109
|
-
paramsOrLocale?: ParamsOrLocale,
|
|
110
|
-
statusCode?: number,
|
|
111
|
-
locale?: string
|
|
112
|
-
): never {
|
|
113
|
-
const normalized = normalizeParams(paramsOrLocale, statusCode, locale)
|
|
114
|
-
throw new I18nError(code, normalized.params, normalized.statusCode, normalized.locale)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** Assert — throw when condition is falsy. */
|
|
118
|
-
static assert(
|
|
119
|
-
condition: unknown,
|
|
120
|
-
code: string,
|
|
121
|
-
paramsOrLocale?: ParamsOrLocale,
|
|
122
|
-
statusCode?: number,
|
|
123
|
-
locale?: string
|
|
124
|
-
): asserts condition {
|
|
125
|
-
if (!condition) {
|
|
126
|
-
const normalized = normalizeParams(paramsOrLocale, statusCode, locale)
|
|
127
|
-
throw new I18nError(code, normalized.params, normalized.statusCode, normalized.locale)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** Check whether the error matches the given code or original key. */
|
|
132
|
-
is(codeOrKey: string | number): boolean {
|
|
133
|
-
return this.code === codeOrKey || this.originalKey === codeOrKey
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
toJSON(): {
|
|
137
|
-
error: string
|
|
138
|
-
originalKey: string
|
|
139
|
-
code: string | number
|
|
140
|
-
message: string
|
|
141
|
-
params: Record<string, unknown>
|
|
142
|
-
statusCode: number
|
|
143
|
-
locale: string | null
|
|
144
|
-
} {
|
|
145
|
-
return {
|
|
146
|
-
error: this.name,
|
|
147
|
-
originalKey: this.originalKey,
|
|
148
|
-
code: this.code,
|
|
149
|
-
message: this.message,
|
|
150
|
-
params: this.params,
|
|
151
|
-
statusCode: this.statusCode,
|
|
152
|
-
locale: this.locale,
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
toString(): string {
|
|
157
|
-
return `${this.name} [${this.code}]: ${this.message}`
|
|
158
|
-
}
|
|
159
|
-
}
|
|
1
|
+
import { renderTemplate } from '../core/TemplateEngine.js'
|
|
2
|
+
import { Locale } from '../core/Locale.js'
|
|
3
|
+
|
|
4
|
+
// V8/Node.js extension
|
|
5
|
+
type ErrorWithCaptureStackTrace = typeof Error & {
|
|
6
|
+
captureStackTrace?: (target: object, ctor: unknown) => void
|
|
7
|
+
}
|
|
8
|
+
const ErrorCtor = Error as ErrorWithCaptureStackTrace
|
|
9
|
+
|
|
10
|
+
type ParamsOrLocale = Record<string, unknown> | string | null | undefined
|
|
11
|
+
|
|
12
|
+
function normalizeParams(
|
|
13
|
+
paramsOrLocale: ParamsOrLocale,
|
|
14
|
+
statusCode?: unknown,
|
|
15
|
+
locale?: unknown
|
|
16
|
+
): { params: Record<string, unknown>; statusCode: number; locale: string | null } {
|
|
17
|
+
let params: Record<string, unknown> = {}
|
|
18
|
+
let actualStatusCode = 400
|
|
19
|
+
let actualLocale: string | null = null
|
|
20
|
+
|
|
21
|
+
if (typeof paramsOrLocale === 'string') {
|
|
22
|
+
actualLocale = paramsOrLocale
|
|
23
|
+
actualStatusCode = typeof statusCode === 'number' ? statusCode : 400
|
|
24
|
+
} else if (paramsOrLocale && typeof paramsOrLocale === 'object' && !Array.isArray(paramsOrLocale)) {
|
|
25
|
+
params = paramsOrLocale
|
|
26
|
+
actualStatusCode = typeof statusCode === 'number' ? statusCode : 400
|
|
27
|
+
actualLocale = typeof locale === 'string' ? locale : null
|
|
28
|
+
} else {
|
|
29
|
+
actualStatusCode = typeof statusCode === 'number' ? statusCode : 400
|
|
30
|
+
actualLocale = typeof locale === 'string' ? locale : null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { params, statusCode: actualStatusCode, locale: actualLocale }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Internationalised error class.
|
|
38
|
+
* Maintains full v1 API compatibility: create / throw / assert / is / toJSON / toString
|
|
39
|
+
*/
|
|
40
|
+
export class I18nError extends Error {
|
|
41
|
+
readonly name = 'I18nError' as const
|
|
42
|
+
readonly originalKey: string
|
|
43
|
+
readonly code: string | number
|
|
44
|
+
readonly params: Record<string, unknown>
|
|
45
|
+
readonly statusCode: number
|
|
46
|
+
readonly locale: string | null
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
key: string,
|
|
50
|
+
params: Record<string, unknown> = {},
|
|
51
|
+
statusCode = 400,
|
|
52
|
+
locale: string | null = null,
|
|
53
|
+
/** Internal: pre-resolved message template, bypasses Locale lookup (used to decouple init ordering). */
|
|
54
|
+
_resolvedTemplate?: string,
|
|
55
|
+
_resolvedCode?: string | number
|
|
56
|
+
) {
|
|
57
|
+
const targetLocale = locale ?? Locale.getLocale()
|
|
58
|
+
const normalizedParams: Record<string, unknown> = (params !== null && params !== undefined) ? params : {}
|
|
59
|
+
|
|
60
|
+
// Look up locale message config if not pre-resolved
|
|
61
|
+
let template: string
|
|
62
|
+
let resolvedCode: string | number
|
|
63
|
+
if (_resolvedTemplate !== undefined) {
|
|
64
|
+
template = _resolvedTemplate
|
|
65
|
+
resolvedCode = _resolvedCode ?? key
|
|
66
|
+
} else {
|
|
67
|
+
const msgConfig = Locale.getMessageConfig(key, {}, targetLocale)
|
|
68
|
+
if (typeof msgConfig === 'object' && msgConfig !== null && 'message' in msgConfig) {
|
|
69
|
+
template = (msgConfig as { message: string }).message
|
|
70
|
+
resolvedCode = (msgConfig as { code?: string | number }).code ?? key
|
|
71
|
+
} else if (typeof msgConfig === 'string') {
|
|
72
|
+
template = msgConfig
|
|
73
|
+
resolvedCode = key
|
|
74
|
+
} else {
|
|
75
|
+
template = key
|
|
76
|
+
resolvedCode = key
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const message = renderTemplate(template, normalizedParams)
|
|
81
|
+
|
|
82
|
+
super(message)
|
|
83
|
+
|
|
84
|
+
this.originalKey = key
|
|
85
|
+
this.code = resolvedCode
|
|
86
|
+
this.params = normalizedParams
|
|
87
|
+
this.statusCode = statusCode
|
|
88
|
+
this.locale = targetLocale
|
|
89
|
+
|
|
90
|
+
if (ErrorCtor.captureStackTrace) {
|
|
91
|
+
ErrorCtor.captureStackTrace(this, I18nError)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Factory method — create an error instance. */
|
|
96
|
+
static create(
|
|
97
|
+
code: string,
|
|
98
|
+
paramsOrLocale?: ParamsOrLocale,
|
|
99
|
+
statusCode?: number,
|
|
100
|
+
locale?: string
|
|
101
|
+
): I18nError {
|
|
102
|
+
const normalized = normalizeParams(paramsOrLocale, statusCode, locale)
|
|
103
|
+
return new I18nError(code, normalized.params, normalized.statusCode, normalized.locale)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Factory method — create and throw an error. */
|
|
107
|
+
static throw(
|
|
108
|
+
code: string,
|
|
109
|
+
paramsOrLocale?: ParamsOrLocale,
|
|
110
|
+
statusCode?: number,
|
|
111
|
+
locale?: string
|
|
112
|
+
): never {
|
|
113
|
+
const normalized = normalizeParams(paramsOrLocale, statusCode, locale)
|
|
114
|
+
throw new I18nError(code, normalized.params, normalized.statusCode, normalized.locale)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Assert — throw when condition is falsy. */
|
|
118
|
+
static assert(
|
|
119
|
+
condition: unknown,
|
|
120
|
+
code: string,
|
|
121
|
+
paramsOrLocale?: ParamsOrLocale,
|
|
122
|
+
statusCode?: number,
|
|
123
|
+
locale?: string
|
|
124
|
+
): asserts condition {
|
|
125
|
+
if (!condition) {
|
|
126
|
+
const normalized = normalizeParams(paramsOrLocale, statusCode, locale)
|
|
127
|
+
throw new I18nError(code, normalized.params, normalized.statusCode, normalized.locale)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Check whether the error matches the given code or original key. */
|
|
132
|
+
is(codeOrKey: string | number): boolean {
|
|
133
|
+
return this.code === codeOrKey || this.originalKey === codeOrKey
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
toJSON(): {
|
|
137
|
+
error: string
|
|
138
|
+
originalKey: string
|
|
139
|
+
code: string | number
|
|
140
|
+
message: string
|
|
141
|
+
params: Record<string, unknown>
|
|
142
|
+
statusCode: number
|
|
143
|
+
locale: string | null
|
|
144
|
+
} {
|
|
145
|
+
return {
|
|
146
|
+
error: this.name,
|
|
147
|
+
originalKey: this.originalKey,
|
|
148
|
+
code: this.code,
|
|
149
|
+
message: this.message,
|
|
150
|
+
params: this.params,
|
|
151
|
+
statusCode: this.statusCode,
|
|
152
|
+
locale: this.locale,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
toString(): string {
|
|
157
|
+
return `${this.name} [${this.code}]: ${this.message}`
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
// V8/Node.js extension (not in ES2022 lib; declared explicitly)
|
|
2
|
-
type ErrorWithCaptureStackTrace = typeof Error & {
|
|
3
|
-
captureStackTrace?: (target: object, ctor: unknown) => void
|
|
4
|
-
}
|
|
5
|
-
const ErrorCtor = Error as ErrorWithCaptureStackTrace
|
|
6
|
-
|
|
7
|
-
import type { ValidationErrorItem } from '../types/validate.js'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* ValidationError — error class thrown when validateAsync() fails.
|
|
11
|
-
* Fixes v1 bug: malformed message format when errors array is empty.
|
|
12
|
-
*/
|
|
13
|
-
export class ValidationError extends Error {
|
|
14
|
-
readonly name = 'ValidationError' as const
|
|
15
|
-
readonly errors: ValidationErrorItem[]
|
|
16
|
-
readonly data: unknown
|
|
17
|
-
readonly statusCode: number
|
|
18
|
-
|
|
19
|
-
constructor(errors: ValidationErrorItem[], data?: unknown, statusCode = 400) {
|
|
20
|
-
// Fix: provide friendly message when errors array is empty (v1 bug)
|
|
21
|
-
const messages =
|
|
22
|
-
errors.length === 0
|
|
23
|
-
? 'Validation failed'
|
|
24
|
-
: errors
|
|
25
|
-
.map(e => {
|
|
26
|
-
if (e.path) {
|
|
27
|
-
const field = e.path.replace(/^\//, '')
|
|
28
|
-
return field ? `${field}: ${e.message}` : e.message
|
|
29
|
-
}
|
|
30
|
-
return e.message
|
|
31
|
-
})
|
|
32
|
-
.join('; ')
|
|
33
|
-
|
|
34
|
-
// v1 compat: use " - " separator when no path, ": " otherwise
|
|
35
|
-
// v1 compat: single conditional error uses message string directly (no prefix)
|
|
36
|
-
const hasNoPath = errors.every(e => e.path === undefined || e.path === null || e.path === '')
|
|
37
|
-
const isSingleConditional = errors.length === 1 && errors[0].keyword === 'conditional' && hasNoPath
|
|
38
|
-
if (isSingleConditional) {
|
|
39
|
-
super(messages)
|
|
40
|
-
} else {
|
|
41
|
-
super(hasNoPath ? `Validation failed - ${messages}` : `Validation failed: ${messages}`)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
this.errors = errors
|
|
45
|
-
this.data = data
|
|
46
|
-
this.statusCode = statusCode
|
|
47
|
-
|
|
48
|
-
if (ErrorCtor.captureStackTrace) {
|
|
49
|
-
ErrorCtor.captureStackTrace(this, ValidationError)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
toJSON(): {
|
|
54
|
-
error: string
|
|
55
|
-
message: string
|
|
56
|
-
statusCode: number
|
|
57
|
-
details: Array<{
|
|
58
|
-
field: string | null
|
|
59
|
-
message: string
|
|
60
|
-
keyword: string
|
|
61
|
-
params?: Record<string, unknown>
|
|
62
|
-
}>
|
|
63
|
-
} {
|
|
64
|
-
return {
|
|
65
|
-
error: this.name,
|
|
66
|
-
message: this.message,
|
|
67
|
-
statusCode: this.statusCode,
|
|
68
|
-
details: this.errors.map(e => ({
|
|
69
|
-
field: e.path ? e.path.replace(/^\//, '') : null,
|
|
70
|
-
message: e.message,
|
|
71
|
-
keyword: e.keyword,
|
|
72
|
-
...(e.params !== undefined ? { params: e.params } : {}),
|
|
73
|
-
})),
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
getFieldError(field: string): ValidationErrorItem | null {
|
|
78
|
-
const normalized = field.replace(/^\//, '')
|
|
79
|
-
return (
|
|
80
|
-
this.errors.find(e => {
|
|
81
|
-
if (!e.path) return false
|
|
82
|
-
return e.path.replace(/^\//, '') === normalized
|
|
83
|
-
}) ?? null
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
getFieldErrors(): Record<string, string> {
|
|
88
|
-
const result: Record<string, string> = {}
|
|
89
|
-
for (const e of this.errors) {
|
|
90
|
-
if (e.path) {
|
|
91
|
-
const field = e.path.replace(/^\//, '')
|
|
92
|
-
if (field) result[field] = e.message
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return result
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
hasFieldError(field: string): boolean {
|
|
99
|
-
return this.getFieldError(field) !== null
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
getErrorCount(): number {
|
|
103
|
-
return this.errors.length
|
|
104
|
-
}
|
|
105
|
-
}
|
|
1
|
+
// V8/Node.js extension (not in ES2022 lib; declared explicitly)
|
|
2
|
+
type ErrorWithCaptureStackTrace = typeof Error & {
|
|
3
|
+
captureStackTrace?: (target: object, ctor: unknown) => void
|
|
4
|
+
}
|
|
5
|
+
const ErrorCtor = Error as ErrorWithCaptureStackTrace
|
|
6
|
+
|
|
7
|
+
import type { ValidationErrorItem } from '../types/validate.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ValidationError — error class thrown when validateAsync() fails.
|
|
11
|
+
* Fixes v1 bug: malformed message format when errors array is empty.
|
|
12
|
+
*/
|
|
13
|
+
export class ValidationError extends Error {
|
|
14
|
+
readonly name = 'ValidationError' as const
|
|
15
|
+
readonly errors: ValidationErrorItem[]
|
|
16
|
+
readonly data: unknown
|
|
17
|
+
readonly statusCode: number
|
|
18
|
+
|
|
19
|
+
constructor(errors: ValidationErrorItem[], data?: unknown, statusCode = 400) {
|
|
20
|
+
// Fix: provide friendly message when errors array is empty (v1 bug)
|
|
21
|
+
const messages =
|
|
22
|
+
errors.length === 0
|
|
23
|
+
? 'Validation failed'
|
|
24
|
+
: errors
|
|
25
|
+
.map(e => {
|
|
26
|
+
if (e.path) {
|
|
27
|
+
const field = e.path.replace(/^\//, '')
|
|
28
|
+
return field ? `${field}: ${e.message}` : e.message
|
|
29
|
+
}
|
|
30
|
+
return e.message
|
|
31
|
+
})
|
|
32
|
+
.join('; ')
|
|
33
|
+
|
|
34
|
+
// v1 compat: use " - " separator when no path, ": " otherwise
|
|
35
|
+
// v1 compat: single conditional error uses message string directly (no prefix)
|
|
36
|
+
const hasNoPath = errors.every(e => e.path === undefined || e.path === null || e.path === '')
|
|
37
|
+
const isSingleConditional = errors.length === 1 && errors[0].keyword === 'conditional' && hasNoPath
|
|
38
|
+
if (isSingleConditional) {
|
|
39
|
+
super(messages)
|
|
40
|
+
} else {
|
|
41
|
+
super(hasNoPath ? `Validation failed - ${messages}` : `Validation failed: ${messages}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.errors = errors
|
|
45
|
+
this.data = data
|
|
46
|
+
this.statusCode = statusCode
|
|
47
|
+
|
|
48
|
+
if (ErrorCtor.captureStackTrace) {
|
|
49
|
+
ErrorCtor.captureStackTrace(this, ValidationError)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
toJSON(): {
|
|
54
|
+
error: string
|
|
55
|
+
message: string
|
|
56
|
+
statusCode: number
|
|
57
|
+
details: Array<{
|
|
58
|
+
field: string | null
|
|
59
|
+
message: string
|
|
60
|
+
keyword: string
|
|
61
|
+
params?: Record<string, unknown>
|
|
62
|
+
}>
|
|
63
|
+
} {
|
|
64
|
+
return {
|
|
65
|
+
error: this.name,
|
|
66
|
+
message: this.message,
|
|
67
|
+
statusCode: this.statusCode,
|
|
68
|
+
details: this.errors.map(e => ({
|
|
69
|
+
field: e.path ? e.path.replace(/^\//, '') : null,
|
|
70
|
+
message: e.message,
|
|
71
|
+
keyword: e.keyword,
|
|
72
|
+
...(e.params !== undefined ? { params: e.params } : {}),
|
|
73
|
+
})),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getFieldError(field: string): ValidationErrorItem | null {
|
|
78
|
+
const normalized = field.replace(/^\//, '')
|
|
79
|
+
return (
|
|
80
|
+
this.errors.find(e => {
|
|
81
|
+
if (!e.path) return false
|
|
82
|
+
return e.path.replace(/^\//, '') === normalized
|
|
83
|
+
}) ?? null
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getFieldErrors(): Record<string, string> {
|
|
88
|
+
const result: Record<string, string> = {}
|
|
89
|
+
for (const e of this.errors) {
|
|
90
|
+
if (e.path) {
|
|
91
|
+
const field = e.path.replace(/^\//, '')
|
|
92
|
+
if (field) result[field] = e.message
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return result
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
hasFieldError(field: string): boolean {
|
|
99
|
+
return this.getFieldError(field) !== null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getErrorCount(): number {
|
|
103
|
+
return this.errors.length
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BaseExporter — Base interface and abstract class for all exporters.
|
|
3
|
-
*
|
|
4
|
-
* Provides a unified abstract export() method signature; each exporter subclass implements it.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { JSONSchema } from '../types/schema.js'
|
|
8
|
-
|
|
9
|
-
// ==================== Common options type ====================
|
|
10
|
-
|
|
11
|
-
export interface ExporterOptions {
|
|
12
|
-
[key: string]: unknown
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// ==================== BaseExporter ====================
|
|
16
|
-
|
|
17
|
-
export abstract class BaseExporter<TOptions extends ExporterOptions = ExporterOptions> {
|
|
18
|
-
protected options: TOptions
|
|
19
|
-
|
|
20
|
-
constructor(options: Partial<TOptions> = {}) {
|
|
21
|
-
this.options = options as TOptions
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Export a JSON Schema to the target format.
|
|
26
|
-
* Each subclass must implement this method.
|
|
27
|
-
*/
|
|
28
|
-
abstract export(...args: unknown[]): unknown
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Assert that the input JSON Schema is a valid object-type schema.
|
|
32
|
-
* @throws Error if invalid.
|
|
33
|
-
*/
|
|
34
|
-
protected _assertObjectSchema(jsonSchema: unknown, label = 'JSON Schema'): asserts jsonSchema is JSONSchema & { type: 'object' } {
|
|
35
|
-
if (!jsonSchema || typeof jsonSchema !== 'object') {
|
|
36
|
-
throw new Error(`[schema-dsl] ${label} must be an object`)
|
|
37
|
-
}
|
|
38
|
-
const s = jsonSchema as JSONSchema
|
|
39
|
-
if (s.type !== 'object') {
|
|
40
|
-
throw new Error(`[schema-dsl] ${label} must be an object type (got "${String(s.type)}")`)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Escape SQL single quotes (generic utility).
|
|
46
|
-
*/
|
|
47
|
-
protected _escapeString(str: string): string {
|
|
48
|
-
return str.replace(/'/g, "''")
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Detect the primary key column name in a schema (id / _id preferred).
|
|
53
|
-
*/
|
|
54
|
-
protected _detectPrimaryKey(schema: JSONSchema): string | null {
|
|
55
|
-
if (!schema.properties) return null
|
|
56
|
-
if (schema.properties['id']) return 'id'
|
|
57
|
-
if (schema.properties['_id']) return '_id'
|
|
58
|
-
return null
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* BaseExporter — Base interface and abstract class for all exporters.
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified abstract export() method signature; each exporter subclass implements it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { JSONSchema } from '../types/schema.js'
|
|
8
|
+
|
|
9
|
+
// ==================== Common options type ====================
|
|
10
|
+
|
|
11
|
+
export interface ExporterOptions {
|
|
12
|
+
[key: string]: unknown
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ==================== BaseExporter ====================
|
|
16
|
+
|
|
17
|
+
export abstract class BaseExporter<TOptions extends ExporterOptions = ExporterOptions> {
|
|
18
|
+
protected options: TOptions
|
|
19
|
+
|
|
20
|
+
constructor(options: Partial<TOptions> = {}) {
|
|
21
|
+
this.options = options as TOptions
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Export a JSON Schema to the target format.
|
|
26
|
+
* Each subclass must implement this method.
|
|
27
|
+
*/
|
|
28
|
+
abstract export(...args: unknown[]): unknown
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Assert that the input JSON Schema is a valid object-type schema.
|
|
32
|
+
* @throws Error if invalid.
|
|
33
|
+
*/
|
|
34
|
+
protected _assertObjectSchema(jsonSchema: unknown, label = 'JSON Schema'): asserts jsonSchema is JSONSchema & { type: 'object' } {
|
|
35
|
+
if (!jsonSchema || typeof jsonSchema !== 'object') {
|
|
36
|
+
throw new Error(`[schema-dsl] ${label} must be an object`)
|
|
37
|
+
}
|
|
38
|
+
const s = jsonSchema as JSONSchema
|
|
39
|
+
if (s.type !== 'object') {
|
|
40
|
+
throw new Error(`[schema-dsl] ${label} must be an object type (got "${String(s.type)}")`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Escape SQL single quotes (generic utility).
|
|
46
|
+
*/
|
|
47
|
+
protected _escapeString(str: string): string {
|
|
48
|
+
return str.replace(/'/g, "''")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Detect the primary key column name in a schema (id / _id preferred).
|
|
53
|
+
*/
|
|
54
|
+
protected _detectPrimaryKey(schema: JSONSchema): string | null {
|
|
55
|
+
if (!schema.properties) return null
|
|
56
|
+
if (schema.properties['id']) return 'id'
|
|
57
|
+
if (schema.properties['_id']) return '_id'
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
}
|