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,287 @@
1
+ /**
2
+ * 动态多语言配置示例
3
+ *
4
+ * 演示如何从请求头动态获取语言并进行验证
5
+ */
6
+
7
+ const { dsl, Validator, Locale } = require('../index');
8
+
9
+ // ========== 1. 初始化语言包 ==========
10
+
11
+ console.log('📦 初始化语言包...\n');
12
+
13
+ // 中文语言包
14
+ Locale.addLocale('zh-CN', {
15
+ 'required': '{{#label}}不能为空',
16
+ 'min': '{{#label}}至少{{#limit}}个字符',
17
+ 'max': '{{#label}}最多{{#limit}}个字符',
18
+ 'pattern': '{{#label}}格式不正确',
19
+ 'format': '请输入有效的{{#label}}',
20
+
21
+ // Labels
22
+ 'label.username': '用户名',
23
+ 'label.email': '邮箱地址',
24
+ 'label.password': '密码',
25
+ 'label.age': '年龄'
26
+ });
27
+
28
+ // 英文语言包
29
+ Locale.addLocale('en-US', {
30
+ 'required': '{{#label}} is required',
31
+ 'min': '{{#label}} must be at least {{#limit}} characters',
32
+ 'max': '{{#label}} must be at most {{#limit}} characters',
33
+ 'pattern': '{{#label}} format is invalid',
34
+ 'format': 'Please enter a valid {{#label}}',
35
+
36
+ // Labels
37
+ 'label.username': 'Username',
38
+ 'label.email': 'Email',
39
+ 'label.password': 'Password',
40
+ 'label.age': 'Age'
41
+ });
42
+
43
+ console.log('✅ 语言包已加载: zh-CN, en-US\n');
44
+
45
+ // ========== 2. 定义Schema ==========
46
+
47
+ console.log('📋 定义Schema...\n');
48
+
49
+ const userSchema = dsl({
50
+ username: 'string:3-32!'.label('label.username'),
51
+ email: 'email!'.label('label.email'),
52
+ password: 'string:8-64!'
53
+ .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/)
54
+ .label('label.password'),
55
+ age: 'number:18-120'.label('label.age')
56
+ });
57
+
58
+ console.log('✅ Schema定义完成\n');
59
+
60
+ // ========== 3. 测试数据 ==========
61
+
62
+ const testData = {
63
+ username: 'ab', // 太短
64
+ email: 'invalid-email', // 格式错误
65
+ password: 'weak', // 不符合强度要求
66
+ age: 15 // 年龄不够
67
+ };
68
+
69
+ // ========== 4. 创建验证器 ==========
70
+
71
+ const validator = new Validator();
72
+
73
+ // ========== 5. 测试不同语言 ==========
74
+
75
+ console.log('🧪 测试验证 - 不同语言\n');
76
+ console.log('=' .repeat(60));
77
+
78
+ // 中文验证
79
+ console.log('\n📍 测试1: 中文错误消息 (locale: zh-CN)');
80
+ console.log('-'.repeat(60));
81
+
82
+ const result1 = validator.validate(userSchema, testData, {
83
+ locale: 'zh-CN'
84
+ });
85
+
86
+ console.log('验证结果:', result1.valid ? '✅ 通过' : '❌ 失败');
87
+ if (!result1.valid) {
88
+ console.log('\n错误列表:');
89
+ result1.errors.forEach((error, index) => {
90
+ console.log(` ${index + 1}. ${error.path}: ${error.message}`);
91
+ });
92
+ }
93
+
94
+ // 英文验证
95
+ console.log('\n📍 测试2: 英文错误消息 (locale: en-US)');
96
+ console.log('-'.repeat(60));
97
+
98
+ const result2 = validator.validate(userSchema, testData, {
99
+ locale: 'en-US'
100
+ });
101
+
102
+ console.log('验证结果:', result2.valid ? '✅ 通过' : '❌ 失败');
103
+ if (!result2.valid) {
104
+ console.log('\n错误列表:');
105
+ result2.errors.forEach((error, index) => {
106
+ console.log(` ${index + 1}. ${error.path}: ${error.message}`);
107
+ });
108
+ }
109
+
110
+ // ========== 6. 模拟HTTP请求场景 ==========
111
+
112
+ console.log('\n\n🌐 模拟HTTP请求场景');
113
+ console.log('=' .repeat(60));
114
+
115
+ /**
116
+ * 解析 Accept-Language 头
117
+ */
118
+ function parseAcceptLanguage(acceptLanguage) {
119
+ if (!acceptLanguage) return 'en-US';
120
+
121
+ const languages = acceptLanguage.split(',').map(lang => {
122
+ const [code, qValue] = lang.trim().split(';');
123
+ const q = qValue ? parseFloat(qValue.split('=')[1]) : 1.0;
124
+ return { code: code.trim(), q };
125
+ });
126
+
127
+ languages.sort((a, b) => b.q - a.q);
128
+
129
+ const supportedLocales = ['zh-CN', 'en-US'];
130
+ for (const lang of languages) {
131
+ const matched = supportedLocales.find(locale =>
132
+ locale.toLowerCase() === lang.code.toLowerCase()
133
+ );
134
+ if (matched) return matched;
135
+ }
136
+
137
+ return 'en-US';
138
+ }
139
+
140
+ /**
141
+ * 模拟验证请求
142
+ */
143
+ function handleRequest(acceptLanguage, data) {
144
+ const locale = parseAcceptLanguage(acceptLanguage);
145
+
146
+ console.log(`\n📍 请求头: Accept-Language: ${acceptLanguage}`);
147
+ console.log(` 解析语言: ${locale}`);
148
+ console.log('-'.repeat(60));
149
+
150
+ const result = validator.validate(userSchema, data, { locale });
151
+
152
+ if (!result.valid) {
153
+ console.log('❌ 验证失败:');
154
+ result.errors.forEach((error, index) => {
155
+ console.log(` ${index + 1}. ${error.path}: ${error.message}`);
156
+ });
157
+ } else {
158
+ console.log('✅ 验证通过');
159
+ }
160
+
161
+ return result;
162
+ }
163
+
164
+ // 测试不同的 Accept-Language 头
165
+ handleRequest('zh-CN,zh;q=0.9,en;q=0.8', testData);
166
+ handleRequest('en-US,en;q=0.9', testData);
167
+ handleRequest('zh-CN', testData);
168
+ handleRequest('en-US', testData);
169
+ handleRequest('ja-JP,en-US;q=0.8', testData); // 不支持的语言,回退到英文
170
+
171
+ // ========== 6.1 格式化类型测试 (v2.1.0 新增) ==========
172
+
173
+ console.log('\n\n🌐 格式化类型测试 (v2.1.0)');
174
+ console.log('=' .repeat(60));
175
+
176
+ const formatSchema = dsl({
177
+ email: 'email!',
178
+ url: 'url!',
179
+ ipv4: 'ipv4!'
180
+ });
181
+
182
+ const formatData = {
183
+ email: 'invalid-email',
184
+ url: 'invalid-url',
185
+ ipv4: '999.999.999.999'
186
+ };
187
+
188
+ function testFormat(locale) {
189
+ console.log(`\nTesting format with locale: ${locale}`);
190
+ const result = validator.validate(formatSchema, formatData, { locale });
191
+ result.errors.forEach(e => console.log(` ${e.path}: ${e.message}`));
192
+ }
193
+
194
+ testFormat('zh-CN');
195
+ testFormat('en-US');
196
+ testFormat('ja-JP'); // 如果已加载 ja-JP
197
+
198
+ // ========== 7. 并发测试 ==========
199
+
200
+ console.log('\n\n⚡ 并发测试 - 验证无竞态问题');
201
+ console.log('=' .repeat(60));
202
+
203
+ async function concurrentTest() {
204
+ const promises = [
205
+ // 5个中文请求
206
+ ...Array(5).fill().map(() =>
207
+ Promise.resolve(validator.validate(userSchema, testData, { locale: 'zh-CN' }))
208
+ ),
209
+ // 5个英文请求
210
+ ...Array(5).fill().map(() =>
211
+ Promise.resolve(validator.validate(userSchema, testData, { locale: 'en-US' }))
212
+ )
213
+ ];
214
+
215
+ const results = await Promise.all(promises);
216
+
217
+ // 检查结果
218
+ const zhResults = results.slice(0, 5);
219
+ const enResults = results.slice(5);
220
+
221
+ const allZhCorrect = zhResults.every(r =>
222
+ r.errors[0].message.includes('用户名')
223
+ );
224
+
225
+ const allEnCorrect = enResults.every(r =>
226
+ r.errors[0].message.includes('username')
227
+ );
228
+
229
+ console.log('\n并发测试结果:');
230
+ console.log(` 中文请求 (5个): ${allZhCorrect ? '✅ 全部正确' : '❌ 有错误'}`);
231
+ console.log(` 英文请求 (5个): ${allEnCorrect ? '✅ 全部正确' : '❌ 有错误'}`);
232
+
233
+ if (allZhCorrect && allEnCorrect) {
234
+ console.log('\n🎉 并发测试通过!无竞态问题!');
235
+ }
236
+ }
237
+
238
+ concurrentTest();
239
+
240
+ // ========== 8. 正确数据测试 ==========
241
+
242
+ console.log('\n\n✅ 测试正确数据');
243
+ console.log('=' .repeat(60));
244
+
245
+ const validData = {
246
+ username: 'john_doe',
247
+ email: 'john@example.com',
248
+ password: 'Password123',
249
+ age: 25
250
+ };
251
+
252
+ console.log('\n📍 中文验证');
253
+ const validResult1 = validator.validate(userSchema, validData, {
254
+ locale: 'zh-CN'
255
+ });
256
+ console.log('结果:', validResult1.valid ? '✅ 验证通过' : '❌ 验证失败');
257
+
258
+ console.log('\n📍 英文验证');
259
+ const validResult2 = validator.validate(userSchema, validData, {
260
+ locale: 'en-US'
261
+ });
262
+ console.log('结果:', validResult2.valid ? '✅ 验证通过' : '❌ 验证失败');
263
+
264
+ // ========== 总结 ==========
265
+
266
+ console.log('\n\n' + '='.repeat(60));
267
+ console.log('📊 总结');
268
+ console.log('='.repeat(60));
269
+ console.log(`
270
+ ✅ 核心功能:
271
+ 1. 支持动态语言切换 (locale参数)
272
+ 2. 支持并发请求,无竞态问题
273
+ 3. 自动解析 Accept-Language 头
274
+ 4. 支持多语言错误消息
275
+
276
+ ✅ 使用方式:
277
+ validator.validate(schema, data, { locale: 'zh-CN' })
278
+
279
+ ✅ 推荐场景:
280
+ - Express/Koa API 服务
281
+ - 多语言Web应用
282
+ - 国际化移动应用后端
283
+
284
+ 📖 详细文档: docs/dynamic-locale.md
285
+ `);
286
+
287
+
@@ -0,0 +1,130 @@
1
+ /**
2
+ * 数据库导出示例
3
+ *
4
+ * 演示如何将JSON Schema导出为MongoDB、MySQL、PostgreSQL Schema
5
+ * 包含v2.0.1 String扩展特性
6
+ */
7
+
8
+ const { dsl, exporters } = require('../index');
9
+
10
+ // ========== 1. 定义用户Schema(使用String扩展)==========
11
+
12
+ const userSchema = dsl({
13
+ id: 'string!',
14
+ // ✨ String扩展:username带正则验证
15
+ username: 'string:3-32!'
16
+ .pattern(/^[a-zA-Z0-9_]+$/)
17
+ .label('用户名'),
18
+ // ✨ String扩展:email带标签
19
+ email: 'email!'
20
+ .label('邮箱地址'),
21
+ password: 'string:8-64!',
22
+ age: 'number:18-120',
23
+ gender: 'male|female|other',
24
+ status: 'active|inactive|pending',
25
+ role: 'user|admin|moderator',
26
+ createdAt: 'date!',
27
+ updatedAt: 'date!',
28
+ profile: {
29
+ bio: 'string:500',
30
+ // ✨ String扩展:website带描述
31
+ website: 'url'.description('个人主页'),
32
+ avatar: 'url'.label('头像URL'),
33
+ location: 'string:100'
34
+ },
35
+ preferences: {
36
+ language: 'en|zh|ja|ko',
37
+ theme: 'light|dark|auto',
38
+ emailNotifications: 'boolean',
39
+ smsNotifications: 'boolean'
40
+ }
41
+ });
42
+
43
+ console.log('========== 用户Schema(JSON Schema格式) ==========');
44
+ console.log(JSON.stringify(userSchema, null, 2));
45
+
46
+ // ========== 2. 导出为MongoDB Schema ==========
47
+
48
+ console.log('\n========== MongoDB验证Schema ==========');
49
+ const mongoExporter = new exporters.MongoDBExporter({ strict: true });
50
+ const mongoSchema = mongoExporter.export(userSchema);
51
+ console.log(JSON.stringify(mongoSchema, null, 2));
52
+
53
+ // 生成MongoDB命令
54
+ console.log('\n========== MongoDB创建集合命令 ==========');
55
+ const mongoCommand = mongoExporter.generateCommand('users', userSchema);
56
+ console.log(mongoCommand);
57
+
58
+ // ========== 3. 导出为MySQL DDL ==========
59
+
60
+ console.log('\n========== MySQL CREATE TABLE ==========');
61
+ const mysqlExporter = new exporters.MySQLExporter({
62
+ engine: 'InnoDB',
63
+ charset: 'utf8mb4',
64
+ collate: 'utf8mb4_unicode_ci'
65
+ });
66
+ const mysqlDDL = mysqlExporter.export('users', userSchema);
67
+ console.log(mysqlDDL);
68
+
69
+ // 生成索引
70
+ console.log('\n========== MySQL索引 ==========');
71
+ console.log(mysqlExporter.generateIndex('users', 'username', { unique: true }));
72
+ console.log(mysqlExporter.generateIndex('users', 'email', { unique: true }));
73
+ console.log(mysqlExporter.generateIndex('users', 'status'));
74
+
75
+ // ========== 4. 导出为PostgreSQL DDL ==========
76
+
77
+ console.log('\n========== PostgreSQL CREATE TABLE ==========');
78
+ const pgExporter = new exporters.PostgreSQLExporter({ schema: 'public' });
79
+ const pgDDL = pgExporter.export('users', userSchema);
80
+ console.log(pgDDL);
81
+
82
+ // 生成索引
83
+ console.log('\n========== PostgreSQL索引 ==========');
84
+ console.log(pgExporter.generateIndex('users', 'username', { unique: true, method: 'btree' }));
85
+ console.log(pgExporter.generateIndex('users', 'email', { unique: true, method: 'btree' }));
86
+ console.log(pgExporter.generateIndex('users', 'status', { method: 'hash' }));
87
+
88
+ // ========== 5. 多表导出示例 ==========
89
+
90
+ console.log('\n========== 多表导出示例 ==========');
91
+
92
+ // 文章表
93
+ const articleSchema = dsl({
94
+ id: 'string!',
95
+ title: 'string:1-200!',
96
+ content: 'string!',
97
+ authorId: 'string!',
98
+ categoryId: 'string!',
99
+ status: 'draft|published|archived',
100
+ tags: 'array<string:1-50>',
101
+ viewCount: 'number',
102
+ likeCount: 'number',
103
+ createdAt: 'date!',
104
+ updatedAt: 'date!'
105
+ });
106
+
107
+ // 评论表
108
+ const commentSchema = dsl({
109
+ id: 'string!',
110
+ articleId: 'string!',
111
+ userId: 'string!',
112
+ content: 'string:1-500!',
113
+ parentId: 'string',
114
+ status: 'pending|approved|rejected',
115
+ createdAt: 'date!'
116
+ });
117
+
118
+ console.log('\n--- MySQL多表DDL ---');
119
+ console.log(mysqlExporter.export('articles', articleSchema));
120
+ console.log('\n');
121
+ console.log(mysqlExporter.export('comments', commentSchema));
122
+
123
+ console.log('\n--- PostgreSQL多表DDL ---');
124
+ console.log(pgExporter.export('articles', articleSchema));
125
+ console.log('\n');
126
+ console.log(pgExporter.export('comments', commentSchema));
127
+
128
+ console.log('\n✅ 数据库导出示例完成!');
129
+
130
+