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,411 +1,339 @@
1
- # 自定义扩展指南
2
-
3
- > **版本**: v1.0.2
4
- > **更新日期**: 2025-12-31
5
- > **用途**: 教你如何扩展schema-dsl,添加自己的验证器
6
-
7
- ---
8
-
9
- ## 📑 目录
10
-
11
- - [快速开始](#快速开始)
12
- - [添加自定义AJV关键字](#添加自定义ajv关键字)
13
- - [扩展DslBuilder方法](#扩展dslbuilder方法)
14
- - [添加预定义模式](#添加预定义模式)
15
- - [多语言支持](#多语言支持)
16
- - [完整示例](#完整示例)
17
-
18
- ---
19
-
20
- ## 快速开始
21
-
22
- schema-dsl采用模块化设计,你可以轻松扩展:
23
-
24
- 1. **AJV关键字** - 底层验证逻辑
25
- 2. **DslBuilder方法** - DSL语法糖
26
- 3. **预定义模式** - 常用正则模式
27
- 4. **多语言消息** - 错误消息国际化
28
-
29
- ---
30
-
31
- ## 添加自定义AJV关键字
32
-
33
- ### 步骤1:注册关键字
34
-
35
- `lib/validators/CustomKeywords.js` 中添加:
36
-
37
- ```javascript
38
- static registerCustomValidators(ajv) {
39
- // 示例:手机号归属地验证
40
- ajv.addKeyword({
41
- keyword: 'phoneLocation',
42
- type: 'string',
43
- schemaType: 'string', // location参数类型
44
- validate: function validate(location, phoneNumber) {
45
- // location: 期望的归属地,如 'beijing'
46
- // phoneNumber: 用户输入的手机号
47
-
48
- const locationPrefixes = {
49
- 'beijing': ['130', '131', '132'],
50
- 'shanghai': ['133', '134', '135']
51
- };
52
-
53
- const prefixes = locationPrefixes[location];
54
- if (!prefixes) {
55
- validate.errors = [{
56
- keyword: 'phoneLocation',
57
- message: 'phone.location.unknown',
58
- params: { location }
59
- }];
60
- return false;
61
- }
62
-
63
- const prefix = phoneNumber.substring(0, 3);
64
- if (!prefixes.includes(prefix)) {
65
- validate.errors = [{
66
- keyword: 'phoneLocation',
67
- message: 'phone.location.mismatch',
68
- params: { expected: location, actual: prefix }
69
- }];
70
- return false;
71
- }
72
-
73
- return true;
74
- },
75
- errors: true
76
- });
77
- }
78
- ```
79
-
80
- ### 步骤2:在registerAll中调用
81
-
82
- ```javascript
83
- static registerAll(ajv) {
84
- // ...existing keywords...
85
- this.registerCustomValidators(ajv);
86
- }
87
- ```
88
-
89
- ### 步骤3:添加多语言消息
90
-
91
- 在 `lib/locales/zh-CN.js` 中:
92
-
93
- ```javascript
94
- module.exports = {
95
- // ...existing messages...
96
- 'phone.location.unknown': '未知的归属地: {{#location}}',
97
- 'phone.location.mismatch': '手机号归属地不匹配,期望{{#expected}}'
98
- };
99
- ```
100
-
101
- ---
102
-
103
- ## 扩展DslBuilder方法
104
-
105
- ### 步骤1:添加便捷方法
106
-
107
- 在 `lib/core/DslBuilder.js` 中添加:
108
-
109
- ```javascript
110
- /**
111
- * 手机号归属地验证
112
- * @param {string} location - 归属地
113
- * @returns {DslBuilder}
114
- */
115
- phoneLocation(location) {
116
- if (this._baseSchema.type !== 'string') {
117
- throw new Error('phoneLocation() only applies to string type');
118
- }
119
- this._baseSchema.phoneLocation = location;
120
- return this;
121
- }
122
- ```
123
-
124
- ### 步骤2:使用新方法
125
-
126
- ```javascript
127
- const schema = dsl({
128
- mobile: dsl('string!').phone('cn').phoneLocation('beijing')
129
- });
130
-
131
- validate(schema, { mobile: '13012345678' });
132
- ```
133
-
134
- ---
135
-
136
- ## 添加预定义模式
137
-
138
- ### 步骤1:创建模式文件
139
-
140
- 创建 `lib/config/patterns/custom.js`:
141
-
142
- ```javascript
143
- module.exports = {
144
- /**
145
- * 微信号验证
146
- */
147
- wechat: {
148
- pattern: /^[a-zA-Z]([a-zA-Z0-9_-]{5,19})$/,
149
- key: 'pattern.wechat',
150
- min: 6,
151
- max: 20
152
- },
153
-
154
- /**
155
- * QQ号验证
156
- */
157
- qq: {
158
- pattern: /^[1-9][0-9]{4,10}$/,
159
- key: 'pattern.qq',
160
- min: 5,
161
- max: 11
162
- }
163
- };
164
- ```
165
-
166
- ### 步骤2:导出模式
167
-
168
- `lib/config/patterns/index.js` 中:
169
-
170
- ```javascript
171
- module.exports = {
172
- // ...existing patterns...
173
- custom: require('./custom')
174
- };
175
- ```
176
-
177
- ### 步骤3:添加DslBuilder方法
178
-
179
- ```javascript
180
- /**
181
- * 微信号验证
182
- * @returns {DslBuilder}
183
- */
184
- wechat() {
185
- if (this._baseSchema.type !== 'string') {
186
- throw new Error('wechat() only applies to string type');
187
- }
188
- const config = patterns.custom.wechat;
189
- return this.pattern(config.pattern).messages({ 'pattern': config.key });
190
- }
191
- ```
192
-
193
- ### 步骤4:添加多语言
194
-
195
- ```javascript
196
- // lib/locales/zh-CN.js
197
- 'pattern.wechat': '{{#label}}必须是有效的微信号',
198
- 'pattern.qq': '{{#label}}必须是有效的QQ号'
199
- ```
200
-
201
- ---
202
-
203
- ## 多语言支持
204
-
205
- ### 添加新语言
206
-
207
- 1. **创建语言文件**
208
-
209
- 创建 `lib/locales/ko-KR.js`(韩语):
210
-
211
- ```javascript
212
- module.exports = {
213
- required: '{{#label}}은(는) 필수 항목입니다',
214
- type: '{{#label}}은(는) {{#expected}} 유형이어야 합니다',
215
- // ...其他73个键
216
- };
217
- ```
218
-
219
- 2. **配置加载**
220
-
221
- ```javascript
222
- dsl.config({
223
- i18n: path.join(__dirname, 'lib/locales')
224
- });
225
- ```
226
-
227
- 3. **使用新语言**
228
-
229
- ```javascript
230
- validate(schema, data, { locale: 'ko-KR' });
231
- ```
232
-
233
- ### 自定义错误消息
234
-
235
- ```javascript
236
- // 全局配置
237
- dsl.config({
238
- i18n: {
239
- 'zh-CN': {
240
- 'custom.emailTaken': '该邮箱已被注册',
241
- 'custom.invalidFormat': '格式不正确'
242
- }
243
- }
244
- });
245
-
246
- // 使用
247
- const schema = dsl({ email: 'email!' });
248
- schema.properties.email._customMessages = {
249
- 'format': 'custom.emailTaken'
250
- };
251
- ```
252
-
253
- ---
254
-
255
- ## 完整示例
256
-
257
- ### 示例1:银行卡号验证器
258
-
259
- ```javascript
260
- // 1. 添加AJV关键字
261
- static registerBankCard Validator(ajv) {
262
- ajv.addKeyword({
263
- keyword: 'bankCard',
264
- type: 'string',
265
- schemaType: 'boolean',
266
- validate: function validate(schema, cardNumber) {
267
- if (!schema) return true;
268
-
269
- // Luhn算法验证
270
- let sum = 0;
271
- let isEven = false;
272
-
273
- for (let i = cardNumber.length - 1; i >= 0; i--) {
274
- let digit = parseInt(cardNumber[i], 10);
275
-
276
- if (isEven) {
277
- digit *= 2;
278
- if (digit > 9) digit -= 9;
279
- }
280
-
281
- sum += digit;
282
- isEven = !isEven;
283
- }
284
-
285
- if (sum % 10 !== 0) {
286
- validate.errors = [{
287
- keyword: 'bankCard',
288
- message: 'pattern.bankCard',
289
- params: {}
290
- }];
291
- return false;
292
- }
293
-
294
- return true;
295
- },
296
- errors: true
297
- });
298
- }
299
-
300
- // 2. 添加DslBuilder方法
301
- bankCard() {
302
- if (this._baseSchema.type !== 'string') {
303
- throw new Error('bankCard() only applies to string type');
304
- }
305
- this._baseSchema.bankCard = true;
306
- return this;
307
- }
308
-
309
- // 3. 添加多语言
310
- 'pattern.bankCard': '{{#label}}必须是有效的银行卡号'
311
-
312
- // 4. 使用
313
- const schema = dsl({ cardNumber: dsl('string!').bankCard() });
314
- validate(schema, { cardNumber: '6222026006956145' });
315
- ```
316
-
317
- ### 示例2:IP段验证器
318
-
319
- ```javascript
320
- // 1. 添加AJV关键字
321
- static registerIPRange(ajv) {
322
- ajv.addKeyword({
323
- keyword: 'ipRange',
324
- type: 'string',
325
- schemaType: 'array', // [min, max]
326
- validate: function validate(range, ip) {
327
- const ipToNumber = (ip) => {
328
- return ip.split('.').reduce((acc, octet) => {
329
- return (acc << 8) + parseInt(octet, 10);
330
- }, 0);
331
- };
332
-
333
- const ipNum = ipToNumber(ip);
334
- const [minIP, maxIP] = range;
335
- const minNum = ipToNumber(minIP);
336
- const maxNum = ipToNumber(maxIP);
337
-
338
- if (ipNum < minNum || ipNum > maxNum) {
339
- validate.errors = [{
340
- keyword: 'ipRange',
341
- message: 'ip.range',
342
- params: { min: minIP, max: maxIP }
343
- }];
344
- return false;
345
- }
346
-
347
- return true;
348
- },
349
- errors: true
350
- });
351
- }
352
-
353
- // 2. 使用
354
- const schema = {
355
- type: 'string',
356
- format: 'ipv4',
357
- ipRange: ['192.168.1.1', '192.168.1.255']
358
- };
359
- ```
360
-
361
- ---
362
-
363
- ## 最佳实践
364
-
365
- ### 1. 命名规范
366
-
367
- - **关键字**:小驼峰,如 `phoneLocation`
368
- - **方法名**:小驼峰,如 `.phoneLocation()`
369
- - **消息键**:点分隔,如 `phone.location.mismatch`
370
-
371
- ### 2. 错误消息
372
-
373
- - 使用占位符:`{{#label}}`, `{{#limit}}`, `{{#expected}}`
374
- - 提供详细的错误信息
375
- - 支持多语言
376
-
377
- ### 3. 性能优化
378
-
379
- - 预编译正则表达式
380
- - 避免复杂的循环
381
- - 使用纯函数
382
-
383
- ### 4. 测试覆盖
384
-
385
- ```javascript
386
- describe('Custom Validator - bankCard', function() {
387
- it('应该验证有效的银行卡号', function() {
388
- const schema = dsl({ card: dsl('string!').bankCard() });
389
- expect(validate(schema, { card: '6222026006956145' }).valid).to.be.true;
390
- });
391
-
392
- it('应该拒绝无效的银行卡号', function() {
393
- const schema = dsl({ card: dsl('string!').bankCard() });
394
- expect(validate(schema, { card: '1234567890123456' }).valid).to.be.false;
395
- });
396
- });
397
- ```
398
-
399
- ---
400
-
401
- ## 参考资料
402
-
403
- - [AJV自定义关键字文档](https://ajv.js.org/guide/user-keywords.html)
404
- - [JSON Schema规范](https://json-schema.org/)
405
- - [schema-dsl API文档](./api-reference.md)
406
- - [验证指南](./validation-guide.md)
407
-
408
- ---
409
-
410
- **需要帮助?** 访问 [GitHub Issues](https://github.com/vextjs/schema-dsl/issues)
411
-
1
+ # 自定义扩展指南
2
+
3
+ > **版本**: 2.0.0-beta.2
4
+ > **更新日期**: 2026-05-08
5
+ > **用途**: 说明当前版本推荐的运行时扩展方式,以及在维护 schema-dsl 自身源码时如何继续深入扩展
6
+
7
+ ---
8
+
9
+ ## 📑 目录
10
+
11
+ - [快速开始](#快速开始)
12
+ - [添加自定义AJV关键字](#添加自定义ajv关键字)
13
+ - [扩展DslBuilder方法](#扩展dslbuilder方法)
14
+ - [添加预定义模式](#添加预定义模式)
15
+ - [多语言支持](#多语言支持)
16
+ - [完整示例](#完整示例)
17
+
18
+ ---
19
+
20
+ ## 快速开始
21
+
22
+ schema-dsl采用模块化设计,你可以轻松扩展:
23
+
24
+ 1. **`Validator.addKeyword()`** - 运行时注册自定义 AJV 关键字
25
+ 2. **`TypeRegistry.register()` / `DslBuilder.registerType()`** - 注册自定义 DSL 类型
26
+ 3. **`PluginManager` + `schema-dsl/plugins/*`** - 组合插件、hook 与官方插件入口
27
+ 4. **`Locale.addLocale()` / `dsl.config({ i18n })`** - 扩展多语言消息
28
+
29
+ ## 当前版本推荐路径
30
+
31
+ > ⚠️ 如果你是把 `schema-dsl` 当成依赖使用,优先通过公开运行时 API 扩展,而不是直接修改 `src/*`。
32
+ > 只有在你维护 `schema-dsl` 自身源码时,才需要继续阅读后面的“修改内部模块”类示例。
33
+
34
+ - 自定义关键字:优先用 `new Validator().addKeyword(name, definition)`
35
+ - 自定义类型:优先用 `TypeRegistry.register()` 或 `DslBuilder.registerType()`
36
+ - 官方插件:优先用 `PluginManager` 配合 `schema-dsl/plugins/custom-format`、`schema-dsl/plugins/custom-validator`、`schema-dsl/plugins/custom-type-example`
37
+ - 自定义语言:优先用 `Locale.addLocale()` 或 `dsl.config({ i18n: { locales } })`
38
+
39
+ ---
40
+
41
+ ## 添加自定义AJV关键字
42
+
43
+ ### 步骤1:通过公开 API 注册关键字
44
+
45
+ ```javascript
46
+ const { Validator } = require('schema-dsl');
47
+
48
+ const validator = new Validator();
49
+
50
+ validator.addKeyword('isPositive', {
51
+ type: 'number',
52
+ validate: (_schema, data) => data > 0
53
+ });
54
+
55
+ const result = validator.validate({ type: 'number', isPositive: true }, 42);
56
+ ```
57
+
58
+ ### 步骤2:需要复用时,再封装成插件
59
+
60
+ ```javascript
61
+ const plugin = {
62
+ name: 'my-validator-plugin',
63
+ install(core) {
64
+ const validator = new core.Validator();
65
+ validator.addKeyword('isPositive', {
66
+ type: 'number',
67
+ validate: (_schema, data) => data > 0
68
+ });
69
+ }
70
+ };
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 注册自定义 DSL 类型
76
+
77
+ ### 运行时推荐写法
78
+
79
+ ```javascript
80
+ const { DslBuilder, dsl } = require('schema-dsl');
81
+
82
+ DslBuilder.registerType('invoice-id', {
83
+ type: 'string',
84
+ pattern: '^INV-\\d{4}$'
85
+ });
86
+
87
+ const schema = dsl({ id: 'invoice-id!' });
88
+ ```
89
+
90
+ ### 低层入口
91
+
92
+ ```javascript
93
+ const { TypeRegistry } = require('schema-dsl');
94
+
95
+ TypeRegistry.register('evenNumber', {
96
+ baseSchema: { type: 'number', multipleOf: 2 }
97
+ });
98
+ ```
99
+
100
+ > 如果你要扩展 `schema-dsl` 自身源码,才需要继续修改 `DslBuilder` 内部方法或 parser/compiler 逻辑。
101
+
102
+ ---
103
+
104
+ ## 封装预定义模式
105
+
106
+ 当前版本更推荐用“自定义类型 + 现有约束”或“插件”来封装预定义模式,而不是直接要求业务侧修改包内 `src/config/patterns/*`。
107
+
108
+ ```typescript
109
+ import { DslBuilder } from 'schema-dsl';
110
+
111
+ DslBuilder.registerType('wechat-id', {
112
+ type: 'string',
113
+ pattern: '^[a-zA-Z]([a-zA-Z0-9_-]{5,19})$'
114
+ });
115
+ ```
116
+
117
+ ---
118
+
119
+ ## 多语言支持
120
+
121
+ ### 添加新语言
122
+
123
+ 1. **运行时追加语言**
124
+
125
+ ```typescript
126
+ import { Locale } from 'schema-dsl';
127
+
128
+ Locale.addLocale('ko-KR', {
129
+ required: '{{#label}}은(는) 필수 항목입니다',
130
+ type: '{{#label}}은(는) {{#expected}} 유형이어야 합니다'
131
+ });
132
+ ```
133
+
134
+ 2. **或通过配置对象集中注入**
135
+
136
+ ```javascript
137
+ dsl.config({
138
+ i18n: {
139
+ locales: {
140
+ 'ko-KR': {
141
+ required: '{{#label}}은(는) 필수 항목입니다'
142
+ }
143
+ }
144
+ }
145
+ });
146
+ ```
147
+
148
+ 3. **使用新语言**
149
+
150
+ ```javascript
151
+ validate(schema, data, { locale: 'ko-KR' });
152
+ ```
153
+
154
+ ### 自定义错误消息
155
+
156
+ ```javascript
157
+ // 全局配置
158
+ dsl.config({
159
+ i18n: {
160
+ 'zh-CN': {
161
+ 'custom.emailTaken': '该邮箱已被注册',
162
+ 'custom.invalidFormat': '格式不正确'
163
+ }
164
+ }
165
+ });
166
+
167
+ // 使用
168
+ const schema = dsl({ email: 'email!' });
169
+ schema.properties.email._customMessages = {
170
+ 'format': 'custom.emailTaken'
171
+ };
172
+ ```
173
+
174
+ ---
175
+
176
+ ## 完整示例
177
+
178
+ ### 示例1:银行卡号验证器
179
+
180
+ ```javascript
181
+ // 1. 添加AJV关键字
182
+ static registerBankCard Validator(ajv) {
183
+ ajv.addKeyword({
184
+ keyword: 'bankCard',
185
+ type: 'string',
186
+ schemaType: 'boolean',
187
+ validate: function validate(schema, cardNumber) {
188
+ if (!schema) return true;
189
+
190
+ // Luhn算法验证
191
+ let sum = 0;
192
+ let isEven = false;
193
+
194
+ for (let i = cardNumber.length - 1; i >= 0; i--) {
195
+ let digit = parseInt(cardNumber[i], 10);
196
+
197
+ if (isEven) {
198
+ digit *= 2;
199
+ if (digit > 9) digit -= 9;
200
+ }
201
+
202
+ sum += digit;
203
+ isEven = !isEven;
204
+ }
205
+
206
+ if (sum % 10 !== 0) {
207
+ validate.errors = [{
208
+ keyword: 'bankCard',
209
+ message: 'pattern.bankCard',
210
+ params: {}
211
+ }];
212
+ return false;
213
+ }
214
+
215
+ return true;
216
+ },
217
+ errors: true
218
+ });
219
+ }
220
+
221
+ // 2. 添加DslBuilder方法
222
+ bankCard() {
223
+ if (this._baseSchema.type !== 'string') {
224
+ throw new Error('bankCard() only applies to string type');
225
+ }
226
+ this._baseSchema.bankCard = true;
227
+ return this;
228
+ }
229
+
230
+ // 3. 添加多语言
231
+ 'pattern.bankCard': '{{#label}}必须是有效的银行卡号'
232
+
233
+ // 4. 使用
234
+ const schema = dsl({ cardNumber: dsl('string!').bankCard() });
235
+ validate(schema, { cardNumber: '6222026006956145' });
236
+ ```
237
+
238
+ ### 示例2:IP段验证器
239
+
240
+ ```javascript
241
+ // 1. 添加AJV关键字
242
+ static registerIPRange(ajv) {
243
+ ajv.addKeyword({
244
+ keyword: 'ipRange',
245
+ type: 'string',
246
+ schemaType: 'array', // [min, max]
247
+ validate: function validate(range, ip) {
248
+ const ipToNumber = (ip) => {
249
+ return ip.split('.').reduce((acc, octet) => {
250
+ return (acc << 8) + parseInt(octet, 10);
251
+ }, 0);
252
+ };
253
+
254
+ const ipNum = ipToNumber(ip);
255
+ const [minIP, maxIP] = range;
256
+ const minNum = ipToNumber(minIP);
257
+ const maxNum = ipToNumber(maxIP);
258
+
259
+ if (ipNum < minNum || ipNum > maxNum) {
260
+ validate.errors = [{
261
+ keyword: 'ipRange',
262
+ message: 'ip.range',
263
+ params: { min: minIP, max: maxIP }
264
+ }];
265
+ return false;
266
+ }
267
+
268
+ return true;
269
+ },
270
+ errors: true
271
+ });
272
+ }
273
+
274
+ // 2. 使用
275
+ const schema = {
276
+ type: 'string',
277
+ format: 'ipv4',
278
+ ipRange: ['192.168.1.1', '192.168.1.255']
279
+ };
280
+ ```
281
+
282
+ ---
283
+
284
+ ## 最佳实践
285
+
286
+ ### 1. 命名规范
287
+
288
+ - **关键字**:小驼峰,如 `phoneLocation`
289
+ - **方法名**:小驼峰,如 `.phoneLocation()`
290
+ - **消息键**:点分隔,如 `phone.location.mismatch`
291
+
292
+ ### 2. 错误消息
293
+
294
+ - 使用占位符:`{{#label}}`, `{{#limit}}`, `{{#expected}}`
295
+ - 提供详细的错误信息
296
+ - 支持多语言
297
+
298
+ ### 3. 性能优化
299
+
300
+ - 预编译正则表达式
301
+ - 避免复杂的循环
302
+ - 使用纯函数
303
+
304
+ ### 4. 测试覆盖
305
+
306
+ ```javascript
307
+ describe('Custom Validator - bankCard', function() {
308
+ it('应该验证有效的银行卡号', function() {
309
+ const schema = dsl({ card: dsl('string!').bankCard() });
310
+ expect(validate(schema, { card: '6222026006956145' }).valid).to.be.true;
311
+ });
312
+
313
+ it('应该拒绝无效的银行卡号', function() {
314
+ const schema = dsl({ card: dsl('string!').bankCard() });
315
+ expect(validate(schema, { card: '1234567890123456' }).valid).to.be.false;
316
+ });
317
+ });
318
+ ```
319
+
320
+ ---
321
+
322
+ ## 参考资料
323
+
324
+ - [AJV自定义关键字文档](https://ajv.js.org/guide/user-keywords.html)
325
+ - [JSON Schema规范](https://json-schema.org/)
326
+ - [schema-dsl API文档](./api-reference.md)
327
+ - [验证指南](./validation-guide.md)
328
+
329
+ ---
330
+
331
+ **需要帮助?** 访问 [GitHub Issues](https://github.com/vextjs/schema-dsl/issues)
332
+
333
+ ---
334
+
335
+ ## 对应示例文件
336
+
337
+ **示例入口**: [custom-extensions-guide.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/custom-extensions-guide.ts)
338
+ **说明**: 以运行时公开 API 为主,覆盖 `Validator.addKeyword()`、`DslBuilder.registerType()`、`Locale.addLocale()` 和官方插件入口四条扩展路径。
339
+