schema-dsl 1.2.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/CHANGELOG.md +130 -238
  2. package/LICENSE +21 -21
  3. package/README.md +628 -2486
  4. package/dist/DslBuilder-BIgQOAXp.d.ts +343 -0
  5. package/dist/DslBuilder-CjHTucNQ.d.cts +343 -0
  6. package/dist/Validator-CllRdrY0.d.ts +192 -0
  7. package/dist/Validator-D6okG9tr.d.cts +192 -0
  8. package/dist/index.cjs +6640 -0
  9. package/dist/index.d.cts +1151 -0
  10. package/dist/index.d.ts +1151 -0
  11. package/dist/index.js +6574 -0
  12. package/dist/plugin-CIKtTMtS.d.cts +246 -0
  13. package/dist/plugin-CIKtTMtS.d.ts +246 -0
  14. package/dist/plugins/custom-format.cjs +3818 -0
  15. package/dist/plugins/custom-format.d.cts +12 -0
  16. package/dist/plugins/custom-format.d.ts +12 -0
  17. package/dist/plugins/custom-format.js +3788 -0
  18. package/dist/plugins/custom-type-example.cjs +3811 -0
  19. package/dist/plugins/custom-type-example.d.cts +8 -0
  20. package/dist/plugins/custom-type-example.d.ts +8 -0
  21. package/dist/plugins/custom-type-example.js +3781 -0
  22. package/dist/plugins/custom-validator.cjs +144 -0
  23. package/dist/plugins/custom-validator.d.cts +10 -0
  24. package/dist/plugins/custom-validator.d.ts +10 -0
  25. package/dist/plugins/custom-validator.js +119 -0
  26. package/docs/FEATURE-INDEX.md +553 -519
  27. package/docs/add-custom-locale.md +496 -483
  28. package/docs/add-keyword.md +24 -0
  29. package/docs/api-reference.md +1047 -805
  30. package/docs/api.md +13 -0
  31. package/docs/best-practices-project-structure.md +417 -408
  32. package/docs/best-practices.md +712 -672
  33. package/docs/cache-manager.md +344 -336
  34. package/docs/compile.md +45 -0
  35. package/docs/conditional-api.md +1307 -1278
  36. package/docs/custom-extensions-guide.md +339 -411
  37. package/docs/design-philosophy.md +606 -601
  38. package/docs/doc-index.md +324 -0
  39. package/docs/dsl-syntax.md +714 -664
  40. package/docs/dynamic-locale.md +608 -598
  41. package/docs/enum.md +482 -475
  42. package/docs/error-handling.md +1975 -1966
  43. package/docs/export-guide.md +501 -462
  44. package/docs/export-limitations.md +567 -551
  45. package/docs/faq.md +596 -577
  46. package/docs/frontend-i18n-guide.md +307 -293
  47. package/docs/i18n-user-guide.md +487 -474
  48. package/docs/i18n.md +476 -457
  49. package/docs/index.md +48 -0
  50. package/docs/json-schema-basics.md +40 -0
  51. package/docs/label-vs-description.md +271 -262
  52. package/docs/markdown-exporter.md +406 -397
  53. package/docs/mongodb-exporter.md +302 -295
  54. package/docs/multi-language.md +26 -0
  55. package/docs/multi-type-support.md +322 -329
  56. package/docs/mysql-exporter.md +280 -273
  57. package/docs/number-operators.md +449 -442
  58. package/docs/optional-marker-guide.md +326 -321
  59. package/docs/performance-guide.md +49 -0
  60. package/docs/plugin-system.md +381 -542
  61. package/docs/plugin-type-registration.md +34 -0
  62. package/docs/postgresql-exporter.md +311 -304
  63. package/docs/public/favicon.svg +5 -0
  64. package/docs/quick-start.md +435 -761
  65. package/docs/runtime-locale-support.md +532 -521
  66. package/docs/schema-helper.md +345 -340
  67. package/docs/schema-utils-advanced-issues.md +23 -0
  68. package/docs/schema-utils-best-practices.md +20 -0
  69. package/docs/schema-utils-chaining.md +150 -143
  70. package/docs/schema-utils.md +524 -490
  71. package/docs/security-checklist.md +20 -0
  72. package/docs/string-extensions.md +488 -480
  73. package/docs/troubleshooting.md +486 -471
  74. package/docs/type-converter.md +310 -319
  75. package/docs/type-reference.md +242 -219
  76. package/docs/typescript-guide.md +584 -573
  77. package/docs/union-type-guide.md +157 -147
  78. package/docs/union-types.md +284 -277
  79. package/docs/validate-async.md +491 -480
  80. package/docs/validate-batch.md +49 -0
  81. package/docs/validate-dsl-object-support.md +578 -573
  82. package/docs/validate.md +506 -486
  83. package/docs/validation-guide.md +502 -484
  84. package/docs/validator.md +39 -0
  85. package/package.json +131 -73
  86. package/plugins/custom-format.cjs +8 -0
  87. package/plugins/custom-type-example.cjs +8 -0
  88. package/plugins/custom-validator.cjs +8 -0
  89. package/src/adapters/DslAdapter.ts +111 -0
  90. package/src/adapters/index.ts +1 -0
  91. package/src/config/constants.ts +83 -0
  92. package/src/config/index.ts +2 -0
  93. package/src/config/patterns.ts +77 -0
  94. package/src/core/CacheManager.ts +169 -0
  95. package/src/core/ConditionalBuilder.ts +382 -0
  96. package/src/core/ConditionalRuntime.ts +28 -0
  97. package/src/core/ConditionalValidator.ts +255 -0
  98. package/src/core/DslBuilder.ts +687 -0
  99. package/src/core/ErrorCodes.ts +38 -0
  100. package/src/core/ErrorFormatter.ts +271 -0
  101. package/src/core/JSONSchemaCore.ts +65 -0
  102. package/src/core/Locale.ts +187 -0
  103. package/src/core/MessageTemplate.ts +42 -0
  104. package/src/core/ObjectDslBuilder.ts +64 -0
  105. package/src/core/PluginManager.ts +326 -0
  106. package/src/core/StringExtensions.ts +140 -0
  107. package/src/core/TemplateEngine.ts +44 -0
  108. package/src/core/Validator.ts +448 -0
  109. package/src/errors/I18nError.ts +159 -0
  110. package/src/errors/ValidationError.ts +105 -0
  111. package/src/exporters/BaseExporter.ts +60 -0
  112. package/src/exporters/MarkdownExporter.ts +305 -0
  113. package/src/exporters/MongoDBExporter.ts +126 -0
  114. package/src/exporters/MySQLExporter.ts +156 -0
  115. package/src/exporters/PostgreSQLExporter.ts +222 -0
  116. package/src/exporters/index.ts +18 -0
  117. package/src/index.ts +651 -0
  118. package/{lib/locales/en-US.js → src/locales/en-US.ts} +160 -176
  119. package/{lib/locales/es-ES.js → src/locales/es-ES.ts} +160 -113
  120. package/{lib/locales/fr-FR.js → src/locales/fr-FR.ts} +160 -113
  121. package/src/locales/index.ts +103 -0
  122. package/{lib/locales/ja-JP.js → src/locales/ja-JP.ts} +160 -118
  123. package/src/locales/types.ts +156 -0
  124. package/{lib/locales/zh-CN.js → src/locales/zh-CN.ts} +160 -177
  125. package/src/parser/ConstraintParser.ts +101 -0
  126. package/src/parser/DslParser.ts +470 -0
  127. package/src/parser/SchemaCompiler.ts +66 -0
  128. package/src/parser/TypeRegistry.ts +250 -0
  129. package/src/parser/index.ts +6 -0
  130. package/src/plugins/custom-format.ts +124 -0
  131. package/src/plugins/custom-type-example.ts +106 -0
  132. package/src/plugins/custom-validator.ts +138 -0
  133. package/src/types/conditional.ts +28 -0
  134. package/src/types/config.ts +59 -0
  135. package/src/types/dsl.ts +131 -0
  136. package/src/types/error.ts +60 -0
  137. package/src/types/index.ts +17 -0
  138. package/src/types/infer.ts +128 -0
  139. package/src/types/plugin.ts +58 -0
  140. package/src/types/safe-regex.d.ts +9 -0
  141. package/src/types/schema.ts +66 -0
  142. package/src/types/validate.ts +71 -0
  143. package/src/utils/SchemaHelper.ts +196 -0
  144. package/src/utils/SchemaUtils.ts +365 -0
  145. package/src/utils/TypeConverter.ts +215 -0
  146. package/src/utils/index.ts +10 -0
  147. package/src/validators/CustomKeywords.ts +477 -0
  148. package/.eslintignore +0 -11
  149. package/.eslintrc.json +0 -27
  150. package/CONTRIBUTING.md +0 -368
  151. package/STATUS.md +0 -491
  152. package/changelogs/v1.0.0.md +0 -328
  153. package/changelogs/v1.0.9.md +0 -367
  154. package/changelogs/v1.1.0.md +0 -389
  155. package/changelogs/v1.1.1.md +0 -308
  156. package/changelogs/v1.1.2.md +0 -183
  157. package/changelogs/v1.1.3.md +0 -161
  158. package/changelogs/v1.1.4.md +0 -432
  159. package/changelogs/v1.1.5.md +0 -493
  160. package/changelogs/v1.1.6.md +0 -211
  161. package/changelogs/v1.1.8.md +0 -376
  162. package/changelogs/v1.2.3.md +0 -124
  163. package/docs/INDEX.md +0 -252
  164. package/docs/issues-resolved-summary.md +0 -196
  165. package/docs/performance-benchmark-report.md +0 -179
  166. package/docs/performance-quick-reference.md +0 -123
  167. package/docs/user-questions-answered.md +0 -353
  168. package/docs/validation-rules-v1.0.2.md +0 -1608
  169. package/examples/README.md +0 -81
  170. package/examples/array-dsl-example.js +0 -227
  171. package/examples/conditional-example.js +0 -288
  172. package/examples/conditional-non-object.js +0 -129
  173. package/examples/conditional-validate-example.js +0 -321
  174. package/examples/custom-extension.js +0 -85
  175. package/examples/dsl-match-example.js +0 -74
  176. package/examples/dsl-style.js +0 -118
  177. package/examples/dynamic-locale-configuration.js +0 -348
  178. package/examples/dynamic-locale-example.js +0 -287
  179. package/examples/enum.examples.js +0 -324
  180. package/examples/export-demo.js +0 -130
  181. package/examples/express-integration.js +0 -376
  182. package/examples/i18n-error-handling-complete.js +0 -381
  183. package/examples/i18n-error-handling-quickstart.md +0 -0
  184. package/examples/i18n-error.examples.js +0 -181
  185. package/examples/i18n-full-demo.js +0 -301
  186. package/examples/i18n-memory-safety.examples.js +0 -268
  187. package/examples/markdown-export.js +0 -71
  188. package/examples/middleware-usage.js +0 -93
  189. package/examples/new-features-comparison.js +0 -315
  190. package/examples/password-reset/README.md +0 -153
  191. package/examples/password-reset/schema.js +0 -26
  192. package/examples/password-reset/test.js +0 -101
  193. package/examples/plugin-system.examples.js +0 -205
  194. package/examples/schema-utils-chaining.examples.js +0 -250
  195. package/examples/simple-example.js +0 -122
  196. package/examples/slug.examples.js +0 -179
  197. package/examples/string-extensions.js +0 -297
  198. package/examples/union-type-example.js +0 -127
  199. package/examples/union-types-example.js +0 -77
  200. package/examples/user-registration/README.md +0 -156
  201. package/examples/user-registration/routes.js +0 -92
  202. package/examples/user-registration/schema.js +0 -150
  203. package/examples/user-registration/server.js +0 -74
  204. package/index.d.ts +0 -3658
  205. package/index.js +0 -475
  206. package/index.mjs +0 -60
  207. package/lib/adapters/DslAdapter.js +0 -995
  208. package/lib/adapters/index.js +0 -20
  209. package/lib/config/constants.js +0 -286
  210. package/lib/config/patterns/common.js +0 -47
  211. package/lib/config/patterns/creditCard.js +0 -9
  212. package/lib/config/patterns/idCard.js +0 -9
  213. package/lib/config/patterns/index.js +0 -9
  214. package/lib/config/patterns/licensePlate.js +0 -4
  215. package/lib/config/patterns/passport.js +0 -4
  216. package/lib/config/patterns/phone.js +0 -9
  217. package/lib/config/patterns/postalCode.js +0 -5
  218. package/lib/core/CacheManager.js +0 -376
  219. package/lib/core/ConditionalBuilder.js +0 -503
  220. package/lib/core/DslBuilder.js +0 -1589
  221. package/lib/core/ErrorCodes.js +0 -233
  222. package/lib/core/ErrorFormatter.js +0 -445
  223. package/lib/core/JSONSchemaCore.js +0 -347
  224. package/lib/core/Locale.js +0 -130
  225. package/lib/core/MessageTemplate.js +0 -98
  226. package/lib/core/PluginManager.js +0 -448
  227. package/lib/core/StringExtensions.js +0 -240
  228. package/lib/core/Validator.js +0 -654
  229. package/lib/errors/I18nError.js +0 -328
  230. package/lib/errors/ValidationError.js +0 -191
  231. package/lib/exporters/MarkdownExporter.js +0 -420
  232. package/lib/exporters/MongoDBExporter.js +0 -162
  233. package/lib/exporters/MySQLExporter.js +0 -212
  234. package/lib/exporters/PostgreSQLExporter.js +0 -289
  235. package/lib/exporters/index.js +0 -24
  236. package/lib/locales/index.js +0 -8
  237. package/lib/utils/LRUCache.js +0 -174
  238. package/lib/utils/SchemaHelper.js +0 -240
  239. package/lib/utils/SchemaUtils.js +0 -445
  240. package/lib/utils/TypeConverter.js +0 -245
  241. package/lib/utils/index.js +0 -13
  242. package/lib/validators/CustomKeywords.js +0 -616
  243. package/lib/validators/index.js +0 -11
@@ -1,654 +0,0 @@
1
- /**
2
- * Validator - JSON Schema验证器
3
- *
4
- * 基于ajv实现,支持JSON Schema Draft 7标准
5
- *
6
- * @module lib/core/Validator
7
- * @version 1.0.0
8
- */
9
-
10
- const Ajv = require('ajv');
11
- const addFormats = require('ajv-formats');
12
- const CacheManager = require('./CacheManager');
13
- const ErrorFormatter = require('./ErrorFormatter');
14
- const CustomKeywords = require('../validators/CustomKeywords');
15
- const Locale = require('./Locale');
16
-
17
- /**
18
- * 验证器类
19
- *
20
- * @class Validator
21
- * @description 封装ajv,提供统一的验证接口
22
- */
23
- class Validator {
24
- /**
25
- * 构造函数
26
- * @param {Object} options - ajv配置选项
27
- * @param {boolean} options.allErrors - 是否返回所有错误(默认true)
28
- * @param {boolean} options.useDefaults - 是否使用默认值(默认true)
29
- * @param {boolean} options.coerceTypes - 是否自动类型转换(默认false)
30
- * @param {boolean} options.removeAdditional - 是否移除额外属性(默认false)
31
- * @param {Object} options.cache - 缓存配置选项 (v1.0.4+)
32
- * @param {number} options.cache.maxSize - 最大缓存条目数(默认100)
33
- * @param {number} options.cache.ttl - 缓存过期时间(毫秒,默认3600000)
34
- */
35
- constructor(options = {}) {
36
- // ajv配置
37
- this.ajvOptions = {
38
- allErrors: options.allErrors !== false,
39
- useDefaults: options.useDefaults !== false,
40
- coerceTypes: options.coerceTypes || false,
41
- removeAdditional: options.removeAdditional || false,
42
- verbose: true, // 启用详细模式,以便访问 parentSchema
43
- ...options
44
- };
45
-
46
- // 创建ajv实例
47
- this.ajv = new Ajv(this.ajvOptions);
48
-
49
- // 添加格式支持(email、uri、date-time等)
50
- addFormats(this.ajv);
51
-
52
- // 注册自定义关键字
53
- CustomKeywords.registerAll(this.ajv);
54
-
55
- // 编译缓存(支持自定义配置)
56
- const cacheOptions = {
57
- maxSize: options.cache?.maxSize ?? 100,
58
- ttl: options.cache?.ttl ?? 3600000, // 1小时
59
- enabled: options.cache?.enabled,
60
- statsEnabled: options.cache?.statsEnabled
61
- };
62
- this.cache = new CacheManager(cacheOptions);
63
-
64
- // 错误格式化器
65
- this.errorFormatter = new ErrorFormatter();
66
-
67
- // ✅ 性能优化:WeakMap 缓存键(避免 JSON.stringify)
68
- this.schemaMap = new WeakMap();
69
- this.schemaKeyCounter = 0;
70
-
71
- // ✅ 性能优化:缓存 DslBuilder 转换结果
72
- this.dslSchemaCache = new WeakMap();
73
-
74
- // 自定义关键字注册表
75
- this.customKeywords = new Map();
76
- }
77
-
78
- /**
79
- * 编译Schema
80
- * @param {Object} schema - JSON Schema对象
81
- * @param {string} cacheKey - 缓存键(可选)
82
- * @returns {Function} ajv验证函数
83
- */
84
- compile(schema, cacheKey = null) {
85
- // 尝试从缓存获取
86
- if (cacheKey) {
87
- const cached = this.cache.get(cacheKey);
88
- if (cached) {
89
- return cached;
90
- }
91
- }
92
-
93
- try {
94
- // 编译Schema
95
- const validate = this.ajv.compile(schema);
96
-
97
- // 缓存编译结果
98
- if (cacheKey) {
99
- this.cache.set(cacheKey, validate);
100
- }
101
-
102
- return validate;
103
- } catch (error) {
104
- throw new Error(`Schema compilation failed: ${error.message}`);
105
- }
106
- }
107
-
108
- /**
109
- * 验证数据
110
- * @param {Object} schema - JSON Schema对象或已编译的验证函数
111
- * @param {*} data - 待验证的数据
112
- * @param {Object} options - 验证选项
113
- * @param {boolean} options.format - 是否格式化错误(默认true)
114
- * @param {string} options.locale - 动态指定语言(如 'zh-CN', 'en-US')
115
- * @returns {Object} 验证结果 { valid: boolean, errors: Array, data: * }
116
- */
117
- validate(schema, data, options = {}) {
118
- // ✅ 优化:直接传递 locale 到内部方法,不再切换全局状态
119
- return this._validateInternal(schema, data, options);
120
- }
121
-
122
- /**
123
- * 内部验证方法
124
- * @private
125
- */
126
- _validateInternal(schema, data, options = {}) {
127
- const shouldFormat = options.format !== false;
128
- const locale = options.locale || Locale.getLocale();
129
-
130
- // ✅ 性能优化:缓存 DslBuilder 转换结果
131
- if (schema && typeof schema.toSchema === 'function') {
132
- if (!this.dslSchemaCache.has(schema)) {
133
- this.dslSchemaCache.set(schema, schema.toSchema());
134
- }
135
- schema = this.dslSchemaCache.get(schema);
136
- }
137
-
138
- // ✅ 处理 ConditionalBuilder 条件链(运行时条件判断)
139
- if (schema && schema._isConditional) {
140
- // 顶层 ConditionalBuilder(直接作为 Schema 使用)
141
- // 此时验证的是整个数据对象
142
- return this._validateConditional(schema, data, null, data, options);
143
- }
144
-
145
- // ✅ 预处理包含 ConditionalBuilder 的 Schema
146
- if (schema && schema.properties) {
147
- const hasConditional = Object.values(schema.properties).some(
148
- fieldSchema => fieldSchema && fieldSchema._isConditional
149
- );
150
-
151
- if (hasConditional) {
152
- return this._validateWithConditionals(schema, data, options);
153
- }
154
- }
155
-
156
- // 检查是否需要移除额外字段 (clean 模式)
157
- if (schema && schema._removeAdditional) {
158
- // 创建新的 Validator 实例,启用 removeAdditional
159
- const tempValidator = new Validator({
160
- ...this.ajvOptions,
161
- removeAdditional: true
162
- });
163
-
164
- // 移除标记字段并深拷贝,避免修改原 schema
165
- const cleanSchema = JSON.parse(JSON.stringify(schema));
166
- delete cleanSchema._removeAdditional;
167
-
168
- return tempValidator.validate(cleanSchema, data, options);
169
- }
170
-
171
- try {
172
- // 如果schema是函数,说明已编译
173
- let validate;
174
- if (typeof schema === 'function') {
175
- validate = schema;
176
- } else {
177
- // 生成缓存键
178
- const cacheKey = this._generateCacheKey(schema);
179
- validate = this.compile(schema, cacheKey);
180
- }
181
-
182
- // 执行验证
183
- const valid = validate(data);
184
-
185
- // ✅ 优化:直接传递 locale 和 messages 到格式化器,不修改全局状态
186
- const errors = valid ? [] : (
187
- shouldFormat
188
- ? this.errorFormatter.format(validate.errors, {
189
- locale,
190
- messages: options.messages
191
- })
192
- : validate.errors
193
- );
194
-
195
- return {
196
- valid,
197
- errors,
198
- data // 可能被useDefaults修改过
199
- };
200
- } catch (error) {
201
- return {
202
- valid: false,
203
- errors: [{
204
- message: `Validation error: ${error.message}`,
205
- path: '',
206
- keyword: 'error'
207
- }],
208
- data
209
- };
210
- }
211
- }
212
-
213
- /**
214
- * 快速验证(不缓存)
215
- * @param {Object} schema - JSON Schema对象
216
- * @param {*} data - 待验证的数据
217
- * @returns {boolean} 是否有效
218
- */
219
- quickValidate(schema, data) {
220
- try {
221
- return this.ajv.validate(schema, data);
222
- } catch (error) {
223
- return false;
224
- }
225
- }
226
-
227
- /**
228
- * 异步验证方法(验证失败自动抛出异常)
229
- *
230
- * @param {Object|Function} schema - JSON Schema对象或DslBuilder实例
231
- * @param {*} data - 待验证的数据
232
- * @param {Object} options - 验证选项(可选)
233
- * @param {boolean} options.format - 是否格式化错误(默认true)
234
- * @param {string} options.locale - 语言环境(如 'zh-CN', 'en-US')
235
- * @returns {Promise<*>} 验证通过返回处理后的数据
236
- * @throws {ValidationError} 验证失败抛出异常
237
- *
238
- * @example
239
- * // 基础使用
240
- * try {
241
- * const data = await validator.validateAsync(userSchema, inputData);
242
- * console.log('验证通过:', data);
243
- * } catch (error) {
244
- * if (error instanceof ValidationError) {
245
- * console.error('验证失败:', error.errors);
246
- * }
247
- * }
248
- *
249
- * @example
250
- * // Express 中使用
251
- * app.post('/users', async (req, res, next) => {
252
- * try {
253
- * const data = await validator.validateAsync(userSchema, req.body);
254
- * const user = await db.users.insert(data);
255
- * res.json(user);
256
- * } catch (error) {
257
- * next(error);
258
- * }
259
- * });
260
- */
261
- async validateAsync(schema, data, options = {}) {
262
- const result = this.validate(schema, data, options);
263
-
264
- if (!result.valid) {
265
- const ValidationError = require('../errors/ValidationError');
266
- throw new ValidationError(result.errors, data);
267
- }
268
-
269
- return result.data;
270
- }
271
-
272
- /**
273
- * 批量验证
274
- * @param {Object} schema - JSON Schema对象
275
- * @param {Array} dataArray - 数据数组
276
- * @returns {Array} 验证结果数组
277
- */
278
- validateBatch(schema, dataArray) {
279
- if (!Array.isArray(dataArray)) {
280
- throw new Error('Data must be an array');
281
- }
282
-
283
- // 编译一次,复用验证函数
284
- const cacheKey = this._generateCacheKey(schema);
285
- const validate = this.compile(schema, cacheKey);
286
-
287
- return dataArray.map(data => this.validate(validate, data));
288
- }
289
-
290
- /**
291
- * 添加自定义关键字
292
- * @param {string} keyword - 关键字名称
293
- * @param {Object} definition - 关键字定义
294
- * @returns {Validator} 返回this支持链式调用
295
- */
296
- addKeyword(keyword, definition) {
297
- try {
298
- this.ajv.addKeyword(keyword, definition);
299
- this.customKeywords.set(keyword, definition);
300
- return this;
301
- } catch (error) {
302
- throw new Error(`Failed to add keyword '${keyword}': ${error.message}`);
303
- }
304
- }
305
-
306
- /**
307
- * 添加自定义格式
308
- * @param {string} name - 格式名称
309
- * @param {Function|RegExp} validator - 验证函数或正则表达式
310
- * @returns {Validator} 返回this支持链式调用
311
- */
312
- addFormat(name, validator) {
313
- try {
314
- this.ajv.addFormat(name, validator);
315
- return this;
316
- } catch (error) {
317
- throw new Error(`Failed to add format '${name}': ${error.message}`);
318
- }
319
- }
320
-
321
- /**
322
- * 添加Schema引用
323
- * @param {string} uri - Schema URI
324
- * @param {Object} schema - JSON Schema对象
325
- * @returns {Validator} 返回this支持链式调用
326
- */
327
- addSchema(uri, schema) {
328
- try {
329
- this.ajv.addSchema(schema, uri);
330
- return this;
331
- } catch (error) {
332
- throw new Error(`Failed to add schema '${uri}': ${error.message}`);
333
- }
334
- }
335
-
336
- /**
337
- * 移除Schema引用
338
- * @param {string} uri - Schema URI
339
- * @returns {Validator} 返回this支持链式调用
340
- */
341
- removeSchema(uri) {
342
- this.ajv.removeSchema(uri);
343
- return this;
344
- }
345
-
346
- /**
347
- * 获取ajv实例(高级用法)
348
- * @returns {Ajv} ajv实例
349
- */
350
- getAjv() {
351
- return this.ajv;
352
- }
353
-
354
- /**
355
- * 清空缓存
356
- */
357
- clearCache() {
358
- this.cache.clear();
359
- }
360
-
361
- /**
362
- * 获取缓存统计
363
- * @returns {Object} 缓存统计信息
364
- */
365
- getCacheStats() {
366
- return this.cache.stats();
367
- }
368
-
369
- /**
370
- * 生成Schema的缓存键
371
- * @private
372
- * @param {Object} schema - JSON Schema对象
373
- * @returns {string} 缓存键
374
- */
375
- _generateCacheKey(schema) {
376
- // ✅ 性能优化:使用 WeakMap 避免昂贵的 JSON.stringify
377
- // 对于对象类型的 schema,使用 WeakMap 存储唯一标识符
378
- if (typeof schema === 'object' && schema !== null) {
379
- if (!this.schemaMap.has(schema)) {
380
- this.schemaMap.set(schema, `schema_${++this.schemaKeyCounter}`);
381
- }
382
- return this.schemaMap.get(schema);
383
- }
384
-
385
- // 对于原始类型或 null,降级到字符串化
386
- return JSON.stringify(schema);
387
- }
388
-
389
- /**
390
- * 验证包含 ConditionalBuilder 字段的 Schema
391
- * @private
392
- * @param {Object} schema - Schema 对象
393
- * @param {*} data - 待验证的数据
394
- * @param {Object} options - 验证选项
395
- * @returns {Object} 验证结果
396
- */
397
- _validateWithConditionals(schema, data, options = {}) {
398
- const errors = [];
399
-
400
- // 深拷贝 schema,避免修改原始对象
401
- const cleanSchema = JSON.parse(JSON.stringify(schema));
402
- const conditionalFields = {};
403
-
404
- // 提取所有条件字段
405
- for (const [fieldName, fieldSchema] of Object.entries(schema.properties || {})) {
406
- if (fieldSchema && fieldSchema._isConditional) {
407
- conditionalFields[fieldName] = fieldSchema;
408
- // 从 cleanSchema 中删除条件字段
409
- delete cleanSchema.properties[fieldName];
410
- }
411
- }
412
-
413
- // 先验证非条件字段
414
- const baseResult = this._validateInternal(cleanSchema, data, options);
415
- if (!baseResult.valid) {
416
- errors.push(...baseResult.errors);
417
- }
418
-
419
- // 然后验证每个条件字段
420
- for (const [fieldName, conditionalSchema] of Object.entries(conditionalFields)) {
421
- // ✅ 传递字段名和完整数据对象
422
- const fieldResult = this._validateConditional(
423
- conditionalSchema,
424
- data,
425
- fieldName,
426
- data[fieldName],
427
- options
428
- );
429
-
430
- if (!fieldResult.valid) {
431
- // 添加字段路径信息
432
- fieldResult.errors.forEach(error => {
433
- if (!error.path || error.path === '') {
434
- error.path = fieldName;
435
- }
436
- errors.push(error);
437
- });
438
- }
439
- }
440
-
441
- return {
442
- valid: errors.length === 0,
443
- errors,
444
- data
445
- };
446
- }
447
-
448
- /**
449
- * 验证条件链(ConditionalBuilder)
450
- * @private
451
- * @param {Object} conditionalSchema - 条件 Schema 对象
452
- * @param {*} data - 完整数据对象(用于条件判断)
453
- * @param {string} fieldName - 字段名
454
- * @param {*} fieldValue - 字段值(用于验证)
455
- * @param {Object} options - 验证选项
456
- * @returns {Object} 验证结果
457
- */
458
- _validateConditional(conditionalSchema, data, fieldName, fieldValue, options = {}) {
459
- const locale = options.locale || Locale.getLocale();
460
-
461
- try {
462
- // ✅ 遍历条件链,执行第一个匹配的条件
463
- // 对于 if/elseIf 链,只执行第一个满足条件的分支
464
- for (let i = 0; i < conditionalSchema.conditions.length; i++) {
465
- const cond = conditionalSchema.conditions[i];
466
-
467
- // 执行组合条件(支持 and/or),获取结果和失败消息
468
- const evaluation = conditionalSchema._evaluateCondition(cond, data);
469
- const matched = evaluation.result;
470
- const failedMessage = evaluation.failedMessage;
471
-
472
- if (cond.action === 'throw') {
473
- // ✅ message 模式:条件为 true 时抛错,条件为 false 时通过
474
- if (matched) {
475
- // ✅ 条件满足(true),抛出错误
476
- // 优先使用具体的失败消息,如果没有则使用整体消息
477
- const errorMsg = failedMessage || cond.message;
478
- // 支持多语言:如果 message 是 key(如 'conditional.underAge'),从语言包获取翻译
479
- // 传递 locale 参数以支持动态语言切换
480
- const messageConfig = Locale.getMessage(errorMsg, options.messages || {}, locale);
481
-
482
- // v1.1.5: Locale.getMessage 返回对象 { code, message },需要提取 message
483
- const errorMessage = typeof messageConfig === 'object' && messageConfig.message
484
- ? messageConfig.message
485
- : messageConfig;
486
-
487
- return {
488
- valid: false,
489
- errors: [{
490
- message: errorMessage,
491
- path: '',
492
- keyword: 'conditional',
493
- params: { condition: cond.type }
494
- }],
495
- data
496
- };
497
- }
498
- // 条件不满足(false),继续验证
499
- continue;
500
- }
501
-
502
- // ✅ then/else 模式:找到第一个满足的条件就执行并返回
503
- if (matched) {
504
- // 条件满足,执行 then Schema
505
- if (cond.then !== undefined && cond.then !== null) {
506
- return this._executeThenBranch(cond.then, data, fieldValue, fieldName, options);
507
- }
508
-
509
- // 条件满足但没有 then,表示验证通过
510
- return {
511
- valid: true,
512
- errors: [],
513
- data
514
- };
515
- }
516
-
517
- // ✅ 如果是 if/elseIf 条件不满足,继续检查下一个 elseIf
518
- // 这样就支持了 if...elseIf...elseIf...else 链
519
- }
520
-
521
- // ✅ 所有条件都不满足,执行 else
522
- if (conditionalSchema.else !== undefined) {
523
- if (conditionalSchema.else === null) {
524
- // else 为 null,表示跳过验证
525
- return {
526
- valid: true,
527
- errors: [],
528
- data
529
- };
530
- }
531
-
532
- // 执行 else Schema
533
- return this._executeThenBranch(conditionalSchema.else, data, fieldValue, fieldName, options);
534
- }
535
-
536
- // 没有 else,表示不验证
537
- return {
538
- valid: true,
539
- errors: [],
540
- data
541
- };
542
- } catch (error) {
543
- return {
544
- valid: false,
545
- errors: [{
546
- message: `Conditional validation error: ${error.message}`,
547
- path: '',
548
- keyword: 'conditional'
549
- }],
550
- data
551
- };
552
- }
553
- }
554
-
555
- /**
556
- * 执行 then 分支(提取公共逻辑)
557
- * @private
558
- * @param {*} thenSchema - then 分支的 Schema
559
- * @param {*} data - 完整数据对象(用于嵌套条件判断)
560
- * @param {*} fieldValue - 字段值(用于验证)
561
- * @param {string} fieldName - 字段名
562
- * @param {Object} options - 验证选项
563
- */
564
- _executeThenBranch(thenSchema, data, fieldValue, fieldName, options) {
565
- const DslBuilder = require('./DslBuilder');
566
-
567
- // ✅ 如果是 ConditionalBuilder 实例(未调用 toSchema),先转换
568
- if (thenSchema && typeof thenSchema.toSchema === 'function') {
569
- thenSchema = thenSchema.toSchema();
570
- }
571
-
572
- // ✅ 处理嵌套的 ConditionalBuilder(已转换为 Schema 对象)
573
- if (thenSchema && thenSchema._isConditional) {
574
- // 嵌套的条件构建器,递归处理
575
- // 传递完整数据对象用于条件判断,传递字段值用于验证
576
- return this._validateConditional(thenSchema, data, fieldName, fieldValue, options);
577
- }
578
-
579
- // 如果是字符串,解析为 Schema
580
- if (typeof thenSchema === 'string') {
581
- const builder = new DslBuilder(thenSchema);
582
- thenSchema = builder.toSchema();
583
- }
584
-
585
- // ✅ 验证字段值
586
- return this._validateFieldValue(thenSchema, fieldValue, fieldName, options);
587
- }
588
-
589
- /**
590
- * 验证字段值(处理空字符串和 undefined)
591
- * @private
592
- * @param {Object} schema - Schema 对象
593
- * @param {*} fieldValue - 字段值
594
- * @param {string} fieldName - 字段名
595
- * @param {Object} options - 验证选项
596
- * @returns {Object} 验证结果
597
- */
598
- _validateFieldValue(schema, fieldValue, fieldName, options = {}) {
599
- // ✅ 检查字段是否必填
600
- const isRequired = schema && schema._required === true;
601
-
602
- // ✅ 处理 undefined:可选字段缺失时跳过验证
603
- if (!isRequired && fieldValue === undefined) {
604
- return {
605
- valid: true,
606
- errors: [],
607
- data: fieldValue
608
- };
609
- }
610
-
611
- // ✅ 处理空字符串:可选字段的空字符串视为未提供
612
- if (!isRequired && fieldValue === '') {
613
- return {
614
- valid: true,
615
- errors: [],
616
- data: fieldValue
617
- };
618
- }
619
-
620
- // 正常验证
621
- return this._validateInternal(schema, fieldValue, options);
622
- }
623
-
624
- /**
625
- * 静态工厂方法
626
- * @static
627
- * @param {Object} options - ajv配置选项
628
- * @returns {Validator} Validator实例
629
- */
630
- static create(options) {
631
- return new Validator(options);
632
- }
633
-
634
- /**
635
- * 静态方法:快速验证(不创建实例)
636
- * @static
637
- * @param {Object} schema - JSON Schema对象
638
- * @param {*} data - 待验证的数据
639
- * @returns {boolean} 是否有效
640
- */
641
- static quickValidate(schema, data) {
642
- const ajv = new Ajv();
643
- return ajv.validate(schema, data);
644
- }
645
- }
646
-
647
- // Support calling without new
648
- const ValidatorProxy = new Proxy(Validator, {
649
- apply: function (target, thisArg, argumentsList) {
650
- return new target(...argumentsList);
651
- }
652
- });
653
-
654
- module.exports = ValidatorProxy;