schema-dsl 2.0.0 → 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 (145) hide show
  1. package/CHANGELOG.md +130 -113
  2. package/LICENSE +21 -21
  3. package/README.md +628 -628
  4. package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
  5. package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
  6. package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
  7. package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
  8. package/dist/index.cjs +75 -29
  9. package/dist/index.d.cts +10 -4
  10. package/dist/index.d.ts +10 -4
  11. package/dist/index.js +75 -29
  12. package/dist/plugins/custom-format.cjs +33 -17
  13. package/dist/plugins/custom-format.d.cts +1 -1
  14. package/dist/plugins/custom-format.d.ts +1 -1
  15. package/dist/plugins/custom-format.js +33 -17
  16. package/dist/plugins/custom-type-example.cjs +33 -17
  17. package/dist/plugins/custom-type-example.d.cts +1 -1
  18. package/dist/plugins/custom-type-example.d.ts +1 -1
  19. package/dist/plugins/custom-type-example.js +33 -17
  20. package/dist/plugins/custom-validator.cjs +0 -2
  21. package/dist/plugins/custom-validator.d.cts +1 -1
  22. package/dist/plugins/custom-validator.d.ts +1 -1
  23. package/dist/plugins/custom-validator.js +0 -2
  24. package/docs/FEATURE-INDEX.md +553 -553
  25. package/docs/add-custom-locale.md +496 -496
  26. package/docs/add-keyword.md +24 -24
  27. package/docs/api-reference.md +1047 -1047
  28. package/docs/api.md +13 -13
  29. package/docs/best-practices-project-structure.md +417 -417
  30. package/docs/best-practices.md +712 -712
  31. package/docs/cache-manager.md +344 -344
  32. package/docs/compile.md +45 -45
  33. package/docs/conditional-api.md +1307 -1307
  34. package/docs/custom-extensions-guide.md +339 -339
  35. package/docs/design-philosophy.md +606 -606
  36. package/docs/doc-index.md +324 -324
  37. package/docs/dsl-syntax.md +714 -714
  38. package/docs/dynamic-locale.md +608 -608
  39. package/docs/enum.md +482 -482
  40. package/docs/error-handling.md +1975 -1975
  41. package/docs/export-guide.md +501 -501
  42. package/docs/export-limitations.md +567 -567
  43. package/docs/faq.md +596 -596
  44. package/docs/frontend-i18n-guide.md +307 -307
  45. package/docs/i18n-user-guide.md +487 -487
  46. package/docs/i18n.md +476 -476
  47. package/docs/index.md +48 -48
  48. package/docs/json-schema-basics.md +40 -40
  49. package/docs/label-vs-description.md +271 -271
  50. package/docs/markdown-exporter.md +406 -406
  51. package/docs/mongodb-exporter.md +302 -302
  52. package/docs/multi-language.md +26 -26
  53. package/docs/multi-type-support.md +322 -322
  54. package/docs/mysql-exporter.md +280 -280
  55. package/docs/number-operators.md +449 -449
  56. package/docs/optional-marker-guide.md +326 -326
  57. package/docs/performance-guide.md +49 -49
  58. package/docs/plugin-system.md +381 -381
  59. package/docs/plugin-type-registration.md +34 -34
  60. package/docs/postgresql-exporter.md +311 -311
  61. package/docs/public/favicon.svg +4 -4
  62. package/docs/quick-start.md +435 -435
  63. package/docs/runtime-locale-support.md +532 -532
  64. package/docs/schema-helper.md +345 -345
  65. package/docs/schema-utils-advanced-issues.md +23 -23
  66. package/docs/schema-utils-best-practices.md +20 -20
  67. package/docs/schema-utils-chaining.md +150 -150
  68. package/docs/schema-utils.md +524 -524
  69. package/docs/security-checklist.md +20 -20
  70. package/docs/string-extensions.md +488 -488
  71. package/docs/troubleshooting.md +486 -486
  72. package/docs/type-converter.md +310 -310
  73. package/docs/type-reference.md +242 -242
  74. package/docs/typescript-guide.md +584 -584
  75. package/docs/union-type-guide.md +157 -157
  76. package/docs/union-types.md +284 -284
  77. package/docs/validate-async.md +491 -491
  78. package/docs/validate-batch.md +49 -49
  79. package/docs/validate-dsl-object-support.md +578 -578
  80. package/docs/validate.md +506 -506
  81. package/docs/validation-guide.md +502 -502
  82. package/docs/validator.md +39 -39
  83. package/package.json +131 -131
  84. package/plugins/custom-format.cjs +8 -8
  85. package/plugins/custom-type-example.cjs +8 -8
  86. package/plugins/custom-validator.cjs +8 -8
  87. package/src/adapters/DslAdapter.ts +111 -111
  88. package/src/adapters/index.ts +1 -1
  89. package/src/config/constants.ts +83 -83
  90. package/src/config/index.ts +2 -2
  91. package/src/config/patterns.ts +77 -77
  92. package/src/core/CacheManager.ts +169 -159
  93. package/src/core/ConditionalBuilder.ts +382 -382
  94. package/src/core/ConditionalRuntime.ts +27 -27
  95. package/src/core/ConditionalValidator.ts +254 -254
  96. package/src/core/DslBuilder.ts +687 -677
  97. package/src/core/ErrorCodes.ts +38 -38
  98. package/src/core/ErrorFormatter.ts +271 -271
  99. package/src/core/JSONSchemaCore.ts +65 -65
  100. package/src/core/Locale.ts +187 -187
  101. package/src/core/MessageTemplate.ts +42 -42
  102. package/src/core/ObjectDslBuilder.ts +64 -64
  103. package/src/core/PluginManager.ts +326 -326
  104. package/src/core/StringExtensions.ts +140 -140
  105. package/src/core/TemplateEngine.ts +44 -44
  106. package/src/core/Validator.ts +448 -448
  107. package/src/errors/I18nError.ts +159 -159
  108. package/src/errors/ValidationError.ts +105 -105
  109. package/src/exporters/BaseExporter.ts +60 -60
  110. package/src/exporters/MarkdownExporter.ts +305 -305
  111. package/src/exporters/MongoDBExporter.ts +126 -126
  112. package/src/exporters/MySQLExporter.ts +156 -155
  113. package/src/exporters/PostgreSQLExporter.ts +222 -222
  114. package/src/exporters/index.ts +18 -18
  115. package/src/index.ts +651 -633
  116. package/src/locales/en-US.ts +160 -160
  117. package/src/locales/es-ES.ts +160 -160
  118. package/src/locales/fr-FR.ts +160 -160
  119. package/src/locales/index.ts +103 -103
  120. package/src/locales/ja-JP.ts +160 -160
  121. package/src/locales/types.ts +156 -156
  122. package/src/locales/zh-CN.ts +160 -160
  123. package/src/parser/ConstraintParser.ts +101 -101
  124. package/src/parser/DslParser.ts +470 -470
  125. package/src/parser/SchemaCompiler.ts +66 -66
  126. package/src/parser/TypeRegistry.ts +250 -250
  127. package/src/parser/index.ts +6 -6
  128. package/src/plugins/custom-format.ts +124 -126
  129. package/src/plugins/custom-type-example.ts +106 -108
  130. package/src/plugins/custom-validator.ts +138 -140
  131. package/src/types/conditional.ts +28 -28
  132. package/src/types/config.ts +59 -59
  133. package/src/types/dsl.ts +131 -131
  134. package/src/types/error.ts +60 -60
  135. package/src/types/index.ts +17 -17
  136. package/src/types/infer.ts +127 -127
  137. package/src/types/plugin.ts +58 -58
  138. package/src/types/safe-regex.d.ts +9 -9
  139. package/src/types/schema.ts +66 -66
  140. package/src/types/validate.ts +71 -71
  141. package/src/utils/SchemaHelper.ts +196 -196
  142. package/src/utils/SchemaUtils.ts +365 -346
  143. package/src/utils/TypeConverter.ts +215 -215
  144. package/src/utils/index.ts +10 -10
  145. package/src/validators/CustomKeywords.ts +477 -477
@@ -1,140 +1,140 @@
1
- /**
2
- * StringExtensions — opt-in String.prototype chainable DSL extensions.
3
- *
4
- * v2 fixes:
5
- * S-01/S-02: array-driven symmetric install/uninstall (v1 uninstall was missing `format` and
6
- * `phoneNumber`). All method names are now maintained in the EXTENSION_METHODS
7
- * array so both operations are guaranteed to be in sync.
8
- *
9
- * @example
10
- * import { installStringExtensions } from 'schema-dsl'
11
- * installStringExtensions(dsl)
12
- * // Then you can use:
13
- * 'email!'.label('Email address').messages({ format: 'Invalid format' })
14
- * 'string:3-32!'.username('medium')
15
- */
16
-
17
- import type { DslBuilder } from './DslBuilder.js'
18
-
19
- // S-01/S-02 fix: all extension method names are managed here so install/uninstall stay symmetric
20
- const EXTENSION_METHODS = [
21
- 'pattern',
22
- 'label',
23
- 'messages',
24
- 'error',
25
- 'description',
26
- 'format',
27
- 'custom',
28
- 'default',
29
- 'toSchema',
30
- 'toJsonSchema',
31
- 'username',
32
- 'password',
33
- 'phone',
34
- 'phoneNumber',
35
- 'idCard',
36
- 'creditCard',
37
- 'licensePlate',
38
- 'postalCode',
39
- 'passport',
40
- 'slug',
41
- 'domain',
42
- 'ip',
43
- 'base64',
44
- 'jwt',
45
- 'dateGreater',
46
- 'dateLess',
47
- 'after',
48
- 'before',
49
- 'dateFormat',
50
- 'min',
51
- 'max',
52
- 'alphanum',
53
- 'lowercase',
54
- 'uppercase',
55
- 'json',
56
- 'precision',
57
- 'multiple',
58
- 'port',
59
- 'requireAll',
60
- 'strict',
61
- 'noSparse',
62
- 'includesRequired',
63
- 'required',
64
- 'optional',
65
- 'enum',
66
- '_dslExtensionsInstalled',
67
- ] as const
68
-
69
- type DslFn = (dslStr: string) => DslBuilder
70
-
71
- // Track which dslFunction the extensions were installed with
72
- let _installedDslFn: DslFn | null = null
73
-
74
- /**
75
- * Install String.prototype extensions.
76
- * @param dslFunction - dsl() function (converts a string to a DslBuilder instance)
77
- */
78
- export function installStringExtensions(dslFunction: DslFn): void {
79
- // Idempotent: skip only if installed with the same dslFunction reference
80
- if (_installedDslFn === dslFunction) return
81
- // If installed with a different function, uninstall first then reinstall
82
- if (_installedDslFn !== null) {
83
- uninstallStringExtensions()
84
- }
85
-
86
- const proto = String.prototype as unknown as Record<string, unknown>
87
-
88
- function extend(name: string, fn: (...args: unknown[]) => unknown): void {
89
- proto[name] = fn
90
- }
91
-
92
- // Proxy all listed methods transparently to the DslBuilder instance
93
- const delegatedMethods: string[] = [
94
- 'pattern', 'label', 'messages', 'error', 'description', 'format', 'custom',
95
- 'default', 'username', 'password', 'phone', 'phoneNumber', 'idCard', 'creditCard',
96
- 'licensePlate', 'postalCode', 'passport', 'slug', 'domain', 'ip', 'base64', 'jwt',
97
- 'dateGreater', 'dateLess', 'after', 'before', 'dateFormat',
98
- 'min', 'max', 'alphanum', 'lowercase', 'uppercase', 'json',
99
- 'precision', 'multiple', 'port', 'requireAll', 'strict',
100
- 'noSparse', 'includesRequired', 'required', 'optional',
101
- ]
102
-
103
- for (const method of delegatedMethods) {
104
- extend(method, function (this: string, ...args: unknown[]): DslBuilder {
105
- const builder = dslFunction(String(this))
106
- return (builder as unknown as Record<string, (...args: unknown[]) => DslBuilder>)[method](...args)
107
- })
108
- }
109
-
110
- // enum method accepts rest parameters
111
- extend('enum', function (this: string, ...values: unknown[]): DslBuilder {
112
- return dslFunction(String(this)).enum(...values)
113
- })
114
-
115
- // toSchema / toJsonSchema return JSONSchema (not DslBuilder)
116
- extend('toSchema', function (this: string) {
117
- return dslFunction(String(this)).toSchema()
118
- })
119
- extend('toJsonSchema', function (this: string) {
120
- return dslFunction(String(this)).toJsonSchema()
121
- })
122
-
123
- // Installation marker (v1 BC)
124
- proto['_dslExtensionsInstalled'] = true
125
- _installedDslFn = dslFunction
126
- }
127
-
128
- /**
129
- * Uninstall String.prototype extensions (useful for tests or clean-up).
130
- * S-01/S-02 fix: uses EXTENSION_METHODS array to guarantee perfect symmetry with install.
131
- */
132
- export function uninstallStringExtensions(): void {
133
- if (!(String.prototype as unknown as Record<string, unknown>)['_dslExtensionsInstalled']) return
134
-
135
- const proto = String.prototype as unknown as Record<string, unknown>
136
- for (const method of EXTENSION_METHODS) {
137
- delete proto[method]
138
- }
139
- _installedDslFn = null
140
- }
1
+ /**
2
+ * StringExtensions — opt-in String.prototype chainable DSL extensions.
3
+ *
4
+ * v2 fixes:
5
+ * S-01/S-02: array-driven symmetric install/uninstall (v1 uninstall was missing `format` and
6
+ * `phoneNumber`). All method names are now maintained in the EXTENSION_METHODS
7
+ * array so both operations are guaranteed to be in sync.
8
+ *
9
+ * @example
10
+ * import { installStringExtensions } from 'schema-dsl'
11
+ * installStringExtensions(dsl)
12
+ * // Then you can use:
13
+ * 'email!'.label('Email address').messages({ format: 'Invalid format' })
14
+ * 'string:3-32!'.username('medium')
15
+ */
16
+
17
+ import type { DslBuilder } from './DslBuilder.js'
18
+
19
+ // S-01/S-02 fix: all extension method names are managed here so install/uninstall stay symmetric
20
+ const EXTENSION_METHODS = [
21
+ 'pattern',
22
+ 'label',
23
+ 'messages',
24
+ 'error',
25
+ 'description',
26
+ 'format',
27
+ 'custom',
28
+ 'default',
29
+ 'toSchema',
30
+ 'toJsonSchema',
31
+ 'username',
32
+ 'password',
33
+ 'phone',
34
+ 'phoneNumber',
35
+ 'idCard',
36
+ 'creditCard',
37
+ 'licensePlate',
38
+ 'postalCode',
39
+ 'passport',
40
+ 'slug',
41
+ 'domain',
42
+ 'ip',
43
+ 'base64',
44
+ 'jwt',
45
+ 'dateGreater',
46
+ 'dateLess',
47
+ 'after',
48
+ 'before',
49
+ 'dateFormat',
50
+ 'min',
51
+ 'max',
52
+ 'alphanum',
53
+ 'lowercase',
54
+ 'uppercase',
55
+ 'json',
56
+ 'precision',
57
+ 'multiple',
58
+ 'port',
59
+ 'requireAll',
60
+ 'strict',
61
+ 'noSparse',
62
+ 'includesRequired',
63
+ 'required',
64
+ 'optional',
65
+ 'enum',
66
+ '_dslExtensionsInstalled',
67
+ ] as const
68
+
69
+ type DslFn = (dslStr: string) => DslBuilder
70
+
71
+ // Track which dslFunction the extensions were installed with
72
+ let _installedDslFn: DslFn | null = null
73
+
74
+ /**
75
+ * Install String.prototype extensions.
76
+ * @param dslFunction - dsl() function (converts a string to a DslBuilder instance)
77
+ */
78
+ export function installStringExtensions(dslFunction: DslFn): void {
79
+ // Idempotent: skip only if installed with the same dslFunction reference
80
+ if (_installedDslFn === dslFunction) return
81
+ // If installed with a different function, uninstall first then reinstall
82
+ if (_installedDslFn !== null) {
83
+ uninstallStringExtensions()
84
+ }
85
+
86
+ const proto = String.prototype as unknown as Record<string, unknown>
87
+
88
+ function extend(name: string, fn: (...args: unknown[]) => unknown): void {
89
+ proto[name] = fn
90
+ }
91
+
92
+ // Proxy all listed methods transparently to the DslBuilder instance
93
+ const delegatedMethods: string[] = [
94
+ 'pattern', 'label', 'messages', 'error', 'description', 'format', 'custom',
95
+ 'default', 'username', 'password', 'phone', 'phoneNumber', 'idCard', 'creditCard',
96
+ 'licensePlate', 'postalCode', 'passport', 'slug', 'domain', 'ip', 'base64', 'jwt',
97
+ 'dateGreater', 'dateLess', 'after', 'before', 'dateFormat',
98
+ 'min', 'max', 'alphanum', 'lowercase', 'uppercase', 'json',
99
+ 'precision', 'multiple', 'port', 'requireAll', 'strict',
100
+ 'noSparse', 'includesRequired', 'required', 'optional',
101
+ ]
102
+
103
+ for (const method of delegatedMethods) {
104
+ extend(method, function (this: string, ...args: unknown[]): DslBuilder {
105
+ const builder = dslFunction(String(this))
106
+ return (builder as unknown as Record<string, (...args: unknown[]) => DslBuilder>)[method](...args)
107
+ })
108
+ }
109
+
110
+ // enum method accepts rest parameters
111
+ extend('enum', function (this: string, ...values: unknown[]): DslBuilder {
112
+ return dslFunction(String(this)).enum(...values)
113
+ })
114
+
115
+ // toSchema / toJsonSchema return JSONSchema (not DslBuilder)
116
+ extend('toSchema', function (this: string) {
117
+ return dslFunction(String(this)).toSchema()
118
+ })
119
+ extend('toJsonSchema', function (this: string) {
120
+ return dslFunction(String(this)).toJsonSchema()
121
+ })
122
+
123
+ // Installation marker (v1 BC)
124
+ proto['_dslExtensionsInstalled'] = true
125
+ _installedDslFn = dslFunction
126
+ }
127
+
128
+ /**
129
+ * Uninstall String.prototype extensions (useful for tests or clean-up).
130
+ * S-01/S-02 fix: uses EXTENSION_METHODS array to guarantee perfect symmetry with install.
131
+ */
132
+ export function uninstallStringExtensions(): void {
133
+ if (!(String.prototype as unknown as Record<string, unknown>)['_dslExtensionsInstalled']) return
134
+
135
+ const proto = String.prototype as unknown as Record<string, unknown>
136
+ for (const method of EXTENSION_METHODS) {
137
+ delete proto[method]
138
+ }
139
+ _installedDslFn = null
140
+ }
@@ -1,44 +1,44 @@
1
- /**
2
- * Unified template engine.
3
- *
4
- * Merges the two rendering implementations from v1 — MessageTemplate.render() and
5
- * ErrorFormatter._interpolate() — into a single pipeline.
6
- * Fixes the CORE-03 template injection vulnerability (single-pass replacement so that
7
- * substituted values are never expanded a second time).
8
- *
9
- * Supports two placeholder formats:
10
- * {{#key}} ← all v1 locale files use this format (must remain compatible)
11
- * {key} ← recommended format for v2 message templates
12
- */
13
-
14
- /**
15
- * Render a template string by replacing placeholders with corresponding values from params.
16
- *
17
- * @param template - Template string, e.g. "{{#label}} must be at least {{#min}} characters"
18
- * @param params - Substitution parameter object
19
- * @returns Rendered string; placeholders with no matching key are kept as-is (aids debugging)
20
- *
21
- * @example
22
- * renderTemplate('{{#label}} is required', { label: 'Email' })
23
- * // → 'Email is required'
24
- *
25
- * renderTemplate('{field} must be {min}~{max}', { field: 'age', min: 18, max: 65 })
26
- * // → 'age must be 18~65'
27
- */
28
- export function renderTemplate(template: string, params: Record<string, unknown>): string {
29
- // Single-pass replace — prevents substituted values from being expanded again (CORE-03 fix)
30
- // Regex matches both formats: {{#key}} and {key}
31
- return template.replace(/\{\{#([^}]+)\}\}|\{([^}]+)\}/g, (match, k1: string | undefined, k2: string | undefined) => {
32
- const key = k1 ?? k2
33
- if (key !== undefined && key in params) {
34
- const val = params[key]
35
- if (val === null) return 'null'
36
- if (val === undefined) return match
37
- if (Array.isArray(val)) return val.join(', ')
38
- if (val instanceof RegExp) return val.toString()
39
- if (val instanceof Date) return val.toISOString()
40
- return String(val)
41
- }
42
- return match // No matching key — keep placeholder as-is
43
- })
44
- }
1
+ /**
2
+ * Unified template engine.
3
+ *
4
+ * Merges the two rendering implementations from v1 — MessageTemplate.render() and
5
+ * ErrorFormatter._interpolate() — into a single pipeline.
6
+ * Fixes the CORE-03 template injection vulnerability (single-pass replacement so that
7
+ * substituted values are never expanded a second time).
8
+ *
9
+ * Supports two placeholder formats:
10
+ * {{#key}} ← all v1 locale files use this format (must remain compatible)
11
+ * {key} ← recommended format for v2 message templates
12
+ */
13
+
14
+ /**
15
+ * Render a template string by replacing placeholders with corresponding values from params.
16
+ *
17
+ * @param template - Template string, e.g. "{{#label}} must be at least {{#min}} characters"
18
+ * @param params - Substitution parameter object
19
+ * @returns Rendered string; placeholders with no matching key are kept as-is (aids debugging)
20
+ *
21
+ * @example
22
+ * renderTemplate('{{#label}} is required', { label: 'Email' })
23
+ * // → 'Email is required'
24
+ *
25
+ * renderTemplate('{field} must be {min}~{max}', { field: 'age', min: 18, max: 65 })
26
+ * // → 'age must be 18~65'
27
+ */
28
+ export function renderTemplate(template: string, params: Record<string, unknown>): string {
29
+ // Single-pass replace — prevents substituted values from being expanded again (CORE-03 fix)
30
+ // Regex matches both formats: {{#key}} and {key}
31
+ return template.replace(/\{\{#([^}]+)\}\}|\{([^}]+)\}/g, (match, k1: string | undefined, k2: string | undefined) => {
32
+ const key = k1 ?? k2
33
+ if (key !== undefined && key in params) {
34
+ const val = params[key]
35
+ if (val === null) return 'null'
36
+ if (val === undefined) return match
37
+ if (Array.isArray(val)) return val.join(', ')
38
+ if (val instanceof RegExp) return val.toString()
39
+ if (val instanceof Date) return val.toISOString()
40
+ return String(val)
41
+ }
42
+ return match // No matching key — keep placeholder as-is
43
+ })
44
+ }