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,310 @@
1
+ /**
2
+ * 完整的多语言配置示例
3
+ *
4
+ * 演示如何使用 dsl.config() 配置用户自定义语言包
5
+ * 以及如何在 Express 应用中实现动态语言切换
6
+ */
7
+
8
+ const express = require('express');
9
+ const { dsl, validate, Locale } = require('../index');
10
+ const path = require('path');
11
+
12
+ console.log('========== 多语言配置完整示例 ==========\n');
13
+
14
+ // ========================================
15
+ // 步骤 1:应用启动时配置
16
+ // ========================================
17
+ console.log('【步骤 1】配置用户语言包和缓存\n');
18
+
19
+ dsl.config({
20
+ // 用户语言包配置
21
+ i18n: {
22
+ // 方式 A:从目录加载(推荐用于大型项目)
23
+ // localesPath: path.join(__dirname, 'i18n/labels'),
24
+
25
+ // 方式 B:直接传入对象(推荐用于小型项目)
26
+ locales: {
27
+ 'zh-CN': {
28
+ // 字段标签
29
+ 'username': '用户名',
30
+ 'email': '邮箱地址',
31
+ 'password': '密码',
32
+ 'age': '年龄',
33
+ 'phone': '手机号',
34
+
35
+ // 嵌套字段
36
+ 'address.city': '城市',
37
+ 'address.street': '街道',
38
+
39
+ // 自定义错误消息
40
+ 'custom.invalidEmail': '邮箱格式不正确,请重新输入',
41
+ 'custom.emailTaken': '该邮箱已被注册',
42
+ 'custom.usernameTaken': '该用户名已被使用',
43
+ 'custom.passwordWeak': '密码强度不够,请包含大小写字母和数字'
44
+ },
45
+ 'en-US': {
46
+ // Field labels
47
+ 'username': 'Username',
48
+ 'email': 'Email Address',
49
+ 'password': 'Password',
50
+ 'age': 'Age',
51
+ 'phone': 'Phone Number',
52
+
53
+ // Nested fields
54
+ 'address.city': 'City',
55
+ 'address.street': 'Street',
56
+
57
+ // Custom error messages
58
+ 'custom.invalidEmail': 'Invalid email format, please try again',
59
+ 'custom.emailTaken': 'This email is already registered',
60
+ 'custom.usernameTaken': 'This username is already taken',
61
+ 'custom.passwordWeak': 'Password is too weak, please include uppercase, lowercase and numbers'
62
+ },
63
+ 'ja-JP': {
64
+ // フィールドラベル
65
+ 'username': 'ユーザー名',
66
+ 'email': 'メールアドレス',
67
+ 'password': 'パスワード',
68
+ 'age': '年齢',
69
+ 'phone': '電話番号',
70
+
71
+ // ネストされたフィールド
72
+ 'address.city': '都市',
73
+ 'address.street': '通り',
74
+
75
+ // カスタムエラーメッセージ
76
+ 'custom.invalidEmail': 'メール形式が正しくありません',
77
+ 'custom.emailTaken': 'このメールは既に登録されています',
78
+ 'custom.usernameTaken': 'このユーザー名は既に使用されています',
79
+ 'custom.passwordWeak': 'パスワードが弱すぎます'
80
+ }
81
+ }
82
+ },
83
+
84
+ // 缓存配置(可选,大型项目推荐)
85
+ cache: {
86
+ maxSize: 10000, // 大型项目:1万个 Schema
87
+ ttl: 7200000 // 2 小时
88
+ }
89
+ });
90
+
91
+ console.log('✅ 配置完成\n');
92
+
93
+ // ========================================
94
+ // 步骤 2:定义 Schema(使用 key 引用语言包)
95
+ // ========================================
96
+ console.log('【步骤 2】定义 Schema\n');
97
+
98
+ const userSchema = dsl({
99
+ username: 'string:3-32!'.label('username'),
100
+ email: 'email!'.label('email').messages({
101
+ 'format': 'custom.invalidEmail'
102
+ }),
103
+ password: 'string:8-32!'.label('password'),
104
+ age: 'number:18-120'.label('age'),
105
+ phone: 'string'.label('phone'),
106
+ address: dsl({
107
+ city: 'string!'.label('address.city'),
108
+ street: 'string!'.label('address.street')
109
+ })
110
+ });
111
+
112
+ console.log('✅ Schema 定义完成\n');
113
+
114
+ // ========================================
115
+ // 步骤 3:测试不同语言的验证
116
+ // ========================================
117
+ console.log('【步骤 3】测试多语言验证\n');
118
+
119
+ const testData = {
120
+ username: 'ab',
121
+ email: 'invalid-email',
122
+ password: '123',
123
+ age: 15,
124
+ address: {}
125
+ };
126
+
127
+ // 中文
128
+ console.log('--- 中文验证 ---');
129
+ let result = validate(userSchema, testData, { locale: 'zh-CN' });
130
+ console.log('有效:', result.valid);
131
+ console.log('错误:');
132
+ result.errors.forEach(err => {
133
+ console.log(` - ${err.path}: ${err.message}`);
134
+ });
135
+
136
+ console.log('\n--- 英文验证 ---');
137
+ result = validate(userSchema, testData, { locale: 'en-US' });
138
+ console.log('有效:', result.valid);
139
+ console.log('错误:');
140
+ result.errors.forEach(err => {
141
+ console.log(` - ${err.path}: ${err.message}`);
142
+ });
143
+
144
+ console.log('\n--- 日文验证 ---');
145
+ result = validate(userSchema, testData, { locale: 'ja-JP' });
146
+ console.log('有效:', result.valid);
147
+ console.log('错误:');
148
+ result.errors.forEach(err => {
149
+ console.log(` - ${err.path}: ${err.message}`);
150
+ });
151
+
152
+ console.log('\n========================================');
153
+ console.log('【步骤 4】Express 集成示例');
154
+ console.log('========================================\n');
155
+
156
+ // ========================================
157
+ // Express 应用示例
158
+ // ========================================
159
+
160
+ const app = express();
161
+ app.use(express.json());
162
+
163
+ // 中间件:提取语言参数
164
+ app.use((req, res, next) => {
165
+ // 优先级:URL 参数 > Accept-Language 头 > 默认
166
+ req.locale = req.query.lang ||
167
+ req.headers['accept-language'] ||
168
+ 'zh-CN';
169
+
170
+ // 只取主语言代码
171
+ if (req.locale.includes(',')) {
172
+ req.locale = req.locale.split(',')[0].trim();
173
+ }
174
+
175
+ next();
176
+ });
177
+
178
+ // API 路由:用户注册
179
+ app.post('/api/register', (req, res) => {
180
+ console.log(`\n[POST /api/register] 语言: ${req.locale}`);
181
+
182
+ // 使用全局 validate,传递 locale 参数
183
+ const result = validate(userSchema, req.body, {
184
+ locale: req.locale
185
+ });
186
+
187
+ if (!result.valid) {
188
+ return res.status(400).json({
189
+ success: false,
190
+ errors: result.errors
191
+ });
192
+ }
193
+
194
+ // 注册逻辑...
195
+ res.json({
196
+ success: true,
197
+ message: '注册成功'
198
+ });
199
+ });
200
+
201
+ // 健康检查
202
+ app.get('/health', (req, res) => {
203
+ res.json({ status: 'ok' });
204
+ });
205
+
206
+ // 启动服务器
207
+ const PORT = 3000;
208
+ const server = app.listen(PORT, () => {
209
+ console.log(`✅ Express 服务器启动成功`);
210
+ console.log(` 地址: http://localhost:${PORT}`);
211
+ console.log(`\n测试 API:`);
212
+ console.log(` curl -X POST http://localhost:${PORT}/api/register \\`);
213
+ console.log(` -H "Content-Type: application/json" \\`);
214
+ console.log(` -H "Accept-Language: zh-CN" \\`);
215
+ console.log(` -d '{"username":"ab","email":"bad"}'`);
216
+ console.log(`\n按 Ctrl+C 停止服务器\n`);
217
+ });
218
+
219
+ // 优雅关闭
220
+ process.on('SIGINT', () => {
221
+ console.log('\n\n正在关闭服务器...');
222
+ server.close(() => {
223
+ console.log('服务器已关闭');
224
+ process.exit(0);
225
+ });
226
+ });
227
+
228
+ // ========================================
229
+ // 前端集成示例(React)
230
+ // ========================================
231
+
232
+ console.log('========================================');
233
+ console.log('【步骤 5】前端集成示例(React)');
234
+ console.log('========================================\n');
235
+
236
+ const frontendExample = `
237
+ // src/api/validation.js
238
+ export async function validateUser(formData, locale = 'zh-CN') {
239
+ const response = await fetch('http://localhost:3000/api/register', {
240
+ method: 'POST',
241
+ headers: {
242
+ 'Content-Type': 'application/json',
243
+ 'Accept-Language': locale // ← 传递语言
244
+ },
245
+ body: JSON.stringify(formData)
246
+ });
247
+
248
+ return response.json();
249
+ }
250
+
251
+ // src/components/RegisterForm.jsx
252
+ import { useState } from 'react';
253
+ import { validateUser } from '../api/validation';
254
+
255
+ function RegisterForm() {
256
+ const [locale, setLocale] = useState('zh-CN');
257
+ const [errors, setErrors] = useState([]);
258
+
259
+ const handleSubmit = async (e) => {
260
+ e.preventDefault();
261
+
262
+ const formData = {
263
+ username: e.target.username.value,
264
+ email: e.target.email.value,
265
+ password: e.target.password.value,
266
+ age: Number(e.target.age.value)
267
+ };
268
+
269
+ const result = await validateUser(formData, locale);
270
+
271
+ if (!result.success) {
272
+ setErrors(result.errors);
273
+ } else {
274
+ alert('注册成功!');
275
+ }
276
+ };
277
+
278
+ return (
279
+ <div>
280
+ {/* 语言切换 */}
281
+ <select value={locale} onChange={(e) => setLocale(e.target.value)}>
282
+ <option value="zh-CN">中文</option>
283
+ <option value="en-US">English</option>
284
+ <option value="ja-JP">日本語</option>
285
+ </select>
286
+
287
+ <form onSubmit={handleSubmit}>
288
+ <input name="username" placeholder="用户名" />
289
+ <input name="email" type="email" placeholder="邮箱" />
290
+ <input name="password" type="password" placeholder="密码" />
291
+ <input name="age" type="number" placeholder="年龄" />
292
+ <button type="submit">注册</button>
293
+ </form>
294
+
295
+ {/* 错误显示 */}
296
+ {errors.map(err => (
297
+ <div key={err.path} style={{ color: 'red' }}>
298
+ {err.message} {/* 已经是对应语言 */}
299
+ </div>
300
+ ))}
301
+ </div>
302
+ );
303
+ }
304
+ `;
305
+
306
+ console.log('React 示例代码:');
307
+ console.log(frontendExample);
308
+
309
+ console.log('\n========== 示例完成 ==========');
310
+
@@ -0,0 +1,268 @@
1
+ /**
2
+ * i18n 内存安全示例
3
+ *
4
+ * 演示如何使用 LRU 缓存防止内存泄漏
5
+ * 正确实现:缓存完整的语言包对象,而非单个消息
6
+ *
7
+ * @version 2.2.1
8
+ */
9
+
10
+ const { Validator, Locale } = require('../index');
11
+ const LRUCache = require('../lib/utils/LRUCache');
12
+
13
+ console.log('========== i18n 内存安全示例 ==========\n');
14
+
15
+ // ========== 1. 问题:无限制缓存导致内存泄漏 ==========
16
+
17
+ console.log('❌ 问题场景:无限制语言包缓存');
18
+ console.log('-----------------------------------');
19
+
20
+ class UnsafeLocale {
21
+ static languagePackCache = new Map(); // ⚠️ 无容量限制
22
+
23
+ static getLanguagePack(locale) {
24
+ if (!this.languagePackCache.has(locale)) {
25
+ // 模拟加载整个语言包(包含所有错误消息)
26
+ const pack = this.loadLanguagePack(locale);
27
+ this.languagePackCache.set(locale, pack); // ⚠️ 永远不清理
28
+ }
29
+
30
+ return this.languagePackCache.get(locale);
31
+ }
32
+
33
+ static loadLanguagePack(locale) {
34
+ // 模拟加载完整语言包(10个错误类型)
35
+ const pack = {};
36
+ const errorTypes = ['required', 'minLength', 'maxLength', 'pattern', 'email',
37
+ 'url', 'type', 'enum', 'minimum', 'maximum'];
38
+ errorTypes.forEach(type => {
39
+ pack[type] = `[${locale}] ${type} error message`;
40
+ });
41
+ return pack;
42
+ }
43
+
44
+ static getMessage(locale, key) {
45
+ const pack = this.getLanguagePack(locale);
46
+ return pack[key] || `Unknown error: ${key}`;
47
+ }
48
+ }
49
+
50
+ // 模拟多租户场景:100个租户使用50种不同语言
51
+ console.log('\n模拟 100 个租户,使用 50 种不同语言...');
52
+ const locales = [];
53
+ for (let i = 1; i <= 50; i++) {
54
+ locales.push(`lang-${i}`);
55
+ }
56
+
57
+ for (let tenant = 1; tenant <= 100; tenant++) {
58
+ const locale = locales[tenant % 50]; // 每个租户使用不同语言
59
+ UnsafeLocale.getMessage(locale, 'required');
60
+ }
61
+
62
+ console.log(`缓存语言包数: ${UnsafeLocale.languagePackCache.size}`);
63
+ console.log('⚠️ 风险:50种语言 = 50个语言包缓存,无限增长');
64
+
65
+ // ========== 2. 解决方案:LRU 缓存语言包 ==========
66
+
67
+ console.log('\n\n✅ 解决方案:LRU 缓存(推荐)');
68
+ console.log('-----------------------------------');
69
+ console.log('关键:一个语言只有一个缓存(缓存完整语言包对象)\n');
70
+
71
+ class SafeLocale {
72
+ // ✅ 使用 LRU 缓存,最多缓存 10 个语言包
73
+ static languagePackCache = new LRUCache({
74
+ maxSize: 10,
75
+ enableStats: true
76
+ });
77
+
78
+ static getLanguagePack(locale) {
79
+ // 尝试从缓存获取
80
+ let pack = this.languagePackCache.get(locale);
81
+
82
+ if (!pack) {
83
+ // 缓存未命中,加载语言包
84
+ pack = this.loadLanguagePack(locale);
85
+ this.languagePackCache.set(locale, pack); // ✅ 自动清理最少使用的语言包
86
+ console.log(` [加载] 语言包: ${locale}`);
87
+ }
88
+
89
+ return pack;
90
+ }
91
+
92
+ static loadLanguagePack(locale) {
93
+ // 模拟加载完整语言包
94
+ const pack = {};
95
+ const errorTypes = ['required', 'minLength', 'maxLength', 'pattern', 'email',
96
+ 'url', 'type', 'enum', 'minimum', 'maximum'];
97
+ errorTypes.forEach(type => {
98
+ pack[type] = `[${locale}] ${type} error message`;
99
+ });
100
+ return pack;
101
+ }
102
+
103
+ static getMessage(locale, key) {
104
+ const pack = this.getLanguagePack(locale);
105
+ return pack[key] || `Unknown error: ${key}`;
106
+ }
107
+
108
+ static getStats() {
109
+ return this.languagePackCache.getStats();
110
+ }
111
+ }
112
+
113
+ // 模拟相同场景:100个租户使用50种语言
114
+ console.log('模拟 100 个租户,使用 50 种不同语言...');
115
+ for (let tenant = 1; tenant <= 100; tenant++) {
116
+ const locale = locales[tenant % 50];
117
+ SafeLocale.getMessage(locale, 'required');
118
+ }
119
+
120
+ const stats = SafeLocale.getStats();
121
+ console.log(`\n缓存语言包数: ${SafeLocale.languagePackCache.size} / 10 (maxSize)`);
122
+ console.log(`加载次数: ${stats.sets}`);
123
+ console.log(`命中次数: ${stats.hits}`);
124
+ console.log(`未命中次数: ${stats.misses}`);
125
+ console.log(`驱逐次数: ${stats.evictions}`);
126
+ const hitRate = stats.hits + stats.misses > 0
127
+ ? (stats.hits / (stats.hits + stats.misses) * 100).toFixed(2)
128
+ : '0.00';
129
+ console.log(`命中率: ${hitRate}%`);
130
+ console.log('✅ 优势:最多只缓存10个语言包,内存恒定可控');
131
+ console.log('💡 说明:50种语言但只缓存最常用的10种,冷门语言自动清理');
132
+
133
+ // ========== 3. 并发场景测试 ==========
134
+
135
+ console.log('\n\n🔄 并发场景测试');
136
+ console.log('-----------------------------------');
137
+
138
+ class ConcurrentSafeLocale {
139
+ static languagePackCache = new LRUCache({ maxSize: 5, enableStats: true });
140
+
141
+ static getLanguagePack(locale) {
142
+ let pack = this.languagePackCache.get(locale);
143
+ if (!pack) {
144
+ pack = { required: `[${locale}] required`, email: `[${locale}] email` };
145
+ this.languagePackCache.set(locale, pack);
146
+ }
147
+ return pack;
148
+ }
149
+ }
150
+
151
+ // 模拟并发请求:10个并发请求,使用不同语言
152
+ console.log('模拟 10 个并发请求...');
153
+ const concurrentRequests = [];
154
+ const testLocales = ['zh-CN', 'en-US', 'ja-JP', 'es-ES', 'fr-FR', 'ko-KR', 'de-DE', 'it-IT'];
155
+
156
+ for (let i = 0; i < 10; i++) {
157
+ const locale = testLocales[i % testLocales.length];
158
+ concurrentRequests.push(
159
+ Promise.resolve().then(() => {
160
+ const pack = ConcurrentSafeLocale.getLanguagePack(locale);
161
+ return { requestId: i + 1, locale, message: pack.required };
162
+ })
163
+ );
164
+ }
165
+
166
+ Promise.all(concurrentRequests).then(results => {
167
+ console.log('\n并发请求结果:');
168
+ results.forEach(r => {
169
+ console.log(` 请求${r.requestId}: ${r.locale} → ${r.message}`);
170
+ });
171
+
172
+ const concStats = ConcurrentSafeLocale.languagePackCache.getStats();
173
+ console.log(`\n缓存状态: ${ConcurrentSafeLocale.languagePackCache.size} 个语言包`);
174
+ console.log(`最多缓存: 5 个语言包 (maxSize)`);
175
+ console.log('✅ 验证:8种语言访问,只缓存最近使用的5种');
176
+
177
+ continueDemo();
178
+ }).catch(err => {
179
+ console.error('并发测试错误:', err);
180
+ continueDemo();
181
+ });
182
+
183
+ function continueDemo() {
184
+ // ========== 4. 内存监控示例 ==========
185
+
186
+ console.log('\n\n📊 内存监控示例');
187
+ console.log('-----------------------------------');
188
+
189
+ function formatBytes(bytes) {
190
+ return (bytes / 1024 / 1024).toFixed(2) + ' MB';
191
+ }
192
+
193
+ const memBefore = process.memoryUsage();
194
+ console.log(`初始内存: ${formatBytes(memBefore.heapUsed)}`);
195
+
196
+ // 创建大量语言包测试内存占用
197
+ const testCache = new LRUCache({ maxSize: 10 });
198
+ for (let i = 0; i < 1000; i++) {
199
+ // 模拟语言包对象(包含50个错误消息)
200
+ const languagePack = {};
201
+ for (let j = 0; j < 50; j++) {
202
+ languagePack[`error_${j}`] = `Language ${i} error message ${j}`;
203
+ }
204
+ testCache.set(`lang-${i}`, languagePack);
205
+ }
206
+
207
+ const memAfter = process.memoryUsage();
208
+ console.log(`处理后内存: ${formatBytes(memAfter.heapUsed)}`);
209
+ console.log(`内存增长: ${formatBytes(memAfter.heapUsed - memBefore.heapUsed)}`);
210
+ console.log(`缓存语言包: ${testCache.size} / 10 (maxSize)`);
211
+ console.log('✅ 验证:1000种语言加载,只缓存最近的10种');
212
+
213
+ // ========== 5. 最佳实践建议 ==========
214
+
215
+ console.log('\n\n📚 最佳实践建议');
216
+ console.log('-----------------------------------');
217
+ console.log(`
218
+ 🔑 核心原则:一个语言只有一个缓存(缓存完整语言包对象)
219
+
220
+ 1️⃣ 使用 LRU 缓存防止内存泄漏
221
+ ✅ 缓存语言包对象,不是单个消息
222
+ ✅ 设置合理的 maxSize(推荐 10-20种语言)
223
+ ✅ 启用统计功能监控命中率
224
+ ✅ 生产环境定期检查内存使用
225
+
226
+ 2️⃣ 正确的缓存策略
227
+ ✅ 缓存键:locale(如 "zh-CN")
228
+ ✅ 缓存值:完整语言包 { required: "...", minLength: "..." }
229
+ ❌ 错误:缓存 "zh-CN:required" 这样的组合键
230
+
231
+ 示例:
232
+ // ✅ 正确
233
+ cache.set('zh-CN', { required: '必填', email: '邮箱格式' });
234
+
235
+ // ❌ 错误(浪费内存)
236
+ cache.set('zh-CN:required', '必填');
237
+ cache.set('zh-CN:email', '邮箱格式');
238
+
239
+ 3️⃣ 多租户场景推荐方案
240
+ 选项 A:实例级配置(每个请求创建独立 Validator)⭐⭐⭐⭐⭐
241
+ 选项 B:请求级传参(validator.validate(data, { locale }))⭐⭐⭐⭐
242
+ 选项 C:中间件统一处理(req.locale)⭐⭐⭐
243
+
244
+ 4️⃣ 前端动态切换语言
245
+ ⚠️ 避免使用全局 Locale.setLocale()
246
+ ✅ 通过请求头传递 Accept-Language
247
+ ✅ 每个请求创建独立验证器实例
248
+
249
+ 5️⃣ 内存监控
250
+ • 开发环境:启用 enableStats 追踪缓存效率
251
+ • 生产环境:定期检查 process.memoryUsage()
252
+ • 告警阈值:堆内存超过容器限制的 80%
253
+
254
+ 6️⃣ 性能优化
255
+ • 热门语言保持在缓存中(中文、英文)
256
+ • 冷门语言按需加载,自动驱逐
257
+ • 命中率目标:> 90%(取决于语言分布)
258
+ • maxSize 建议:支持语言数 × 0.5 ~ 1.0
259
+
260
+ 详细文档:
261
+ - docs/i18n-analysis.md - 完整架构分析
262
+ - docs/frontend-i18n-guide.md - 前端集成指南
263
+ `);
264
+
265
+ console.log('\n✅ i18n 内存安全示例完成!');
266
+ console.log('💡 提示:运行 npm test 验证所有功能');
267
+ }
268
+
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Markdown 导出器示例
3
+ *
4
+ * 演示如何将 Schema 导出为 Markdown 文档
5
+ */
6
+
7
+ const { dsl, exporters } = require('../index');
8
+
9
+ console.log('========== Markdown 导出器示例 ==========\n');
10
+
11
+ // 示例 1: 简单用户 Schema
12
+ console.log('【示例 1】用户注册 API\n');
13
+
14
+ const userSchema = dsl({
15
+ username: 'string:3-32!',
16
+ email: 'email!',
17
+ age: 'number:18-120',
18
+ role: 'admin|user|guest'
19
+ });
20
+
21
+ const zhMarkdown = exporters.MarkdownExporter.export(userSchema, {
22
+ title: '用户注册 API',
23
+ locale: 'zh-CN',
24
+ includeExample: true
25
+ });
26
+
27
+ console.log(zhMarkdown);
28
+ console.log('\n' + '='.repeat(60) + '\n');
29
+
30
+ // 示例 2: 带标签的 Schema
31
+ console.log('【示例 2】带标签的产品 Schema\n');
32
+
33
+ const productSchema = dsl({
34
+ 'name': 'string:1-100!'.label('产品名称'),
35
+ 'price': 'number:0.01-!'.label('价格'),
36
+ 'description': 'string:500'.label('产品描述'),
37
+ 'category': 'electronics|clothing|books'.label('类别'),
38
+ 'tags': 'array:1-10<string:1-20>'.label('标签')
39
+ });
40
+
41
+ const productMarkdown = exporters.MarkdownExporter.export(productSchema, {
42
+ title: '产品信息 Schema',
43
+ locale: 'zh-CN'
44
+ });
45
+
46
+ console.log(productMarkdown);
47
+ console.log('\n' + '='.repeat(60) + '\n');
48
+
49
+ // 示例 3: 英文文档
50
+ console.log('【示例 3】英文文档\n');
51
+
52
+ const enMarkdown = exporters.MarkdownExporter.export(userSchema, {
53
+ title: 'User Registration API',
54
+ locale: 'en-US',
55
+ includeExample: true
56
+ });
57
+
58
+ console.log(enMarkdown);
59
+ console.log('\n' + '='.repeat(60) + '\n');
60
+
61
+ // 示例 4: 日文文档
62
+ console.log('【示例 4】日文文档\n');
63
+
64
+ const jaMarkdown = exporters.MarkdownExporter.export(userSchema, {
65
+ title: 'ユーザー登録 API',
66
+ locale: 'ja-JP',
67
+ includeExample: true
68
+ });
69
+
70
+ console.log(jaMarkdown);
71
+