schema-dsl 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/.eslintignore +10 -0
  2. package/.eslintrc.json +27 -0
  3. package/.github/CODE_OF_CONDUCT.md +45 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +57 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +45 -0
  7. package/.github/ISSUE_TEMPLATE/question.md +31 -0
  8. package/.github/PULL_REQUEST_TEMPLATE.md +70 -0
  9. package/.github/SECURITY.md +184 -0
  10. package/.github/workflows/ci.yml +35 -0
  11. package/CHANGELOG.md +633 -0
  12. package/CONTRIBUTING.md +368 -0
  13. package/LICENSE +21 -0
  14. package/README.md +1122 -0
  15. package/STATUS.md +273 -0
  16. package/docs/FEATURE-INDEX.md +521 -0
  17. package/docs/INDEX.md +224 -0
  18. package/docs/api-reference.md +1098 -0
  19. package/docs/best-practices.md +672 -0
  20. package/docs/cache-manager.md +336 -0
  21. package/docs/design-philosophy.md +602 -0
  22. package/docs/dsl-syntax.md +654 -0
  23. package/docs/dynamic-locale.md +552 -0
  24. package/docs/error-handling.md +703 -0
  25. package/docs/export-guide.md +459 -0
  26. package/docs/faq.md +576 -0
  27. package/docs/frontend-i18n-guide.md +290 -0
  28. package/docs/i18n-user-guide.md +488 -0
  29. package/docs/label-vs-description.md +262 -0
  30. package/docs/markdown-exporter.md +398 -0
  31. package/docs/mongodb-exporter.md +279 -0
  32. package/docs/multi-type-support.md +319 -0
  33. package/docs/mysql-exporter.md +257 -0
  34. package/docs/plugin-system.md +542 -0
  35. package/docs/postgresql-exporter.md +290 -0
  36. package/docs/quick-start.md +761 -0
  37. package/docs/schema-helper.md +340 -0
  38. package/docs/schema-utils.md +492 -0
  39. package/docs/string-extensions.md +480 -0
  40. package/docs/troubleshooting.md +471 -0
  41. package/docs/type-converter.md +319 -0
  42. package/docs/type-reference.md +219 -0
  43. package/docs/validate.md +486 -0
  44. package/docs/validation-guide.md +484 -0
  45. package/examples/array-dsl-example.js +227 -0
  46. package/examples/custom-extension.js +85 -0
  47. package/examples/dsl-match-example.js +74 -0
  48. package/examples/dsl-style.js +118 -0
  49. package/examples/dynamic-locale-configuration.js +348 -0
  50. package/examples/dynamic-locale-example.js +287 -0
  51. package/examples/export-demo.js +130 -0
  52. package/examples/i18n-full-demo.js +310 -0
  53. package/examples/i18n-memory-safety.examples.js +268 -0
  54. package/examples/markdown-export.js +71 -0
  55. package/examples/middleware-usage.js +93 -0
  56. package/examples/password-reset/README.md +153 -0
  57. package/examples/password-reset/schema.js +26 -0
  58. package/examples/password-reset/test.js +101 -0
  59. package/examples/plugin-system.examples.js +205 -0
  60. package/examples/simple-example.js +122 -0
  61. package/examples/string-extensions.js +297 -0
  62. package/examples/user-registration/README.md +156 -0
  63. package/examples/user-registration/routes.js +92 -0
  64. package/examples/user-registration/schema.js +150 -0
  65. package/examples/user-registration/server.js +74 -0
  66. package/index.d.ts +1999 -0
  67. package/index.js +270 -0
  68. package/index.mjs +30 -0
  69. package/lib/adapters/DslAdapter.js +653 -0
  70. package/lib/adapters/index.js +20 -0
  71. package/lib/config/constants.js +286 -0
  72. package/lib/config/patterns/creditCard.js +9 -0
  73. package/lib/config/patterns/idCard.js +9 -0
  74. package/lib/config/patterns/index.js +8 -0
  75. package/lib/config/patterns/licensePlate.js +4 -0
  76. package/lib/config/patterns/passport.js +4 -0
  77. package/lib/config/patterns/phone.js +9 -0
  78. package/lib/config/patterns/postalCode.js +5 -0
  79. package/lib/core/CacheManager.js +376 -0
  80. package/lib/core/DslBuilder.js +740 -0
  81. package/lib/core/ErrorCodes.js +233 -0
  82. package/lib/core/ErrorFormatter.js +342 -0
  83. package/lib/core/JSONSchemaCore.js +347 -0
  84. package/lib/core/Locale.js +119 -0
  85. package/lib/core/MessageTemplate.js +89 -0
  86. package/lib/core/PluginManager.js +448 -0
  87. package/lib/core/StringExtensions.js +209 -0
  88. package/lib/core/Validator.js +316 -0
  89. package/lib/exporters/MarkdownExporter.js +420 -0
  90. package/lib/exporters/MongoDBExporter.js +162 -0
  91. package/lib/exporters/MySQLExporter.js +212 -0
  92. package/lib/exporters/PostgreSQLExporter.js +289 -0
  93. package/lib/exporters/index.js +24 -0
  94. package/lib/locales/en-US.js +65 -0
  95. package/lib/locales/es-ES.js +66 -0
  96. package/lib/locales/fr-FR.js +66 -0
  97. package/lib/locales/index.js +8 -0
  98. package/lib/locales/ja-JP.js +66 -0
  99. package/lib/locales/zh-CN.js +93 -0
  100. package/lib/utils/LRUCache.js +174 -0
  101. package/lib/utils/SchemaHelper.js +240 -0
  102. package/lib/utils/SchemaUtils.js +313 -0
  103. package/lib/utils/TypeConverter.js +245 -0
  104. package/lib/utils/index.js +13 -0
  105. package/lib/validators/CustomKeywords.js +203 -0
  106. package/lib/validators/index.js +11 -0
  107. package/package.json +70 -0
  108. package/plugins/custom-format.js +101 -0
  109. package/plugins/custom-validator.js +200 -0
@@ -0,0 +1,290 @@
1
+ # 前端动态切换语言 - 最佳实践指南
2
+
3
+
4
+ > **场景**: 前后端分离架构,前端需要动态切换验证错误消息语言
5
+
6
+ ---
7
+
8
+ ## 📋 目录
9
+
10
+ 1. [使用方法](#使用方法)
11
+ 2. [完整示例](#完整示例)
12
+ 3. [常见问题](#常见问题)
13
+
14
+ ---
15
+
16
+ ## 完整示例
17
+
18
+ ### 示例1:完整的 Express 应用
19
+
20
+ ```javascript
21
+ // server.js
22
+ const express = require('express');
23
+ const cors = require('cors');
24
+ const { Validator, dsl } = require('schema-dsl');
25
+
26
+ const app = express();
27
+ app.use(cors());
28
+ app.use(express.json());
29
+
30
+ // 语言中间件
31
+ app.use((req, res, next) => {
32
+ const acceptLanguage = req.headers['accept-language'] || 'en-US';
33
+ const locale = acceptLanguage.split(',')[0].trim();
34
+
35
+ req.validator = new Validator({
36
+ locale,
37
+ allErrors: true // 返回所有错误
38
+ });
39
+
40
+ next();
41
+ });
42
+
43
+ // Schema 定义
44
+ const schemas = {
45
+ user: dsl({
46
+ username: 'string:3-32!',
47
+ email: 'email!',
48
+ password: 'string:8-64!',
49
+ age: 'number:18-120',
50
+ phone: 'string'
51
+ }),
52
+
53
+ post: dsl({
54
+ title: 'string:1-200!',
55
+ content: 'string:10-10000!',
56
+ tags: 'array:1-5<string:1-20>'
57
+ })
58
+ };
59
+
60
+ // 通用验证端点
61
+ app.post('/api/validate/:type', (req, res) => {
62
+ const { type } = req.params;
63
+ const schema = schemas[type];
64
+
65
+ if (!schema) {
66
+ return res.status(404).json({ error: 'Schema not found' });
67
+ }
68
+
69
+ const result = req.validator.validate(schema, req.body);
70
+ res.json(result);
71
+ });
72
+
73
+ // 用户注册(带验证)
74
+ app.post('/api/register', (req, res) => {
75
+ const result = req.validator.validate(schemas.user, req.body);
76
+
77
+ if (!result.valid) {
78
+ return res.status(400).json({
79
+ success: false,
80
+ errors: result.errors
81
+ });
82
+ }
83
+
84
+ // 保存用户...
85
+ res.json({ success: true, message: '注册成功' });
86
+ });
87
+
88
+ app.listen(3000, () => {
89
+ console.log('Server running on http://localhost:3000');
90
+ });
91
+ ```
92
+
93
+ ### 示例2:Vue 3 前端
94
+
95
+ ```vue
96
+ <template>
97
+ <div class="validation-form">
98
+ <!-- 语言切换 -->
99
+ <div class="language-selector">
100
+ <button
101
+ v-for="lang in languages"
102
+ :key="lang.code"
103
+ :class="{ active: locale === lang.code }"
104
+ @click="locale = lang.code"
105
+ >
106
+ {{ lang.label }}
107
+ </button>
108
+ </div>
109
+
110
+ <!-- 表单 -->
111
+ <form @submit.prevent="handleSubmit">
112
+ <div class="form-group">
113
+ <label>用户名</label>
114
+ <input v-model="form.username" />
115
+ <span v-if="getError('username')" class="error">
116
+ {{ getError('username') }}
117
+ </span>
118
+ </div>
119
+
120
+ <div class="form-group">
121
+ <label>邮箱</label>
122
+ <input v-model="form.email" type="email" />
123
+ <span v-if="getError('email')" class="error">
124
+ {{ getError('email') }}
125
+ </span>
126
+ </div>
127
+
128
+ <div class="form-group">
129
+ <label>密码</label>
130
+ <input v-model="form.password" type="password" />
131
+ <span v-if="getError('password')" class="error">
132
+ {{ getError('password') }}
133
+ </span>
134
+ </div>
135
+
136
+ <button type="submit">提交</button>
137
+ </form>
138
+ </div>
139
+ </template>
140
+
141
+ <script setup>
142
+ import { ref, reactive } from 'vue';
143
+
144
+ const locale = ref('zh-CN');
145
+ const languages = [
146
+ { code: 'zh-CN', label: '中文' },
147
+ { code: 'en-US', label: 'English' },
148
+ { code: 'ja-JP', label: '日本語' }
149
+ ];
150
+
151
+ const form = reactive({
152
+ username: '',
153
+ email: '',
154
+ password: ''
155
+ });
156
+
157
+ const errors = ref([]);
158
+
159
+ const handleSubmit = async () => {
160
+ try {
161
+ const response = await fetch('http://localhost:3000/api/validate/user', {
162
+ method: 'POST',
163
+ headers: {
164
+ 'Content-Type': 'application/json',
165
+ 'Accept-Language': locale.value
166
+ },
167
+ body: JSON.stringify(form)
168
+ });
169
+
170
+ const result = await response.json();
171
+
172
+ if (!result.valid) {
173
+ errors.value = result.errors;
174
+ } else {
175
+ alert('验证通过!');
176
+ errors.value = [];
177
+ }
178
+ } catch (error) {
179
+ console.error('验证失败:', error);
180
+ }
181
+ };
182
+
183
+ const getError = (field) => {
184
+ const error = errors.value.find(e => e.path === field);
185
+ return error?.message;
186
+ };
187
+ </script>
188
+
189
+ <style scoped>
190
+ .error {
191
+ color: red;
192
+ font-size: 0.9em;
193
+ }
194
+
195
+ .language-selector button.active {
196
+ background: #007bff;
197
+ color: white;
198
+ }
199
+ </style>
200
+ ```
201
+
202
+ ---
203
+
204
+ ## 常见问题
205
+
206
+ ### Q1: 为什么不能直接使用 `Locale.setLocale()`?
207
+
208
+ **A**: 因为 Node.js 是单线程异步的,多个请求可能同时修改全局状态,导致语言混乱。
209
+
210
+ ```javascript
211
+ // ❌ 错误示例
212
+ app.post('/api/validate', (req, res) => {
213
+ Locale.setLocale('zh-CN'); // 全局修改
214
+ // 如果此时另一个请求设置了 'en-US',当前请求可能得到英文消息
215
+ const result = validate(schema, req.body);
216
+ res.json(result);
217
+ });
218
+ ```
219
+
220
+ ### Q2: 每次请求创建 Validator 实例会影响性能吗?
221
+
222
+ **A**: 不会。Validator 实例创建非常轻量,且验证器内部有编译缓存。
223
+
224
+ ```javascript
225
+ // 性能测试结果
226
+ // 创建实例: ~0.001ms
227
+ // 验证数据: ~0.1-1ms
228
+ // 总计: 可忽略不计
229
+ ```
230
+
231
+ ### Q3: 如何支持更多语言?
232
+
233
+ **A**: 使用 `Locale.addLocale()` 添加自定义语言包。
234
+
235
+ ```javascript
236
+ const { Locale } = require('schema-dsl');
237
+
238
+ Locale.addLocale('de-DE', {
239
+ required: '{{#label}} ist erforderlich',
240
+ 'format.email': '{{#label}} muss eine gültige E-Mail-Adresse sein'
241
+ // ... 更多消息
242
+ });
243
+ ```
244
+
245
+ ### Q4: 如何在前端缓存语言包?
246
+
247
+ **A**: 后端返回错误消息已经是本地化的,前端无需处理。如果需要前端验证:
248
+
249
+ ```javascript
250
+ // 前端可以使用相同的 SchemaIO(浏览器版)
251
+ import { dsl, validate } from 'schema-dsl/browser';
252
+
253
+ const schema = dsl({ /* ... */ });
254
+ const result = validate(schema, formData, {
255
+ locale: currentLocale
256
+ });
257
+ ```
258
+
259
+ ### Q5: 如何处理 Cookie 或 Session 中的语言?
260
+
261
+ ```javascript
262
+ // 中间件:优先级 Header > Cookie > Session > 默认
263
+ app.use((req, res, next) => {
264
+ const locale =
265
+ req.headers['accept-language'] ||
266
+ req.cookies?.locale ||
267
+ req.session?.locale ||
268
+ 'en-US';
269
+
270
+ req.validator = new Validator({ locale });
271
+ next();
272
+ });
273
+ ```
274
+
275
+ ---
276
+
277
+ ## 总结
278
+
279
+ ### ✅ 推荐做法
280
+
281
+ 1. **使用实例级配置**:每个请求创建独立 Validator
282
+ 2. **通过请求头传递语言**:符合 HTTP 标准
283
+ 3. **使用中间件统一处理**:提高代码复用性
284
+
285
+ ---
286
+
287
+ **相关文档**:
288
+ - [API 参考](api-reference.md)
289
+ - [最佳实践](best-practices.md)
290
+