schema-dsl 1.0.0 → 1.0.3

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 (44) hide show
  1. package/CHANGELOG.md +218 -542
  2. package/README.md +772 -903
  3. package/STATUS.md +97 -2
  4. package/docs/INDEX.md +1 -2
  5. package/docs/api-reference.md +1 -292
  6. package/docs/custom-extensions-guide.md +411 -0
  7. package/docs/enum.md +475 -0
  8. package/docs/i18n.md +394 -0
  9. package/docs/performance-benchmark-report.md +179 -0
  10. package/docs/plugin-system.md +8 -8
  11. package/docs/validate-async.md +1 -1
  12. package/docs/validation-rules-v1.0.2.md +1601 -0
  13. package/examples/README.md +81 -0
  14. package/examples/enum.examples.js +324 -0
  15. package/examples/express-integration.js +54 -54
  16. package/examples/i18n-full-demo.js +15 -24
  17. package/examples/schema-utils-chaining.examples.js +2 -2
  18. package/examples/slug.examples.js +179 -0
  19. package/index.d.ts +5 -5
  20. package/index.js +30 -34
  21. package/lib/config/constants.js +1 -1
  22. package/lib/config/patterns/common.js +47 -0
  23. package/lib/config/patterns/index.js +2 -1
  24. package/lib/core/DslBuilder.js +500 -8
  25. package/lib/core/StringExtensions.js +31 -0
  26. package/lib/core/Validator.js +42 -15
  27. package/lib/errors/ValidationError.js +3 -3
  28. package/lib/locales/en-US.js +79 -19
  29. package/lib/locales/es-ES.js +60 -19
  30. package/lib/locales/fr-FR.js +84 -43
  31. package/lib/locales/ja-JP.js +83 -42
  32. package/lib/locales/zh-CN.js +32 -0
  33. package/lib/validators/CustomKeywords.js +405 -0
  34. package/package.json +1 -1
  35. package/.github/CODE_OF_CONDUCT.md +0 -45
  36. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -57
  37. package/.github/ISSUE_TEMPLATE/config.yml +0 -11
  38. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -45
  39. package/.github/ISSUE_TEMPLATE/question.md +0 -31
  40. package/.github/PULL_REQUEST_TEMPLATE.md +0 -70
  41. package/.github/SECURITY.md +0 -184
  42. package/.github/workflows/ci.yml +0 -33
  43. package/plugins/custom-format.js +0 -101
  44. package/plugins/custom-validator.js +0 -200
@@ -14,26 +14,24 @@ console.log('========== 多语言配置完整示例 ==========\n');
14
14
  // ========================================
15
15
  // 步骤 1:应用启动时配置
16
16
  // ========================================
17
- console.log('【步骤 1】配置用户语言包和缓存\n');
17
+ console.log('【步骤 1】配置用户语言包\n');
18
18
 
19
19
  dsl.config({
20
- // 用户语言包配置
20
+ // 方式 A:从目录加载(推荐用于大型项目)
21
+ // i18n: path.join(__dirname, 'i18n/dsl')
22
+
23
+ // 方式 B:直接传入对象(推荐用于小型项目)
21
24
  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': '城市',
25
+ 'zh-CN': {
26
+ // 字段标签
27
+ 'username': '用户名',
28
+ 'email': '邮箱地址',
29
+ 'password': '密码',
30
+ 'age': '年龄',
31
+ 'phone': '手机号',
32
+
33
+ // 嵌套字段
34
+ 'address.city': '城市',
37
35
  'address.street': '街道',
38
36
 
39
37
  // 自定义错误消息
@@ -79,13 +77,6 @@ dsl.config({
79
77
  'custom.passwordWeak': 'パスワードが弱すぎます'
80
78
  }
81
79
  }
82
- },
83
-
84
- // 缓存配置(可选,大型项目推荐)
85
- cache: {
86
- maxSize: 10000, // 大型项目:1万个 Schema
87
- ttl: 7200000 // 2 小时
88
- }
89
80
  });
90
81
 
91
82
  console.log('✅ 配置完成\n');
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * SchemaUtils 核心方法示例
3
3
  *
4
- * 展示 v2.1.0 简化后的核心 4 个方法:
4
+ * 展示 v1.0.3 简化后的核心 4 个方法:
5
5
  * 1. omit() - 排除字段
6
6
  * 2. pick() - 保留字段
7
7
  * 3. partial() - 部分验证
8
8
  * 4. extend() - 扩展字段
9
9
  *
10
- * @version 2.1.0 (简化版)
10
+ * @version 1.0.3 (简化版)
11
11
  * @date 2025-12-29
12
12
  */
13
13
 
@@ -0,0 +1,179 @@
1
+ /**
2
+ * slug 类型验证示例
3
+ *
4
+ * slug 用于 URL 友好的字符串,只能包含:
5
+ * - 小写字母 (a-z)
6
+ * - 数字 (0-9)
7
+ * - 连字符 (-)
8
+ *
9
+ * 格式规则:
10
+ * - 必须以字母或数字开头
11
+ * - 必须以字母或数字结尾
12
+ * - 中间可以有连字符,但不能连续
13
+ */
14
+
15
+ const { dsl, validate } = require('../index');
16
+
17
+ // ========== 示例 1: 基础用法 ==========
18
+ console.log('\n========== 示例 1: 基础 slug 验证 ==========');
19
+
20
+ const schema1 = dsl({
21
+ slug: 'slug!'
22
+ });
23
+
24
+ console.log('✅ 有效的 slug:');
25
+ console.log(' my-blog-post:', validate(schema1, { slug: 'my-blog-post' }).valid);
26
+ console.log(' hello-world:', validate(schema1, { slug: 'hello-world' }).valid);
27
+ console.log(' post-123:', validate(schema1, { slug: 'post-123' }).valid);
28
+ console.log(' article:', validate(schema1, { slug: 'article' }).valid);
29
+
30
+ console.log('\n❌ 无效的 slug:');
31
+ console.log(' My-Blog-Post:', validate(schema1, { slug: 'My-Blog-Post' }).valid); // 大写
32
+ console.log(' hello_world:', validate(schema1, { slug: 'hello_world' }).valid); // 下划线
33
+ console.log(' -hello:', validate(schema1, { slug: '-hello' }).valid); // 开头连字符
34
+ console.log(' hello-:', validate(schema1, { slug: 'hello-' }).valid); // 结尾连字符
35
+ console.log(' hello--world:', validate(schema1, { slug: 'hello--world' }).valid); // 连续连字符
36
+ console.log(' hello world:', validate(schema1, { slug: 'hello world' }).valid); // 空格
37
+
38
+ // ========== 示例 2: DSL 字符串语法 ==========
39
+ console.log('\n========== 示例 2: DSL 字符串语法 ==========');
40
+
41
+ const schema2 = dsl({
42
+ articleSlug: 'slug:3-100!' // slug + 长度限制
43
+ });
44
+
45
+ console.log('✅ 3-100字符的 slug:');
46
+ console.log(' abc:', validate(schema2, { articleSlug: 'abc' }).valid);
47
+ console.log(' my-long-article-title-with-many-words:',
48
+ validate(schema2, { articleSlug: 'my-long-article-title-with-many-words' }).valid);
49
+
50
+ console.log('\n❌ 长度不符:');
51
+ console.log(' ab:', validate(schema2, { articleSlug: 'ab' }).valid); // 太短
52
+
53
+ // ========== 示例 3: 链式调用 ==========
54
+ console.log('\n========== 示例 3: 链式调用 ==========');
55
+
56
+ const schema3 = dsl({
57
+ pageSlug: 'string!'.slug().label('页面别名')
58
+ });
59
+
60
+ console.log('✅ 链式调用验证:');
61
+ console.log(' about-us:', validate(schema3, { pageSlug: 'about-us' }).valid);
62
+ console.log(' contact:', validate(schema3, { pageSlug: 'contact' }).valid);
63
+
64
+ // ========== 示例 4: 实际应用场景 ==========
65
+ console.log('\n========== 示例 4: 博客文章Schema ==========');
66
+
67
+ const blogPostSchema = dsl({
68
+ title: 'string:1-200!',
69
+ slug: 'slug:3-100!',
70
+ author: 'string!',
71
+ content: 'string:10-!',
72
+ tags: 'array<slug>', // slug 数组
73
+ publishedAt: 'datetime'
74
+ });
75
+
76
+ const validPost = {
77
+ title: 'Getting Started with Node.js',
78
+ slug: 'getting-started-with-nodejs',
79
+ author: 'John Doe',
80
+ content: 'This is a comprehensive guide to Node.js...',
81
+ tags: ['nodejs', 'javascript', 'backend'],
82
+ publishedAt: '2025-12-31T10:00:00Z'
83
+ };
84
+
85
+ console.log('✅ 有效的博客文章:', validate(blogPostSchema, validPost).valid);
86
+
87
+ const invalidPost = {
88
+ title: 'Invalid Post',
89
+ slug: 'Invalid Slug!', // ❌ 包含大写和特殊字符
90
+ author: 'Jane',
91
+ content: 'Short content',
92
+ tags: ['Node.js', 'JavaScript'], // ❌ 标签包含大写
93
+ publishedAt: '2025-12-31T10:00:00Z'
94
+ };
95
+
96
+ const result = validate(blogPostSchema, invalidPost);
97
+ console.log('\n❌ 无效的博客文章:', result.valid);
98
+ if (!result.valid) {
99
+ console.log('错误信息:');
100
+ result.errors.forEach(err => {
101
+ console.log(` - ${err.path}: ${err.message}`);
102
+ });
103
+ }
104
+
105
+ // ========== 示例 5: URL 生成应用 ==========
106
+ console.log('\n========== 示例 5: 自动生成 slug ==========');
107
+
108
+ function generateSlug(title) {
109
+ return title
110
+ .toLowerCase()
111
+ .replace(/[^a-z0-9]+/g, '-') // 替换非字母数字为连字符
112
+ .replace(/^-+|-+$/g, ''); // 移除首尾连字符
113
+ }
114
+
115
+ const titles = [
116
+ 'Hello World!',
117
+ 'Getting Started with Node.js',
118
+ 'Top 10 JavaScript Tips & Tricks',
119
+ '2025年的技术趋势'
120
+ ];
121
+
122
+ const urlSchema = dsl({ slug: 'slug!' });
123
+
124
+ console.log('标题 → slug 转换:');
125
+ titles.forEach(title => {
126
+ const slug = generateSlug(title);
127
+ const isValid = validate(urlSchema, { slug }).valid;
128
+ console.log(` "${title}"`);
129
+ console.log(` → "${slug}" ${isValid ? '✅' : '❌'}`);
130
+ });
131
+
132
+ // ========== 示例 6: 多语言支持 ==========
133
+ console.log('\n========== 示例 6: 多语言错误消息 ==========');
134
+
135
+ const { Validator } = require('../index');
136
+ const validator = new Validator();
137
+
138
+ const schema6 = dsl({ slug: 'slug!' });
139
+
140
+ // 中文
141
+ const resultCN = validator.validate(schema6, { slug: 'Invalid Slug!' }, { locale: 'zh-CN' });
142
+ console.log('中文错误:', resultCN.errors[0]?.message);
143
+
144
+ // 英文
145
+ const resultEN = validator.validate(schema6, { slug: 'Invalid Slug!' }, { locale: 'en-US' });
146
+ console.log('英文错误:', resultEN.errors[0]?.message);
147
+
148
+ // 西班牙语
149
+ const resultES = validator.validate(schema6, { slug: 'Invalid Slug!' }, { locale: 'es-ES' });
150
+ console.log('西班牙语错误:', resultES.errors[0]?.message);
151
+
152
+ // ========== 示例 7: 常见错误 ==========
153
+ console.log('\n========== 示例 7: 常见 slug 错误 ==========');
154
+
155
+ const testCases = [
156
+ { slug: 'valid-slug-123', expected: true, reason: '✅ 正确格式' },
157
+ { slug: 'Valid-Slug', expected: false, reason: '❌ 包含大写字母' },
158
+ { slug: 'hello_world', expected: false, reason: '❌ 包含下划线' },
159
+ { slug: 'hello world', expected: false, reason: '❌ 包含空格' },
160
+ { slug: '-hello', expected: false, reason: '❌ 以连字符开头' },
161
+ { slug: 'hello-', expected: false, reason: '❌ 以连字符结尾' },
162
+ { slug: 'hello--world', expected: false, reason: '❌ 连续连字符' },
163
+ { slug: 'hello.world', expected: false, reason: '❌ 包含点号' },
164
+ { slug: '123-456', expected: true, reason: '✅ 纯数字+连字符' },
165
+ { slug: 'a', expected: true, reason: '✅ 单个字母' },
166
+ { slug: '1', expected: true, reason: '✅ 单个数字' }
167
+ ];
168
+
169
+ const testSchema = dsl({ slug: 'slug!' });
170
+
171
+ console.log('测试用例:');
172
+ testCases.forEach(({ slug, expected, reason }) => {
173
+ const result = validate(testSchema, { slug });
174
+ const passed = result.valid === expected;
175
+ console.log(` ${passed ? '✅' : '❌'} "${slug}" - ${reason}`);
176
+ });
177
+
178
+ console.log('\n========== 所有示例完成 ==========');
179
+
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- // Type definitions for SchemaIO v2.1.2
2
- // Project: https://github.com/schema-dsl/schema-dsl
3
- // Definitions by: SchemaIO Team
1
+ // Type definitions for schema-dsl v1.0.3
2
+ // Project: https://github.com/vextjs/schema-dsl
3
+ // Definitions by: schema-dsl Team
4
4
 
5
5
 
6
6
  declare module 'schema-dsl' {
@@ -130,9 +130,9 @@ declare module 'schema-dsl' {
130
130
  * ```
131
131
  */
132
132
  export interface ErrorMessages {
133
- /** 最小长度/最小值错误 (v2.1.2+: 推荐使用min代替minLength) */
133
+ /** 最小长度/最小值错误 (v1.0.3+: 推荐使用min代替minLength) */
134
134
  min?: string;
135
- /** 最大长度/最大值错误 (v2.1.2+: 推荐使用max代替maxLength) */
135
+ /** 最大长度/最大值错误 (v1.0.3+: 推荐使用max代替maxLength) */
136
136
  max?: string;
137
137
  /** 最小长度错误 (向后兼容,推荐使用min) */
138
138
  minLength?: string;
package/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * SchemaIO 2.0 - 主入口文件
2
+ * schema-dsl - 主入口文件
3
3
  *
4
4
  * 统一的DSL Builder Pattern
5
5
  * 简洁 + 强大 = 完美平衡
6
6
  *
7
7
  * @module schema-dsl
8
- * @version 2.0.0
8
+ * @version 1.0.3
9
9
  */
10
10
 
11
11
  // ========== 核心层 ==========
@@ -35,48 +35,44 @@ dsl.if = dsl.DslAdapter.if;
35
35
  * 全局配置
36
36
  * @param {Object} options - 配置选项
37
37
  * @param {Object} options.patterns - 验证规则扩展 (phone, idCard, creditCard)
38
- * @param {Object} options.phone - 手机号验证规则扩展 (兼容旧版)
38
+ * @param {string|Object} options.i18n - 多语言配置(目录路径或语言包对象)
39
39
  */
40
40
  dsl.config = function (options = {}) {
41
41
  const patterns = require('./lib/config/patterns');
42
42
 
43
- // 兼容旧版 options.phone
44
- if (options.phone) {
45
- Object.assign(patterns.phone, options.phone);
46
- }
47
-
48
- // 新版 options.patterns
43
+ // patterns 配置
49
44
  if (options.patterns) {
50
45
  if (options.patterns.phone) Object.assign(patterns.phone, options.patterns.phone);
51
46
  if (options.patterns.idCard) Object.assign(patterns.idCard, options.patterns.idCard);
52
47
  if (options.patterns.creditCard) Object.assign(patterns.creditCard, options.patterns.creditCard);
53
48
  }
54
49
 
55
- // 多语言支持 (v2.1.0)
56
- if (options.locales) {
57
- if (typeof options.locales === 'object') {
58
- Object.keys(options.locales).forEach(locale => {
59
- Locale.addLocale(locale, options.locales[locale]);
60
- });
61
- } else if (typeof options.locales === 'string') {
62
- // 支持传入目录路径
63
- try {
64
- const fs = require('fs');
65
- const path = require('path');
66
- if (fs.existsSync(options.locales) && fs.statSync(options.locales).isDirectory()) {
67
- const files = fs.readdirSync(options.locales);
68
- files.forEach(file => {
69
- if (file.endsWith('.js') || file.endsWith('.json')) {
70
- const localeName = path.basename(file, path.extname(file));
71
- const messages = require(path.resolve(options.locales, file));
72
- Locale.addLocale(localeName, messages);
73
- }
74
- });
75
- }
76
- } catch (e) {
77
- console.warn('[SchemaIO] Failed to load locales from path:', e.message);
50
+ // 多语言支持 (v1.0.1 优化)
51
+ if (options.i18n) {
52
+ // 方式 1: 传入目录路径(字符串)
53
+ if (typeof options.i18n === 'string') {
54
+ const fs = require('fs');
55
+ const path = require('path');
56
+
57
+ if (fs.existsSync(options.i18n) && fs.statSync(options.i18n).isDirectory()) {
58
+ const files = fs.readdirSync(options.i18n);
59
+ files.forEach(file => {
60
+ if (file.endsWith('.js') || file.endsWith('.json')) {
61
+ const localeName = path.basename(file, path.extname(file));
62
+ const messages = require(path.resolve(options.i18n, file));
63
+ Locale.addLocale(localeName, messages);
64
+ }
65
+ });
66
+ } else {
67
+ console.warn('[schema-dsl] i18n path does not exist:', options.i18n);
78
68
  }
79
69
  }
70
+ // 方式 2: 直接传入对象
71
+ else if (typeof options.i18n === 'object') {
72
+ Object.keys(options.i18n).forEach(locale => {
73
+ Locale.addLocale(locale, options.i18n[locale]);
74
+ });
75
+ }
80
76
  }
81
77
  };
82
78
 
@@ -154,7 +150,7 @@ installStringExtensions(dsl);
154
150
  * }
155
151
  * });
156
152
  */
157
- dsl.config = function(options = {}) {
153
+ dsl.config = function (options = {}) {
158
154
  // ========== 用户语言包配置 ==========
159
155
  if (options.i18n) {
160
156
  const { localesPath, locales } = options.i18n;
@@ -276,7 +272,7 @@ module.exports = {
276
272
  CONSTANTS,
277
273
 
278
274
  // 版本信息
279
- VERSION: '2.1.0'
275
+ VERSION: '1.0.3'
280
276
  };
281
277
 
282
278
 
@@ -236,7 +236,7 @@ module.exports = {
236
236
  DEFAULT_STYLE: 'joi',
237
237
 
238
238
  // API版本
239
- VERSION: '2.0.0'
239
+ VERSION: '1.0.3'
240
240
  },
241
241
 
242
242
  // ========== 插件配置 ==========
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 通用验证模式(v1.0.2新增)
3
+ *
4
+ * @module lib/config/patterns/common
5
+ */
6
+
7
+ module.exports = {
8
+ /**
9
+ * 域名验证
10
+ * 支持格式: example.com, sub.example.com
11
+ */
12
+ domain: {
13
+ pattern: /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i,
14
+ key: 'pattern.domain',
15
+ min: 3,
16
+ max: 253
17
+ },
18
+
19
+ /**
20
+ * IP地址验证(IPv4 或 IPv6)
21
+ * 支持格式: 192.168.1.1, 2001:0db8:85a3::8a2e:0370:7334
22
+ */
23
+ ip: {
24
+ // 同时支持IPv4和IPv6的复合正则
25
+ pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/,
26
+ key: 'pattern.ip'
27
+ },
28
+
29
+ /**
30
+ * Base64编码验证
31
+ * 支持标准Base64格式(带或不带padding)
32
+ */
33
+ base64: {
34
+ pattern: /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,
35
+ key: 'pattern.base64'
36
+ },
37
+
38
+ /**
39
+ * JWT令牌验证
40
+ * 支持格式: header.payload.signature
41
+ */
42
+ jwt: {
43
+ pattern: /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/,
44
+ key: 'pattern.jwt'
45
+ }
46
+ };
47
+
@@ -4,5 +4,6 @@ module.exports = {
4
4
  creditCard: require('./creditCard'),
5
5
  licensePlate: require('./licensePlate'),
6
6
  postalCode: require('./postalCode'),
7
- passport: require('./passport')
7
+ passport: require('./passport'),
8
+ common: require('./common') // v1.0.2新增
8
9
  };