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,995 +0,0 @@
1
- /**
2
- * DSL风格适配器 v2.0
3
- *
4
- * 统一的DSL Builder Pattern
5
- * 支持:纯字符串DSL + 链式调用扩展
6
- *
7
- * @module lib/adapters/DslAdapter
8
- * @version 2.0.0
9
- */
10
-
11
- const JSONSchemaCore = require("../core/JSONSchemaCore");
12
- const DslBuilder = require("../core/DslBuilder");
13
-
14
- // throw new Error('DEBUG FILE LOADED');
15
-
16
- /**
17
- * DSL适配器类
18
- * @class DslAdapter
19
- */
20
- class DslAdapter {
21
- /**
22
- * 解析DSL字符串为JSON Schema(内部使用)
23
- * @static
24
- * @param {string} dslString - DSL字符串
25
- * @returns {Object} JSON Schema对象
26
- */
27
- static parseString(dslString) {
28
- if (!dslString || typeof dslString !== "string") {
29
- throw new Error("DSL must be a string");
30
- }
31
-
32
- const trimmed = dslString.trim();
33
- let required = trimmed.endsWith("!");
34
- let dslWithoutRequired = required ? trimmed.slice(0, -1) : trimmed;
35
-
36
- // 特殊处理:array!数字范围 → array:数字范围 + 必填
37
- // 支持: array!1-10, array!1-, array!-10
38
- if (/^array![\d-]/.test(trimmed)) {
39
- dslWithoutRequired = trimmed.replace(/^array!/, "array:");
40
- required = true;
41
- }
42
-
43
- // 解析基本类型和约束
44
- const schema = this._parseType(dslWithoutRequired);
45
-
46
- return schema;
47
- }
48
-
49
- /**
50
- * parse方法别名(向后兼容)
51
- * @static
52
- * @param {string} dslString - DSL字符串
53
- * @returns {Object} JSON Schema对象
54
- */
55
- static parse(dslString) {
56
- const schema = this.parseString(dslString);
57
- schema._required = dslString.trim().endsWith("!");
58
- return schema;
59
- }
60
-
61
- /**
62
- * 解析类型和约束
63
- * @private
64
- * @static
65
- * @param {string} dsl - DSL字符串(不含!)
66
- * @returns {Object} JSON Schema对象
67
- */
68
- static _parseType(dsl) {
69
- // 处理数组DSL语法
70
- if (dsl.startsWith("array")) {
71
- const schema = { type: "array" };
72
-
73
- // 匹配模式: array:min-max<itemType> 或 array:constraint<itemType> 或 array<itemType>
74
- // 支持: array:1-10, array:1-, array:-10, array:10, array<string>, array:1-10<string>
75
- const arrayMatch = dsl.match(/^array(?::([^<]+?))?(?:<(.+)>)?$/);
76
-
77
- if (arrayMatch) {
78
- const [, constraint, itemType] = arrayMatch;
79
-
80
- // 解析约束
81
- if (constraint) {
82
- const trimmedConstraint = constraint.trim();
83
-
84
- if (trimmedConstraint.includes("-")) {
85
- // 范围约束: min-max, min-, -max
86
- const [min, max] = trimmedConstraint
87
- .split("-")
88
- .map((v) => v.trim());
89
- if (min) schema.minItems = parseInt(min, 10);
90
- if (max) schema.maxItems = parseInt(max, 10);
91
- } else {
92
- // 单个值 = 最大值
93
- schema.maxItems = parseInt(trimmedConstraint, 10);
94
- }
95
- }
96
-
97
- // 解析元素类型
98
- if (itemType) {
99
- schema.items = this._parseType(itemType.trim());
100
- }
101
-
102
- return schema;
103
- }
104
- }
105
-
106
- // 处理 enum:x,y,z 格式(逗号分隔的枚举,优先于 | 检测)
107
- // 支持格式:enum:a,b,c / enum:number:1,2,3 / enum:a|b|c
108
- if (dsl.startsWith("enum:")) {
109
- const enumBody = dsl.slice(5); // 去掉 'enum:' 前缀
110
-
111
- // 检查是否有类型前缀:enum:number:1,2,3
112
- const colonIdx = enumBody.indexOf(":");
113
- let enumType = "string";
114
- let enumValuesStr;
115
-
116
- if (colonIdx !== -1) {
117
- enumType = enumBody.slice(0, colonIdx);
118
- enumValuesStr = enumBody.slice(colonIdx + 1);
119
- } else {
120
- enumValuesStr = enumBody;
121
- }
122
-
123
- // 统一分隔符:支持逗号和管道符
124
- const separator = enumValuesStr.includes("|") ? "|" : ",";
125
- const values = enumValuesStr
126
- .split(separator)
127
- .map((v) => v.trim())
128
- .filter((v) => v);
129
-
130
- if (values.length > 0) {
131
- // 类型转换
132
- if (enumType === "number") {
133
- return { type: "number", enum: values.map((v) => parseFloat(v)) };
134
- } else if (enumType === "integer") {
135
- return { type: "integer", enum: values.map((v) => parseInt(v, 10)) };
136
- } else if (enumType === "boolean") {
137
- return { type: "boolean", enum: values.map((v) => v === "true") };
138
- }
139
- return { type: "string", enum: values };
140
- }
141
- }
142
-
143
- // 检查是否为纯枚举(包含|但没有:且没有数字范围)
144
- if (dsl.includes("|") && !dsl.includes(":") && !/^\w+\d+-\d+$/.test(dsl)) {
145
- return {
146
- type: "string",
147
- enum: dsl.split("|").map((v) => v.trim()),
148
- };
149
- }
150
-
151
- // 格式: type:constraint 或 type数字范围
152
- // 例如: string:3-32, number:0-100
153
- let type, constraint;
154
-
155
- // 检查是否有冒号
156
- const colonIndex = dsl.indexOf(":");
157
-
158
- if (colonIndex !== -1) {
159
- // 有冒号:string:3-32
160
- type = dsl.substring(0, colonIndex);
161
- constraint = dsl.substring(colonIndex + 1);
162
- } else {
163
- // 无冒号:检查是否有数字约束(string3-32)
164
- const match = dsl.match(/^([a-z]+)(\d.*)$/i);
165
- if (match) {
166
- type = match[1];
167
- constraint = match[2];
168
- } else {
169
- type = dsl;
170
- constraint = "";
171
- }
172
- }
173
-
174
- // 特殊处理 phone:country
175
- if (type === "phone") {
176
- const country = constraint || "cn";
177
- const config = patterns.phone[country];
178
- if (!config) throw new Error(`Unsupported country for phone: ${country}`);
179
- return {
180
- type: "string",
181
- pattern: config.pattern.source,
182
- minLength: config.min,
183
- maxLength: config.max,
184
- _customMessages: { pattern: config.key || config.msg },
185
- };
186
- }
187
-
188
- // 特殊处理 idCard:country
189
- if (type === "idCard") {
190
- const country = constraint || "cn";
191
- const config = patterns.idCard[country.toLowerCase()];
192
- if (!config)
193
- throw new Error(`Unsupported country for idCard: ${country}`);
194
- return {
195
- type: "string",
196
- pattern: config.pattern.source,
197
- minLength: config.min,
198
- maxLength: config.max,
199
- _customMessages: { pattern: config.key || config.msg },
200
- };
201
- }
202
-
203
- // 特殊处理 creditCard:type
204
- if (type === "creditCard") {
205
- const cardType = constraint || "visa";
206
- const config = patterns.creditCard[cardType.toLowerCase()];
207
- if (!config) throw new Error(`Unsupported credit card type: ${cardType}`);
208
- return {
209
- type: "string",
210
- pattern: config.pattern.source,
211
- _customMessages: { pattern: config.key || config.msg },
212
- };
213
- }
214
-
215
- // 特殊处理 licensePlate:country
216
- if (type === "licensePlate") {
217
- const country = constraint || "cn";
218
- const config = patterns.licensePlate[country.toLowerCase()];
219
- if (!config)
220
- throw new Error(`Unsupported country for licensePlate: ${country}`);
221
- return {
222
- type: "string",
223
- pattern: config.pattern.source,
224
- _customMessages: { pattern: config.key || config.msg },
225
- };
226
- }
227
-
228
- // 特殊处理 postalCode:country
229
- if (type === "postalCode") {
230
- const country = constraint || "cn";
231
- const config = patterns.postalCode[country.toLowerCase()];
232
- if (!config)
233
- throw new Error(`Unsupported country for postalCode: ${country}`);
234
- return {
235
- type: "string",
236
- pattern: config.pattern.source,
237
- _customMessages: { pattern: config.key || config.msg },
238
- };
239
- }
240
-
241
- // 特殊处理 passport:country
242
- if (type === "passport") {
243
- const country = constraint || "cn";
244
- const config = patterns.passport[country.toLowerCase()];
245
- if (!config)
246
- throw new Error(`Unsupported country for passport: ${country}`);
247
- return {
248
- type: "string",
249
- pattern: config.pattern.source,
250
- _customMessages: { pattern: config.key || config.msg },
251
- };
252
- }
253
-
254
- // 获取基础类型Schema
255
- const baseSchema = this._getBaseType(type);
256
-
257
- // 应用约束
258
- if (constraint) {
259
- const constraintSchema = this._parseConstraint(
260
- baseSchema.type,
261
- constraint,
262
- );
263
- Object.assign(baseSchema, constraintSchema);
264
- }
265
-
266
- return baseSchema;
267
- }
268
-
269
- /**
270
- * 获取基本类型Schema
271
- * @private
272
- * @static
273
- * @param {string} type - 类型名称
274
- * @returns {Object} 基本类型Schema
275
- */
276
- static _getBaseType(type) {
277
- const typeMap = {
278
- // 基本类型
279
- string: { type: "string" },
280
- number: { type: "number" },
281
- integer: { type: "integer" },
282
- boolean: { type: "boolean" },
283
- object: { type: "object" },
284
- array: { type: "array" },
285
- null: { type: "null" },
286
-
287
- // 格式类型
288
- date: { type: "string", format: "date" },
289
- datetime: { type: "string", format: "date-time" },
290
- time: { type: "string", format: "time" },
291
- email: { type: "string", format: "email" },
292
- url: { type: "string", format: "uri" },
293
- uuid: { type: "string", format: "uuid" },
294
- ipv4: { type: "string", format: "ipv4" },
295
- ipv6: { type: "string", format: "ipv6" },
296
-
297
- // 特殊类型(对比 joi 补充)
298
- binary: { type: "string", contentEncoding: "base64" },
299
- objectId: { type: "string", pattern: "^[0-9a-fA-F]{24}$" },
300
- hexColor: {
301
- type: "string",
302
- pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
303
- },
304
- macAddress: {
305
- type: "string",
306
- pattern: "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$",
307
- },
308
- cron: {
309
- type: "string",
310
- pattern:
311
- "^(\\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\\*\\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\\*|([0-9]|1[0-9]|2[0-3])|\\*\\/([0-9]|1[0-9]|2[0-3])) (\\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\\*\\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\\*|([1-9]|1[0-2])|\\*\\/([1-9]|1[0-2])) (\\*|([0-6])|\\*\\/([0-6]))$",
312
- },
313
- any: {}, // 任意类型
314
- };
315
-
316
- return typeMap[type] || { type: "string" };
317
- }
318
-
319
- /**
320
- * 解析约束
321
- * @private
322
- * @static
323
- * @param {string} baseType - 基础类型
324
- * @param {string} constraint - 约束字符串
325
- * @returns {Object} 约束对象
326
- *
327
- * @example
328
- * // 比较运算符 (v1.2.0+)
329
- * _parseConstraint('number', '>0') // { exclusiveMinimum: 0 }
330
- * _parseConstraint('number', '>=18') // { minimum: 18 }
331
- * _parseConstraint('number', '<100') // { exclusiveMaximum: 100 }
332
- * _parseConstraint('number', '<=100') // { maximum: 100 }
333
- * _parseConstraint('number', '=100') // { enum: [100] }
334
- * _parseConstraint('number', '>0.5') // { exclusiveMinimum: 0.5 } 支持小数
335
- */
336
- static _parseConstraint(baseType, constraint) {
337
- if (!constraint) return {};
338
-
339
- const result = {};
340
-
341
- // ========== 比较运算符(v1.1.2新增,最高优先级)==========
342
- if (baseType === "number" || baseType === "integer") {
343
- // 1. 大于等于: >=18, >=-10 (支持负数)
344
- const gteMatch = constraint.match(/^>=(-?\d+(?:\.\d+)?)$/);
345
- if (gteMatch) {
346
- result.minimum = parseFloat(gteMatch[1]);
347
- return result;
348
- }
349
-
350
- // 2. 小于等于: <=100, <=-10 (支持负数)
351
- const lteMatch = constraint.match(/^<=(-?\d+(?:\.\d+)?)$/);
352
- if (lteMatch) {
353
- result.maximum = parseFloat(lteMatch[1]);
354
- return result;
355
- }
356
-
357
- // 3. 大于: >0, >-10 (不包括边界值,支持负数)
358
- const gtMatch = constraint.match(/^>(-?\d+(?:\.\d+)?)$/);
359
- if (gtMatch) {
360
- result.exclusiveMinimum = parseFloat(gtMatch[1]);
361
- return result;
362
- }
363
-
364
- // 4. 小于: <100, <-10 (不包括边界值,支持负数)
365
- const ltMatch = constraint.match(/^<(-?\d+(?:\.\d+)?)$/);
366
- if (ltMatch) {
367
- result.exclusiveMaximum = parseFloat(ltMatch[1]);
368
- return result;
369
- }
370
-
371
- // 5. 等于: =100, =-10 (支持负数)
372
- const eqMatch = constraint.match(/^=(-?\d+(?:\.\d+)?)$/);
373
- if (eqMatch) {
374
- result.enum = [parseFloat(eqMatch[1])];
375
- return result;
376
- }
377
- }
378
-
379
- // 枚举: value1|value2|value3 (优先检查,避免被数字范围误判)
380
- if (constraint.includes("|") && !/^\d+-\d+$/.test(constraint)) {
381
- result.enum = constraint.split("|").map((v) => v.trim());
382
- return result;
383
- }
384
-
385
- // 范围约束: min-max 或 min- 或 -max
386
- // 支持小数: 0.5-99.9
387
- const rangeMatch = constraint.match(/^(\d*\.?\d*)-(\d*\.?\d*)$/);
388
- if (rangeMatch) {
389
- const [, min, max] = rangeMatch;
390
- if (baseType === "string") {
391
- if (min) result.minLength = parseInt(min, 10);
392
- if (max) result.maxLength = parseInt(max, 10);
393
- } else if (baseType === "array") {
394
- if (min) result.minItems = parseInt(min, 10);
395
- if (max) result.maxItems = parseInt(max, 10);
396
- } else {
397
- if (min) result.minimum = parseFloat(min);
398
- if (max) result.maximum = parseFloat(max);
399
- }
400
- return result;
401
- }
402
-
403
- // 单个约束: min, max
404
- // 支持小数: 99.99
405
- const singleMatch = constraint.match(/^(\d+(?:\.\d+)?)$/);
406
- if (singleMatch) {
407
- const value = parseFloat(singleMatch[1]);
408
- if (baseType === "string") {
409
- result.maxLength = Math.floor(value);
410
- } else if (baseType === "array") {
411
- result.maxItems = Math.floor(value);
412
- } else {
413
- result.maximum = value;
414
- }
415
- return result;
416
- }
417
-
418
- return constraint;
419
- }
420
-
421
- /**
422
- * 解析对象为Schema(支持DslBuilder)
423
- * @static
424
- * @param {Object} dslObject - DSL对象定义
425
- * @returns {Object} JSON Schema对象
426
- */
427
- static parseObject(dslObject) {
428
- const schema = {
429
- type: "object",
430
- properties: {},
431
- required: [],
432
- };
433
-
434
- for (const [key, value] of Object.entries(dslObject)) {
435
- let fieldKey = key;
436
- let isFieldRequired = false;
437
-
438
- // 检查 key 是否带 ! 后缀(表示该字段本身必填)
439
- if (key.endsWith("!")) {
440
- fieldKey = key.slice(0, -1);
441
- isFieldRequired = true;
442
- }
443
-
444
- let fieldSchema;
445
-
446
- // 1. Match 结构 (dsl.match)
447
- if (value && value._isMatch) {
448
- const matchSchema = this._buildMatchSchema(
449
- value.field,
450
- fieldKey,
451
- value.map,
452
- );
453
- if (matchSchema) {
454
- if (!schema.allOf) schema.allOf = [];
455
- schema.allOf.push(matchSchema);
456
- }
457
- // 占位Schema,确保字段存在
458
- fieldSchema = { description: `Depends on ${value.field}` };
459
- }
460
- // 2. If 结构 (dsl.if - 旧版本)
461
- else if (value && value._isIf) {
462
- const ifSchema = this._buildIfSchema(
463
- value.condition,
464
- fieldKey,
465
- value.then,
466
- value.else,
467
- );
468
- if (ifSchema) {
469
- if (!schema.allOf) schema.allOf = [];
470
- schema.allOf.push(ifSchema);
471
- }
472
- fieldSchema = {
473
- description: `Conditional field based on ${value.condition}`,
474
- };
475
- }
476
- // 3. ConditionalBuilder 结构 (dsl.if - 新版本)
477
- else if (value && value._isConditional) {
478
- // 调用 toSchema() 转换为 Schema 对象
479
- const conditionalSchema =
480
- typeof value.toSchema === "function" ? value.toSchema() : value;
481
- // 保存条件链,留待 Validator 执行
482
- fieldSchema = conditionalSchema;
483
- }
484
- // 4. DslBuilder 实例(链式调用结果)
485
- else if (value instanceof DslBuilder) {
486
- fieldSchema = value.toSchema();
487
- }
488
- // 5. 纯字符串 DSL
489
- else if (typeof value === "string") {
490
- const builder = new DslBuilder(value);
491
- fieldSchema = builder.toSchema();
492
- }
493
- // 6. 嵌套对象
494
- else if (
495
- typeof value === "object" &&
496
- !Array.isArray(value) &&
497
- value !== null
498
- ) {
499
- fieldSchema = this.parseObject(value);
500
- }
501
- // 7. 其他类型(保留原样)
502
- else {
503
- fieldSchema = value;
504
- }
505
-
506
- // 处理必填标记(优先级:key! > 字段内部的!)
507
- if (isFieldRequired) {
508
- schema.required.push(fieldKey);
509
- } else if (fieldSchema && fieldSchema._required) {
510
- schema.required.push(fieldKey);
511
- delete fieldSchema._required;
512
- }
513
-
514
- // 清理所有_required标记(包括嵌套的)
515
- this._cleanRequiredMarks(fieldSchema);
516
-
517
- schema.properties[fieldKey] = fieldSchema;
518
- }
519
-
520
- if (schema.required.length === 0) {
521
- delete schema.required;
522
- }
523
-
524
- return schema;
525
- }
526
-
527
- /**
528
- * 创建 Match 结构
529
- * @static
530
- * @param {string} field - 依赖字段名
531
- * @param {Object} map - 值映射
532
- * @returns {Object} Match结构
533
- */
534
- static match(field, map) {
535
- return {
536
- _isMatch: true,
537
- field,
538
- map,
539
- };
540
- }
541
-
542
- /**
543
- * 创建 If 结构
544
- * @static
545
- * @param {string} condition - 条件字段
546
- * @param {string|Object} thenSchema - 满足条件时的Schema
547
- * @param {string|Object} elseSchema - 不满足条件时的Schema
548
- * @returns {Object} If结构
549
- */
550
- static if(condition, thenSchema, elseSchema) {
551
- return {
552
- _isIf: true,
553
- condition,
554
- then: thenSchema,
555
- else: elseSchema,
556
- };
557
- }
558
-
559
- /**
560
- * 解析DSL定义(字符串/Builder/对象)
561
- * @private
562
- * @static
563
- */
564
- static _resolveDsl(dsl) {
565
- if (!dsl) return {};
566
- if (dsl instanceof DslBuilder) return dsl.toSchema();
567
- if (typeof dsl === "string") return new DslBuilder(dsl).toSchema();
568
- if (typeof dsl === "object" && !Array.isArray(dsl))
569
- return this.parseObject(dsl);
570
- return dsl;
571
- }
572
-
573
- /**
574
- * 构建 Match 对应的 JSON Schema (if-then-else 链)
575
- * @private
576
- * @static
577
- */
578
- static _buildMatchSchema(conditionField, targetField, map) {
579
- const entries = Object.entries(map).filter(([k]) => k !== "_default");
580
- const defaultDsl = map._default;
581
-
582
- const build = (index) => {
583
- if (index >= entries.length) {
584
- if (defaultDsl) {
585
- // 处理 _default 值(可能是 Match、If 或普通 DSL)
586
- if (defaultDsl._isMatch) {
587
- return this._buildMatchSchema(
588
- defaultDsl.field,
589
- targetField,
590
- defaultDsl.map,
591
- );
592
- } else if (defaultDsl._isIf) {
593
- return this._buildIfSchema(
594
- defaultDsl.condition,
595
- targetField,
596
- defaultDsl.then,
597
- defaultDsl.else,
598
- );
599
- } else {
600
- const s = this._resolveDsl(defaultDsl);
601
- const isRequired = s._required;
602
- this._cleanRequiredMarks(s);
603
- const result = { properties: { [targetField]: s } };
604
- if (isRequired) result.required = [targetField];
605
- return result;
606
- }
607
- }
608
- return {};
609
- }
610
-
611
- const [val, dsl] = entries[index];
612
-
613
- // 处理分支值(可能是 Match、If 或普通 DSL)
614
- let thenSchema;
615
- // 先检查 null/undefined
616
- if (dsl === null || dsl === undefined) {
617
- // 跳过 null/undefined 分支,继续下一个
618
- return build(index + 1);
619
- } else if (dsl._isMatch) {
620
- thenSchema = this._buildMatchSchema(dsl.field, targetField, dsl.map);
621
- } else if (dsl._isIf) {
622
- thenSchema = this._buildIfSchema(
623
- dsl.condition,
624
- targetField,
625
- dsl.then,
626
- dsl.else,
627
- );
628
- } else {
629
- const branchSchema = this._resolveDsl(dsl);
630
- const isRequired = branchSchema._required;
631
- this._cleanRequiredMarks(branchSchema);
632
- thenSchema = { properties: { [targetField]: branchSchema } };
633
- if (isRequired) thenSchema.required = [targetField];
634
- }
635
-
636
- return {
637
- if: { properties: { [conditionField]: { const: val } } },
638
- then: thenSchema,
639
- else: build(index + 1),
640
- };
641
- };
642
-
643
- return build(0);
644
- }
645
-
646
- /**
647
- * 构建 If 对应的 JSON Schema
648
- * @private
649
- * @static
650
- */
651
- static _buildIfSchema(conditionField, targetField, thenDsl, elseDsl) {
652
- // 处理 then 分支
653
- let thenResult;
654
- if (thenDsl && thenDsl._isMatch) {
655
- // then 是一个 Match 结构,需要递归构建
656
- thenResult = this._buildMatchSchema(
657
- thenDsl.field,
658
- targetField,
659
- thenDsl.map,
660
- );
661
- } else if (thenDsl && thenDsl._isIf) {
662
- // then 是一个 If 结构,需要递归构建
663
- thenResult = this._buildIfSchema(
664
- thenDsl.condition,
665
- targetField,
666
- thenDsl.then,
667
- thenDsl.else,
668
- );
669
- } else {
670
- const thenSchema = this._resolveDsl(thenDsl);
671
- const thenRequired = thenSchema._required;
672
- this._cleanRequiredMarks(thenSchema);
673
- thenResult = { properties: { [targetField]: thenSchema } };
674
- if (thenRequired) thenResult.required = [targetField];
675
- }
676
-
677
- // 处理 else 分支
678
- let elseResult = {};
679
- if (elseDsl) {
680
- if (elseDsl._isMatch) {
681
- // else 也是一个 Match 结构
682
- elseResult = this._buildMatchSchema(
683
- elseDsl.field,
684
- targetField,
685
- elseDsl.map,
686
- );
687
- } else if (elseDsl._isIf) {
688
- // else 也是一个 If 结构
689
- elseResult = this._buildIfSchema(
690
- elseDsl.condition,
691
- targetField,
692
- elseDsl.then,
693
- elseDsl.else,
694
- );
695
- } else {
696
- const elseSchema = this._resolveDsl(elseDsl);
697
- const elseRequired = elseSchema._required;
698
- this._cleanRequiredMarks(elseSchema);
699
- elseResult = { properties: { [targetField]: elseSchema } };
700
- if (elseRequired) elseResult.required = [targetField];
701
- }
702
- }
703
-
704
- // 默认行为:检查字段值为 true (适用于 isVip: boolean)
705
- return {
706
- if: { properties: { [conditionField]: { const: true } } },
707
- then: thenResult,
708
- else: elseResult,
709
- };
710
- }
711
-
712
- /**
713
- * 解析对象为Schema(旧版本,兼容)
714
- * @static
715
- * @param {Object} dslObject - DSL对象定义
716
- * @returns {Object} JSON Schema对象
717
- */
718
- static parseObjectOld(dslObject) {
719
- if (!dslObject || typeof dslObject !== "object") {
720
- throw new Error("DSL object must be an object");
721
- }
722
-
723
- const schema = {
724
- type: "object",
725
- properties: {},
726
- required: [],
727
- };
728
-
729
- for (const [key, value] of Object.entries(dslObject)) {
730
- if (typeof value === "string") {
731
- const parsed = this.parse(value);
732
-
733
- // 处理必填标记
734
- if (parsed._required) {
735
- schema.required.push(key);
736
- delete parsed._required; // 清理临时标记
737
- }
738
-
739
- // 清理所有_required标记(包括嵌套的)
740
- this._cleanRequiredMarks(parsed);
741
-
742
- schema.properties[key] = parsed;
743
- } else if (typeof value === "object") {
744
- // 嵌套对象
745
- schema.properties[key] = this.parseObject(value);
746
- }
747
- }
748
-
749
- if (schema.required.length === 0) {
750
- delete schema.required;
751
- }
752
-
753
- return schema;
754
- }
755
-
756
- /**
757
- * 清理Schema中的临时标记
758
- * @private
759
- * @static
760
- * @param {Object} schema - Schema对象
761
- */
762
- static _cleanRequiredMarks(schema) {
763
- if (!schema || typeof schema !== "object") {
764
- return;
765
- }
766
-
767
- // 只删除 _required 标记,保留其他扩展属性(如 _customValidators)
768
- // 这些属性将由自定义关键字处理
769
- delete schema._required;
770
-
771
- // 递归处理properties
772
- if (schema.properties) {
773
- for (const prop of Object.values(schema.properties)) {
774
- this._cleanRequiredMarks(prop);
775
- }
776
- }
777
-
778
- // 递归处理items
779
- if (schema.items) {
780
- this._cleanRequiredMarks(schema.items);
781
- }
782
- }
783
-
784
- /**
785
- * 转换为JSONSchemaCore实例
786
- * @static
787
- * @param {string|Object} dsl - DSL字符串或对象
788
- * @returns {JSONSchemaCore} JSONSchemaCore实例
789
- */
790
- static toCore(dsl) {
791
- let schema;
792
- if (typeof dsl === "string") {
793
- // 创建DslBuilder并转为Schema
794
- const builder = new DslBuilder(dsl);
795
- schema = builder.toSchema();
796
- } else {
797
- schema = this.parseObject(dsl);
798
- }
799
- return new JSONSchemaCore(schema);
800
- }
801
- }
802
-
803
- // ========== 导出统一API ==========
804
-
805
- /**
806
- * DSL函数 - 统一入口
807
- * @param {string|Object} definition - DSL字符串或对象定义
808
- * @returns {DslBuilder|Object} DslBuilder实例或JSON Schema对象
809
- *
810
- * @example
811
- * // 字符串:返回 DslBuilder(可链式调用)
812
- * const schema = dsl('email!')
813
- * .pattern(/custom/)
814
- * .messages({ 'string.pattern': '格式不正确' });
815
- *
816
- * // 对象:返回 JSON Schema
817
- * const schema = dsl({
818
- * username: 'string:3-32!',
819
- * email: dsl('email!').label('邮箱')
820
- * });
821
- */
822
- function dsl(definition) {
823
- // 字符串:返回 DslBuilder
824
- if (typeof definition === "string") {
825
- return new DslBuilder(definition);
826
- }
827
-
828
- // 对象:解析对象Schema
829
- if (typeof definition === "object" && !Array.isArray(definition)) {
830
- return DslAdapter.parseObject(definition);
831
- }
832
-
833
- throw new Error("Invalid DSL definition: must be string or object");
834
- }
835
-
836
- /**
837
- * 便捷验证函数(同步)
838
- *
839
- * @param {Object|DslBuilder|string} schema - JSON Schema、DslBuilder实例或DSL对象
840
- * @param {*} data - 待验证数据
841
- * @param {Object} options - 验证选项
842
- * @returns {Object} 验证结果 { valid, errors, data }
843
- *
844
- * @example
845
- * // 方式1:传入 JSON Schema
846
- * const result1 = validate({ type: 'object', properties: {...} }, data);
847
- *
848
- * // 方式2:传入 DslBuilder 实例
849
- * const result2 = validate(dsl('email!'), data);
850
- *
851
- * // 方式3:传入 DSL 对象(自动转换)
852
- * const result3 = validate({ email: 'email!', age: 'number!' }, data);
853
- */
854
- function validate(schema, data, options = {}) {
855
- const Validator = require("../core/Validator");
856
-
857
- // ✅ 自动检测并转换 DSL 对象
858
- if (_isDslObject(schema)) {
859
- schema = DslAdapter.parseObject(schema);
860
- }
861
-
862
- const validator = new Validator(options);
863
- return validator.validate(schema, data, options);
864
- }
865
-
866
- /**
867
- * 检测是否为 DSL 对象
868
- * @private
869
- * @param {*} obj - 待检测对象
870
- * @returns {boolean}
871
- */
872
- function _isDslObject(obj) {
873
- // 不是普通对象,直接返回 false
874
- if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
875
- return false;
876
- }
877
-
878
- // 如果有 toSchema 方法,是 DslBuilder,不是 DSL 对象
879
- if (typeof obj.toSchema === "function") {
880
- return false;
881
- }
882
-
883
- // 如果有 _isConditional 标记,是 ConditionalBuilder
884
- if (obj._isConditional) {
885
- return false;
886
- }
887
-
888
- // 如果有标准 JSON Schema 的 type 字段,认为是 JSON Schema
889
- if (
890
- obj.type &&
891
- [
892
- "string",
893
- "number",
894
- "integer",
895
- "boolean",
896
- "object",
897
- "array",
898
- "null",
899
- ].includes(obj.type)
900
- ) {
901
- return false;
902
- }
903
-
904
- // 如果有 properties 但所有属性值都是 JSON Schema 对象(包含 type),认为是 JSON Schema
905
- if (obj.properties && typeof obj.properties === "object") {
906
- const allStandardSchema = Object.values(obj.properties).every(
907
- (prop) => prop && typeof prop === "object" && prop.type,
908
- );
909
- if (allStandardSchema) {
910
- return false;
911
- }
912
- }
913
-
914
- const values = Object.values(obj);
915
-
916
- // ✅ 检查是否有 DslBuilder 实例(有 toSchema 方法的对象)
917
- const hasDslBuilder = values.some(
918
- (v) => v && typeof v === "object" && typeof v.toSchema === "function",
919
- );
920
-
921
- if (hasDslBuilder) {
922
- return true;
923
- }
924
-
925
- // 检查是否有 DSL 格式的字符串值(如 'email!', 'string:3-32')
926
- const hasDslString = values.some(
927
- (v) =>
928
- typeof v === "string" &&
929
- (v.includes(":") ||
930
- v.includes("!") ||
931
- v.includes("|") ||
932
- [
933
- "email",
934
- "url",
935
- "uuid",
936
- "date",
937
- "datetime",
938
- "time",
939
- "phone",
940
- "objectId",
941
- ].includes(v.split(":")[0].replace("!", ""))),
942
- );
943
-
944
- if (hasDslString) {
945
- return true;
946
- }
947
-
948
- // 检查是否有嵌套的 DSL 对象
949
- const hasNestedDslObject = values.some((v) => _isDslObject(v));
950
-
951
- return hasNestedDslObject;
952
- }
953
-
954
- /**
955
- * 便捷异步验证函数
956
- *
957
- * @param {Object|DslBuilder|string} schema - JSON Schema、DslBuilder实例或DSL对象
958
- * @param {*} data - 待验证数据
959
- * @param {Object} options - 验证选项
960
- * @returns {Promise<*>} 验证通过返回数据
961
- * @throws {ValidationError} 验证失败抛出异常
962
- *
963
- * @example
964
- * try {
965
- * // 方式1:传入 JSON Schema
966
- * const data1 = await validateAsync({ type: 'object', properties: {...} }, inputData);
967
- *
968
- * // 方式2:传入 DSL 对象(自动转换)
969
- * const data2 = await validateAsync({ email: 'email!', age: 'number!' }, inputData);
970
- *
971
- * console.log('验证通过:', data);
972
- * } catch (error) {
973
- * if (error instanceof ValidationError) {
974
- * console.error('验证失败:', error.errors);
975
- * }
976
- * }
977
- */
978
- async function validateAsync(schema, data, options = {}) {
979
- const Validator = require("../core/Validator");
980
-
981
- // ✅ 自动检测并转换 DSL 对象
982
- if (_isDslObject(schema)) {
983
- schema = DslAdapter.parseObject(schema);
984
- }
985
-
986
- const validator = new Validator(options);
987
- return validator.validateAsync(schema, data, options);
988
- }
989
-
990
- // 导出
991
- module.exports = dsl;
992
- module.exports.DslAdapter = DslAdapter;
993
- module.exports.DslBuilder = DslBuilder;
994
- module.exports.validate = validate;
995
- module.exports.validateAsync = validateAsync;