schema-dsl 1.2.4 → 2.0.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 (242) hide show
  1. package/CHANGELOG.md +87 -210
  2. package/README.md +391 -2249
  3. package/dist/DslBuilder-DQDN0ZxZ.d.cts +341 -0
  4. package/dist/DslBuilder-DkLaOo9Q.d.ts +341 -0
  5. package/dist/Validator-C7GsVQOH.d.cts +192 -0
  6. package/dist/Validator-hFWKGxir.d.ts +192 -0
  7. package/dist/index.cjs +6594 -0
  8. package/dist/index.d.cts +1145 -0
  9. package/dist/index.d.ts +1145 -0
  10. package/dist/index.js +6528 -0
  11. package/dist/plugin-CIKtTMtS.d.cts +246 -0
  12. package/dist/plugin-CIKtTMtS.d.ts +246 -0
  13. package/dist/plugins/custom-format.cjs +3802 -0
  14. package/dist/plugins/custom-format.d.cts +12 -0
  15. package/dist/plugins/custom-format.d.ts +12 -0
  16. package/dist/plugins/custom-format.js +3772 -0
  17. package/dist/plugins/custom-type-example.cjs +3795 -0
  18. package/dist/plugins/custom-type-example.d.cts +8 -0
  19. package/dist/plugins/custom-type-example.d.ts +8 -0
  20. package/dist/plugins/custom-type-example.js +3765 -0
  21. package/dist/plugins/custom-validator.cjs +146 -0
  22. package/dist/plugins/custom-validator.d.cts +10 -0
  23. package/dist/plugins/custom-validator.d.ts +10 -0
  24. package/dist/plugins/custom-validator.js +121 -0
  25. package/docs/FEATURE-INDEX.md +102 -68
  26. package/docs/add-custom-locale.md +48 -35
  27. package/docs/add-keyword.md +24 -0
  28. package/docs/api-reference.md +396 -154
  29. package/docs/api.md +13 -0
  30. package/docs/best-practices-project-structure.md +19 -10
  31. package/docs/best-practices.md +93 -53
  32. package/docs/cache-manager.md +23 -15
  33. package/docs/compile.md +45 -0
  34. package/docs/conditional-api.md +40 -11
  35. package/docs/custom-extensions-guide.md +80 -152
  36. package/docs/design-philosophy.md +76 -71
  37. package/docs/doc-index.md +324 -0
  38. package/docs/dsl-syntax.md +69 -19
  39. package/docs/dynamic-locale.md +24 -14
  40. package/docs/enum.md +12 -5
  41. package/docs/error-handling.md +53 -44
  42. package/docs/export-guide.md +47 -8
  43. package/docs/export-limitations.md +27 -11
  44. package/docs/faq.md +86 -67
  45. package/docs/frontend-i18n-guide.md +26 -12
  46. package/docs/i18n-user-guide.md +60 -47
  47. package/docs/i18n.md +51 -32
  48. package/docs/index.md +48 -0
  49. package/docs/json-schema-basics.md +40 -0
  50. package/docs/label-vs-description.md +12 -3
  51. package/docs/markdown-exporter.md +15 -6
  52. package/docs/mongodb-exporter.md +11 -4
  53. package/docs/multi-language.md +26 -0
  54. package/docs/multi-type-support.md +26 -33
  55. package/docs/mysql-exporter.md +9 -2
  56. package/docs/number-operators.md +12 -5
  57. package/docs/optional-marker-guide.md +28 -23
  58. package/docs/performance-guide.md +49 -0
  59. package/docs/plugin-system.md +205 -366
  60. package/docs/plugin-type-registration.md +34 -0
  61. package/docs/postgresql-exporter.md +9 -2
  62. package/docs/public/favicon.svg +5 -0
  63. package/docs/quick-start.md +37 -363
  64. package/docs/runtime-locale-support.md +20 -9
  65. package/docs/schema-helper.md +10 -5
  66. package/docs/schema-utils-advanced-issues.md +23 -0
  67. package/docs/schema-utils-best-practices.md +20 -0
  68. package/docs/schema-utils-chaining.md +7 -0
  69. package/docs/schema-utils.md +76 -42
  70. package/docs/security-checklist.md +20 -0
  71. package/docs/string-extensions.md +17 -9
  72. package/docs/troubleshooting.md +36 -21
  73. package/docs/type-converter.md +41 -50
  74. package/docs/type-reference.md +38 -15
  75. package/docs/typescript-guide.md +53 -42
  76. package/docs/union-type-guide.md +11 -1
  77. package/docs/union-types.md +10 -3
  78. package/docs/validate-async.md +36 -25
  79. package/docs/validate-batch.md +49 -0
  80. package/docs/validate-dsl-object-support.md +33 -28
  81. package/docs/validate.md +36 -16
  82. package/docs/validation-guide.md +25 -7
  83. package/docs/validator.md +39 -0
  84. package/package.json +85 -27
  85. package/plugins/custom-format.cjs +8 -0
  86. package/plugins/custom-type-example.cjs +8 -0
  87. package/plugins/custom-validator.cjs +8 -0
  88. package/src/adapters/DslAdapter.ts +111 -0
  89. package/src/adapters/index.ts +1 -0
  90. package/src/config/constants.ts +83 -0
  91. package/src/config/index.ts +2 -0
  92. package/src/config/patterns.ts +77 -0
  93. package/src/core/CacheManager.ts +159 -0
  94. package/src/core/ConditionalBuilder.ts +382 -0
  95. package/src/core/ConditionalRuntime.ts +28 -0
  96. package/src/core/ConditionalValidator.ts +255 -0
  97. package/src/core/DslBuilder.ts +677 -0
  98. package/src/core/ErrorCodes.ts +38 -0
  99. package/src/core/ErrorFormatter.ts +271 -0
  100. package/src/core/JSONSchemaCore.ts +65 -0
  101. package/src/core/Locale.ts +187 -0
  102. package/src/core/MessageTemplate.ts +42 -0
  103. package/src/core/ObjectDslBuilder.ts +64 -0
  104. package/src/core/PluginManager.ts +326 -0
  105. package/src/core/StringExtensions.ts +140 -0
  106. package/src/core/TemplateEngine.ts +44 -0
  107. package/src/core/Validator.ts +448 -0
  108. package/src/errors/I18nError.ts +159 -0
  109. package/src/errors/ValidationError.ts +105 -0
  110. package/src/exporters/BaseExporter.ts +60 -0
  111. package/src/exporters/MarkdownExporter.ts +305 -0
  112. package/src/exporters/MongoDBExporter.ts +126 -0
  113. package/src/exporters/MySQLExporter.ts +155 -0
  114. package/src/exporters/PostgreSQLExporter.ts +222 -0
  115. package/src/exporters/index.ts +18 -0
  116. package/src/index.ts +633 -0
  117. package/{lib/locales/en-US.js → src/locales/en-US.ts} +21 -37
  118. package/{lib/locales/es-ES.js → src/locales/es-ES.ts} +63 -16
  119. package/{lib/locales/fr-FR.js → src/locales/fr-FR.ts} +74 -27
  120. package/src/locales/index.ts +103 -0
  121. package/{lib/locales/ja-JP.js → src/locales/ja-JP.ts} +59 -17
  122. package/src/locales/types.ts +156 -0
  123. package/{lib/locales/zh-CN.js → src/locales/zh-CN.ts} +21 -38
  124. package/src/parser/ConstraintParser.ts +101 -0
  125. package/src/parser/DslParser.ts +470 -0
  126. package/src/parser/SchemaCompiler.ts +66 -0
  127. package/src/parser/TypeRegistry.ts +250 -0
  128. package/src/parser/index.ts +6 -0
  129. package/src/plugins/custom-format.ts +126 -0
  130. package/src/plugins/custom-type-example.ts +108 -0
  131. package/src/plugins/custom-validator.ts +140 -0
  132. package/src/types/conditional.ts +28 -0
  133. package/src/types/config.ts +59 -0
  134. package/src/types/dsl.ts +131 -0
  135. package/src/types/error.ts +60 -0
  136. package/src/types/index.ts +17 -0
  137. package/src/types/infer.ts +128 -0
  138. package/src/types/plugin.ts +58 -0
  139. package/src/types/safe-regex.d.ts +9 -0
  140. package/src/types/schema.ts +66 -0
  141. package/src/types/validate.ts +71 -0
  142. package/src/utils/SchemaHelper.ts +196 -0
  143. package/src/utils/SchemaUtils.ts +346 -0
  144. package/src/utils/TypeConverter.ts +215 -0
  145. package/src/utils/index.ts +10 -0
  146. package/src/validators/CustomKeywords.ts +477 -0
  147. package/.eslintignore +0 -11
  148. package/.eslintrc.json +0 -27
  149. package/CONTRIBUTING.md +0 -368
  150. package/STATUS.md +0 -491
  151. package/changelogs/v1.0.0.md +0 -328
  152. package/changelogs/v1.0.9.md +0 -367
  153. package/changelogs/v1.1.0.md +0 -389
  154. package/changelogs/v1.1.1.md +0 -308
  155. package/changelogs/v1.1.2.md +0 -183
  156. package/changelogs/v1.1.3.md +0 -161
  157. package/changelogs/v1.1.4.md +0 -432
  158. package/changelogs/v1.1.5.md +0 -493
  159. package/changelogs/v1.1.6.md +0 -211
  160. package/changelogs/v1.1.8.md +0 -376
  161. package/changelogs/v1.2.3.md +0 -124
  162. package/docs/INDEX.md +0 -252
  163. package/docs/issues-resolved-summary.md +0 -196
  164. package/docs/performance-benchmark-report.md +0 -179
  165. package/docs/performance-quick-reference.md +0 -123
  166. package/docs/user-questions-answered.md +0 -353
  167. package/docs/validation-rules-v1.0.2.md +0 -1608
  168. package/examples/README.md +0 -81
  169. package/examples/array-dsl-example.js +0 -227
  170. package/examples/conditional-example.js +0 -288
  171. package/examples/conditional-non-object.js +0 -129
  172. package/examples/conditional-validate-example.js +0 -321
  173. package/examples/custom-extension.js +0 -85
  174. package/examples/dsl-match-example.js +0 -74
  175. package/examples/dsl-style.js +0 -118
  176. package/examples/dynamic-locale-configuration.js +0 -348
  177. package/examples/dynamic-locale-example.js +0 -287
  178. package/examples/enum.examples.js +0 -324
  179. package/examples/export-demo.js +0 -130
  180. package/examples/express-integration.js +0 -376
  181. package/examples/i18n-error-handling-complete.js +0 -381
  182. package/examples/i18n-error-handling-quickstart.md +0 -0
  183. package/examples/i18n-error.examples.js +0 -181
  184. package/examples/i18n-full-demo.js +0 -301
  185. package/examples/i18n-memory-safety.examples.js +0 -268
  186. package/examples/markdown-export.js +0 -71
  187. package/examples/middleware-usage.js +0 -93
  188. package/examples/new-features-comparison.js +0 -315
  189. package/examples/password-reset/README.md +0 -153
  190. package/examples/password-reset/schema.js +0 -26
  191. package/examples/password-reset/test.js +0 -101
  192. package/examples/plugin-system.examples.js +0 -205
  193. package/examples/schema-utils-chaining.examples.js +0 -250
  194. package/examples/simple-example.js +0 -122
  195. package/examples/slug.examples.js +0 -179
  196. package/examples/string-extensions.js +0 -297
  197. package/examples/union-type-example.js +0 -127
  198. package/examples/union-types-example.js +0 -77
  199. package/examples/user-registration/README.md +0 -156
  200. package/examples/user-registration/routes.js +0 -92
  201. package/examples/user-registration/schema.js +0 -150
  202. package/examples/user-registration/server.js +0 -74
  203. package/index.d.ts +0 -3540
  204. package/index.js +0 -457
  205. package/index.mjs +0 -60
  206. package/lib/adapters/DslAdapter.js +0 -871
  207. package/lib/adapters/index.js +0 -20
  208. package/lib/config/constants.js +0 -286
  209. package/lib/config/patterns/common.js +0 -47
  210. package/lib/config/patterns/creditCard.js +0 -9
  211. package/lib/config/patterns/idCard.js +0 -9
  212. package/lib/config/patterns/index.js +0 -9
  213. package/lib/config/patterns/licensePlate.js +0 -4
  214. package/lib/config/patterns/passport.js +0 -4
  215. package/lib/config/patterns/phone.js +0 -9
  216. package/lib/config/patterns/postalCode.js +0 -5
  217. package/lib/core/CacheManager.js +0 -376
  218. package/lib/core/ConditionalBuilder.js +0 -503
  219. package/lib/core/DslBuilder.js +0 -1400
  220. package/lib/core/ErrorCodes.js +0 -233
  221. package/lib/core/ErrorFormatter.js +0 -445
  222. package/lib/core/JSONSchemaCore.js +0 -347
  223. package/lib/core/Locale.js +0 -130
  224. package/lib/core/MessageTemplate.js +0 -98
  225. package/lib/core/PluginManager.js +0 -448
  226. package/lib/core/StringExtensions.js +0 -240
  227. package/lib/core/Validator.js +0 -654
  228. package/lib/errors/I18nError.js +0 -328
  229. package/lib/errors/ValidationError.js +0 -191
  230. package/lib/exporters/MarkdownExporter.js +0 -420
  231. package/lib/exporters/MongoDBExporter.js +0 -162
  232. package/lib/exporters/MySQLExporter.js +0 -212
  233. package/lib/exporters/PostgreSQLExporter.js +0 -289
  234. package/lib/exporters/index.js +0 -24
  235. package/lib/locales/index.js +0 -8
  236. package/lib/utils/LRUCache.js +0 -174
  237. package/lib/utils/SchemaHelper.js +0 -240
  238. package/lib/utils/SchemaUtils.js +0 -445
  239. package/lib/utils/TypeConverter.js +0 -245
  240. package/lib/utils/index.js +0 -13
  241. package/lib/validators/CustomKeywords.js +0 -616
  242. package/lib/validators/index.js +0 -11
@@ -0,0 +1,250 @@
1
+ import type { JSONSchema } from '../types/schema.js'
2
+
3
+ /**
4
+ * Type definition (structure of each entry in TypeRegistry)
5
+ */
6
+ export interface TypeDefinition {
7
+ /** Base JSON Schema fragment for this type */
8
+ baseSchema: Partial<JSONSchema>
9
+ /** Custom messages associated with this type (e.g., error key for phone type) */
10
+ customMessages?: Record<string, string>
11
+ /** Whether this type uses a pattern (keys are standard JSON Schema fields, not stripped in toJsonSchema) */
12
+ isPattern?: boolean
13
+ }
14
+
15
+ /** Known internal key set (stripped from output during toJsonSchema) */
16
+ const INTERNAL_KEYS: ReadonlySet<string> = new Set([
17
+ '_label',
18
+ '_customMessages',
19
+ '_description',
20
+ '_required',
21
+ '_isConditional',
22
+ '_runtimeOnlyConditional',
23
+ 'conditions',
24
+ '_evaluateCondition',
25
+ // Custom AJV keywords (non-standard JSON Schema fields, stripped on output)
26
+ 'exactLength',
27
+ 'alphanum',
28
+ 'lowercase',
29
+ 'uppercase',
30
+ 'trim',
31
+ 'jsonString',
32
+ 'port',
33
+ 'requiredAll',
34
+ 'strictSchema',
35
+ 'noSparse',
36
+ 'includesRequired',
37
+ 'dateFormat',
38
+ 'dateGreater',
39
+ 'dateLess',
40
+ 'precision',
41
+ // ⚠️ multipleOf is a standard JSON Schema field and is NOT in this list (fix DB-01)
42
+ ])
43
+
44
+ /**
45
+ * Built-in type registry
46
+ * 33 types covering v1 DslAdapter.typeMap + DslBuilder type lists (fixes inconsistencies DB-02, DA-01)
47
+ */
48
+ const BUILTIN_TYPES: Map<string, TypeDefinition> = new Map([
49
+ // --- Primitive types ---
50
+ ['string', { baseSchema: { type: 'string' } }],
51
+ ['number', { baseSchema: { type: 'number' } }],
52
+ ['integer', { baseSchema: { type: 'integer' } }],
53
+ ['boolean', { baseSchema: { type: 'boolean' } }],
54
+ ['object', { baseSchema: { type: 'object' } }],
55
+ ['array', { baseSchema: { type: 'array' } }],
56
+ ['null', { baseSchema: { type: 'null' } }],
57
+ ['any', { baseSchema: {} }],
58
+
59
+ // --- Format types ---
60
+ ['email', { baseSchema: { type: 'string', format: 'email' } }],
61
+ ['url', { baseSchema: { type: 'string', format: 'uri' } }],
62
+ ['uri', { baseSchema: { type: 'string', format: 'uri' } }],
63
+ ['uuid', { baseSchema: { type: 'string', format: 'uuid' } }],
64
+ ['ipv4', { baseSchema: { type: 'string', format: 'ipv4' } }],
65
+ ['ipv6', { baseSchema: { type: 'string', format: 'ipv6' } }],
66
+ ['ip', { baseSchema: { anyOf: [{ type: 'string', format: 'ipv4' }, { type: 'string', format: 'ipv6' }] } }],
67
+ ['hostname', { baseSchema: { type: 'string', format: 'hostname' } }],
68
+ ['date', { baseSchema: { type: 'string', format: 'date' } }],
69
+ ['datetime', { baseSchema: { type: 'string', format: 'date-time' } }],
70
+ ['time', { baseSchema: { type: 'string', format: 'time' } }],
71
+
72
+ // --- Special string types ---
73
+ ['binary', { baseSchema: { type: 'string', contentEncoding: 'base64' } }],
74
+ ['objectId', { baseSchema: { type: 'string', pattern: '^[0-9a-fA-F]{24}$' }, customMessages: { pattern: 'pattern.objectId' } }],
75
+ ['hexColor', { baseSchema: { type: 'string', pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' }, customMessages: { pattern: 'pattern.hexColor' } }],
76
+ ['macAddress', {
77
+ baseSchema: { type: 'string', pattern: '^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$' },
78
+ customMessages: { pattern: 'pattern.macAddress' },
79
+ }],
80
+ ['cron', {
81
+ baseSchema: {
82
+ type: 'string',
83
+ pattern:
84
+ '^(\\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\\*\\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) ' +
85
+ '(\\*|([0-9]|1[0-9]|2[0-3])|\\*\\/([0-9]|1[0-9]|2[0-3])) ' +
86
+ '(\\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\\*\\/([1-9]|1[0-9]|2[0-9]|3[0-1])) ' +
87
+ '(\\*|([1-9]|1[0-2])|\\*\\/([1-9]|1[0-2])) ' +
88
+ '(\\*|([0-6])|\\*\\/([0-6]))$',
89
+ },
90
+ customMessages: { pattern: 'pattern.cron' },
91
+ }],
92
+
93
+ // --- slug (fix DB-02: v1 DslAdapter was missing slug type definition) ---
94
+ ['slug', {
95
+ baseSchema: { type: 'string', pattern: '^[a-z0-9]+(?:-[a-z0-9]+)*$' },
96
+ customMessages: { pattern: 'pattern.slug' },
97
+ }],
98
+
99
+ // --- CJK / Chinese ---
100
+ ['chineseName', {
101
+ baseSchema: { type: 'string', pattern: '^[\\u4e00-\\u9fa5]{2,10}$' },
102
+ customMessages: { pattern: 'chineseName' },
103
+ }],
104
+ ['chinese', {
105
+ baseSchema: { type: 'string', pattern: '^[\\u4e00-\\u9fa5]+$' },
106
+ }],
107
+
108
+ // --- Domain-related (handled by CustomKeywords; only baseSchema registered here) ---
109
+ ['emailDomain', { baseSchema: { type: 'string', format: 'email' } }],
110
+
111
+ // --- v1 extension types (DslBuilder v1.0.2) ---
112
+ ['alphanum', { baseSchema: { type: 'string', alphanum: true } }],
113
+ ['lower', { baseSchema: { type: 'string', lowercase: true } }],
114
+ ['upper', { baseSchema: { type: 'string', uppercase: true } }],
115
+ ['json', { baseSchema: { type: 'string', jsonString: true } }],
116
+ ['port', { baseSchema: { type: 'integer', port: true } }],
117
+ ])
118
+
119
+ /**
120
+ * Custom type registry (populated via registerType)
121
+ */
122
+ const CUSTOM_TYPES: Map<string, TypeDefinition> = new Map()
123
+ const DYNAMIC_TYPES: Map<string, () => JSONSchema> = new Map()
124
+
125
+ let _strictMode = false
126
+
127
+ /**
128
+ * TypeRegistry — unified type registration and resolution
129
+ *
130
+ * Replaces the three inconsistent type lists in v1 (fixes DB-01/DB-02/DA-01)
131
+ */
132
+ export const TypeRegistry = {
133
+ /**
134
+ * Resolve a type name to its TypeDefinition.
135
+ * Built-in types take priority; custom types may override non-primitive built-ins.
136
+ */
137
+ resolve(typeName: string): TypeDefinition {
138
+ // Dynamic types: call factory function each time
139
+ const dynamicFn = DYNAMIC_TYPES.get(typeName)
140
+ if (dynamicFn) {
141
+ return { baseSchema: dynamicFn() as Partial<JSONSchema> }
142
+ }
143
+
144
+ const custom = CUSTOM_TYPES.get(typeName)
145
+ if (custom) return custom
146
+
147
+ const builtin = BUILTIN_TYPES.get(typeName)
148
+ if (builtin) return builtin
149
+
150
+ // Unknown type: throw in strict mode, otherwise warn and fall back to string
151
+ if (_strictMode) {
152
+ throw new Error(`[schema-dsl] Unknown type "${typeName}"`)
153
+ }
154
+ console.warn(`[schema-dsl] Unknown type "${typeName}", falling back to string`)
155
+ return { baseSchema: { type: 'string' } }
156
+ },
157
+
158
+ /**
159
+ * Register a custom type (delegated from DslBuilder.registerType)
160
+ */
161
+ register(name: string, def: TypeDefinition | Partial<JSONSchema>): void {
162
+ if (!name || typeof name !== 'string') {
163
+ throw new Error('[schema-dsl] TypeRegistry.register: name must be a non-empty string')
164
+ }
165
+ // Accept a raw Partial<JSONSchema> and wrap it automatically
166
+ const normalized: TypeDefinition =
167
+ 'baseSchema' in def ? (def as TypeDefinition) : { baseSchema: def as Partial<JSONSchema> }
168
+ CUSTOM_TYPES.set(name, normalized)
169
+ },
170
+
171
+ /**
172
+ * Register a dynamic type (factory function invoked on every resolve call)
173
+ */
174
+ registerDynamic(name: string, factory: () => JSONSchema): void {
175
+ if (!name || typeof name !== 'string') {
176
+ throw new Error('[schema-dsl] TypeRegistry.registerDynamic: name must be a non-empty string')
177
+ }
178
+ DYNAMIC_TYPES.set(name, factory)
179
+ },
180
+
181
+ /**
182
+ * Unregister a custom type
183
+ */
184
+ unregister(name: string): void {
185
+ CUSTOM_TYPES.delete(name)
186
+ DYNAMIC_TYPES.delete(name)
187
+ },
188
+
189
+ /**
190
+ * Clear all custom and dynamic types (primarily for testing; called by DslBuilder.clearCustomTypes).
191
+ * Built-in types are unaffected.
192
+ */
193
+ clearCustomTypes(): void {
194
+ CUSTOM_TYPES.clear()
195
+ DYNAMIC_TYPES.clear()
196
+ },
197
+
198
+ /**
199
+ * Enable or disable strict mode for type resolution.
200
+ * In strict mode, resolving an unknown type throws instead of warning and falling back to string.
201
+ */
202
+ setStrict(flag: boolean): void {
203
+ _strictMode = flag
204
+ },
205
+
206
+ /**
207
+ * Check whether a type is registered (built-in or custom)
208
+ */
209
+ has(typeName: string): boolean {
210
+ return BUILTIN_TYPES.has(typeName) || CUSTOM_TYPES.has(typeName) || DYNAMIC_TYPES.has(typeName)
211
+ },
212
+
213
+ /**
214
+ * Return an iterator over all registered types (built-in + custom; custom overrides same-name built-in)
215
+ * BC-4 compat: consumed by the DslAdapter.typeMap getter
216
+ */
217
+ entries(): IterableIterator<[string, TypeDefinition]> {
218
+ const merged: Map<string, TypeDefinition> = new Map([...BUILTIN_TYPES, ...CUSTOM_TYPES])
219
+ return merged.entries()
220
+ },
221
+
222
+ /**
223
+ * Return the internal key set (used to strip non-standard fields during toJsonSchema)
224
+ */
225
+ getInternalKeys(): ReadonlySet<string> {
226
+ return INTERNAL_KEYS
227
+ },
228
+
229
+ /**
230
+ * Strip internal keys from a schema and return a clean JSON Schema.
231
+ *
232
+ * Special case for exactLength: translated to standard minLength + maxLength
233
+ * instead of being stripped. This preserves v1 DslBuilder string:N behavior
234
+ * (output {minLength:N, maxLength:N}) while keeping AJV's exactLength Unicode
235
+ * code-point counting advantage internally (CK-Y04).
236
+ */
237
+ toJsonSchema(schema: JSONSchema): JSONSchema {
238
+ const result: JSONSchema = {}
239
+ for (const [k, v] of Object.entries(schema)) {
240
+ if (k === 'exactLength' && typeof v === 'number') {
241
+ // BC-compat: exactLength (AJV custom keyword) → standard JSON Schema minLength + maxLength
242
+ result.minLength = v
243
+ result.maxLength = v
244
+ } else if (!INTERNAL_KEYS.has(k)) {
245
+ result[k] = v
246
+ }
247
+ }
248
+ return result
249
+ },
250
+ } as const
@@ -0,0 +1,6 @@
1
+ export { TypeRegistry } from './TypeRegistry.js'
2
+ export type { TypeDefinition } from './TypeRegistry.js'
3
+ export { ConstraintParser } from './ConstraintParser.js'
4
+ export { SchemaCompiler } from './SchemaCompiler.js'
5
+ export type { AfterCompileHook } from './SchemaCompiler.js'
6
+ export { DslParser } from './DslParser.js'
@@ -0,0 +1,126 @@
1
+ import type { Plugin } from '../types/plugin.js'
2
+ import { DslBuilder } from '../core/DslBuilder.js'
3
+ import type { Validator } from '../core/Validator.js'
4
+
5
+ interface FormatConfig {
6
+ pattern?: RegExp
7
+ validate?: (value: string) => boolean
8
+ schema: Record<string, unknown>
9
+ }
10
+
11
+ const FORMATS: Record<string, FormatConfig> = {
12
+ 'phone-cn': {
13
+ pattern: /^1[3-9]\d{9}$/,
14
+ schema: { type: 'string', pattern: /^1[3-9]\d{9}$/.source, minLength: 11, maxLength: 11 },
15
+ },
16
+ 'postal-code-cn': {
17
+ pattern: /^\d{6}$/,
18
+ schema: { type: 'string', pattern: /^\d{6}$/.source, minLength: 6, maxLength: 6 },
19
+ },
20
+ 'ipv4-custom': {
21
+ 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]?)$/,
22
+ schema: { type: 'string', format: 'ipv4' },
23
+ },
24
+ 'wechat': {
25
+ pattern: /^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/,
26
+ schema: { type: 'string', pattern: /^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/.source, minLength: 6, maxLength: 20 },
27
+ },
28
+ 'qq': {
29
+ pattern: /^[1-9][0-9]{4,10}$/,
30
+ schema: { type: 'string', pattern: /^[1-9][0-9]{4,10}$/.source, minLength: 5, maxLength: 11 },
31
+ },
32
+ 'bank-card': {
33
+ validate: (value: string) => {
34
+ if (!/^\d{16,19}$/.test(value)) return false
35
+
36
+ let sum = 0
37
+ let shouldDouble = false
38
+
39
+ for (let i = value.length - 1; i >= 0; i -= 1) {
40
+ let digit = Number.parseInt(value[i], 10)
41
+
42
+ if (shouldDouble) {
43
+ digit *= 2
44
+ if (digit > 9) digit -= 9
45
+ }
46
+
47
+ sum += digit
48
+ shouldDouble = !shouldDouble
49
+ }
50
+
51
+ return sum % 10 === 0
52
+ },
53
+ schema: { type: 'string', minLength: 16, maxLength: 19, pattern: /^\d{16,19}$/.source },
54
+ },
55
+ 'license-plate': {
56
+ pattern: /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/,
57
+ schema: {
58
+ type: 'string',
59
+ pattern: /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/.source,
60
+ },
61
+ },
62
+ 'credit-code': {
63
+ pattern: /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/,
64
+ schema: {
65
+ type: 'string',
66
+ pattern: /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/.source,
67
+ minLength: 18,
68
+ maxLength: 18,
69
+ },
70
+ },
71
+ 'passport-cn': {
72
+ pattern: /^[EG]\d{8}$/,
73
+ schema: { type: 'string', pattern: /^[EG]\d{8}$/.source, minLength: 9, maxLength: 9 },
74
+ },
75
+ 'hk-macao-pass': {
76
+ pattern: /^[HM]\d{8,10}$/,
77
+ schema: { type: 'string', pattern: /^[HM]\d{8,10}$/.source, minLength: 9, maxLength: 11 },
78
+ },
79
+ }
80
+
81
+ function getAjvLike(core: unknown): {
82
+ addFormat: (name: string, definition: RegExp | { validate: RegExp | ((value: string) => boolean) }) => void
83
+ } {
84
+ const coreRecord = core as { getDefaultValidator?: () => Validator }
85
+ const validator = coreRecord.getDefaultValidator?.()
86
+ if (!validator) {
87
+ throw new Error('getDefaultValidator() is not available. Please provide schema-dsl core object.')
88
+ }
89
+ return validator.getAjv() as {
90
+ addFormat: (name: string, definition: RegExp | { validate: RegExp | ((value: string) => boolean) }) => void
91
+ }
92
+ }
93
+
94
+ function getDslBuilderLike(core: unknown): typeof DslBuilder {
95
+ const coreRecord = core as { DslBuilder?: typeof DslBuilder }
96
+ return coreRecord.DslBuilder ?? DslBuilder
97
+ }
98
+
99
+ export const customFormatPlugin: Plugin & {
100
+ addCustomFormats: (ajv: { addFormat: (name: string, definition: { validate: RegExp | ((value: string) => boolean) }) => void }, dslBuilder: typeof DslBuilder) => void
101
+ } = {
102
+ name: 'custom-format',
103
+ version: '2.0.0',
104
+ description: 'Custom format validation plugin (with DSL type registration)',
105
+ install(core, _options = {}, _context) {
106
+ const ajv = getAjvLike(core)
107
+ const dslBuilder = getDslBuilderLike(core)
108
+ this.addCustomFormats(ajv, dslBuilder)
109
+ console.log('[Plugin] custom-format v2.0.0 installed (with DSL type registration)')
110
+ },
111
+ uninstall() {
112
+ console.log('[Plugin] custom-format uninstalled')
113
+ },
114
+ addCustomFormats(ajv, dslBuilder) {
115
+ for (const [name, config] of Object.entries(FORMATS)) {
116
+ ajv.addFormat(name, {
117
+ validate: config.validate ?? config.pattern!,
118
+ })
119
+ dslBuilder.registerType(name, config.schema)
120
+ }
121
+ },
122
+ }
123
+
124
+ export default customFormatPlugin
125
+
126
+
@@ -0,0 +1,108 @@
1
+ import type { Plugin } from '../types/plugin.js'
2
+ import { DslBuilder } from '../core/DslBuilder.js'
3
+
4
+ export const customTypeExamplePlugin: Plugin & {
5
+ registerCustomTypes: (dslBuilder: typeof DslBuilder) => void
6
+ } = {
7
+ name: 'custom-type-example',
8
+ version: '1.0.0',
9
+ description: 'Custom type registration example plugin',
10
+ install(core, _options = {}, _context) {
11
+ const coreRecord = core as { DslBuilder?: typeof DslBuilder }
12
+ const builder = coreRecord.DslBuilder ?? DslBuilder
13
+
14
+ if (!builder || typeof builder.registerType !== 'function') {
15
+ throw new Error('DslBuilder.registerType is not available. Please upgrade to schema-dsl v1.1.0+')
16
+ }
17
+
18
+ this.registerCustomTypes(builder)
19
+ console.log('[Plugin] custom-type-example installed')
20
+ },
21
+ uninstall() {
22
+ console.log('[Plugin] custom-type-example uninstalled')
23
+ },
24
+ registerCustomTypes(dslBuilder) {
25
+ dslBuilder.registerType('order-id', {
26
+ type: 'string',
27
+ pattern: /^ORD[0-9]{12}$/.source,
28
+ minLength: 15,
29
+ maxLength: 15,
30
+ _customMessages: {
31
+ pattern: 'Invalid order ID format; must be 15 characters starting with ORD',
32
+ },
33
+ })
34
+
35
+ dslBuilder.registerType('sku', {
36
+ type: 'string',
37
+ pattern: /^SKU-[A-Z0-9]{6,10}$/.source,
38
+ minLength: 10,
39
+ maxLength: 14,
40
+ _customMessages: {
41
+ pattern: 'Invalid SKU format; must be SKU- followed by 6–10 alphanumeric characters',
42
+ },
43
+ })
44
+
45
+ dslBuilder.registerType('price', {
46
+ type: 'number',
47
+ minimum: 0,
48
+ multipleOf: 0.01,
49
+ _customMessages: {
50
+ minimum: 'Price cannot be negative',
51
+ multipleOf: 'Price can have at most 2 decimal places',
52
+ },
53
+ })
54
+
55
+ dslBuilder.registerType('rating', {
56
+ type: 'integer',
57
+ minimum: 1,
58
+ maximum: 5,
59
+ _customMessages: {
60
+ minimum: 'Rating cannot be lower than 1 star',
61
+ maximum: 'Rating cannot exceed 5 stars',
62
+ },
63
+ })
64
+
65
+ dslBuilder.registerType('color-code', {
66
+ oneOf: [
67
+ { type: 'string', pattern: /^#[0-9A-Fa-f]{6}$/.source },
68
+ { type: 'string', pattern: /^#[0-9A-Fa-f]{3}$/.source },
69
+ { type: 'string', pattern: /^rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)$/.source },
70
+ { type: 'string', pattern: /^rgba\(\d{1,3},\s*\d{1,3},\s*\d{1,3},\s*(0|1|0?\.\d+)\)$/.source },
71
+ ],
72
+ })
73
+
74
+ dslBuilder.registerType('semver', {
75
+ type: 'string',
76
+ pattern: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/.source,
77
+ _customMessages: {
78
+ pattern: 'Invalid version format; must follow semantic versioning (e.g. 1.0.0, 2.1.3-beta.1)',
79
+ },
80
+ })
81
+
82
+ dslBuilder.registerType('dynamic-age', () => {
83
+ const currentYear = new Date().getFullYear()
84
+ return {
85
+ type: 'integer',
86
+ minimum: 0,
87
+ maximum: currentYear - 1900,
88
+ _customMessages: {
89
+ minimum: 'Age cannot be negative',
90
+ maximum: `Age cannot exceed ${currentYear - 1900} years`,
91
+ },
92
+ }
93
+ })
94
+
95
+ dslBuilder.registerType('phone-intl', {
96
+ type: 'string',
97
+ pattern: /^\+?[1-9]\d{1,14}$/.source,
98
+ minLength: 8,
99
+ maxLength: 15,
100
+ _customMessages: {
101
+ pattern: 'Please enter a valid international phone number (E.164 format)',
102
+ },
103
+ })
104
+ },
105
+ }
106
+
107
+ export default customTypeExamplePlugin
108
+
@@ -0,0 +1,140 @@
1
+ import type { KeywordDefinition } from 'ajv'
2
+ import type { Plugin } from '../types/plugin.js'
3
+ import type { Validator } from '../core/Validator.js'
4
+
5
+ function getValidator(core: unknown): Validator {
6
+ const coreRecord = core as { getDefaultValidator?: () => Validator }
7
+ const validator = coreRecord.getDefaultValidator?.()
8
+ if (!validator) {
9
+ throw new Error('getDefaultValidator() is not available. Please provide schema-dsl core object.')
10
+ }
11
+ return validator
12
+ }
13
+
14
+ function getPluginBucket(): Record<string, unknown> {
15
+ const globalRecord = globalThis as typeof globalThis & {
16
+ __schemaDsl_plugins?: Record<string, unknown>
17
+ }
18
+ if (!globalRecord.__schemaDsl_plugins) {
19
+ globalRecord.__schemaDsl_plugins = {}
20
+ }
21
+ return globalRecord.__schemaDsl_plugins
22
+ }
23
+
24
+ export const customValidatorPlugin: Plugin & {
25
+ addCustomKeywords: (validator: Validator) => void
26
+ _validateIdCardChecksum: (idCard: string) => boolean
27
+ } = {
28
+ name: 'custom-validator',
29
+ version: '1.0.0',
30
+ description: 'Custom validator plugin — adds business-specific validation rules',
31
+ install(core, _options = {}, _context) {
32
+ const validator = getValidator(core)
33
+ this.addCustomKeywords(validator)
34
+ getPluginBucket()['custom-validator'] = this
35
+ console.log('[Plugin] custom-validator installed')
36
+ },
37
+ uninstall() {
38
+ delete getPluginBucket()['custom-validator']
39
+ console.log('[Plugin] custom-validator uninstalled')
40
+ },
41
+ addCustomKeywords(validator) {
42
+ const ajv = validator.getAjv() as {
43
+ getKeyword?: (name: string) => unknown
44
+ }
45
+
46
+ if (!ajv.getKeyword?.('unique')) {
47
+ validator.addKeyword('unique', {
48
+ async: true,
49
+ type: 'string',
50
+ validate: async function validateUnique(schema: unknown) {
51
+ if (!schema) return true
52
+ const { table, field } = schema as { table?: string; field?: string }
53
+ const exists = false
54
+ if (exists) {
55
+ ;(validateUnique as typeof validateUnique & { errors?: unknown[] }).errors = [{
56
+ keyword: 'unique',
57
+ message: `${field} already exists in ${table}`,
58
+ params: { table, field },
59
+ }]
60
+ return false
61
+ }
62
+ return true
63
+ },
64
+ } as unknown as KeywordDefinition)
65
+ }
66
+
67
+ if (!ajv.getKeyword?.('passwordStrength')) {
68
+ validator.addKeyword('passwordStrength', {
69
+ type: 'string',
70
+ validate: function validatePasswordStrength(schema: unknown, data: unknown) {
71
+ if (!schema) return true
72
+
73
+ const strength = String(schema)
74
+ const value = String(data ?? '')
75
+ const rules: Record<string, RegExp> = {
76
+ weak: /^.{6,}$/,
77
+ medium: /^(?=.*[a-z])(?=.*[A-Z]).{8,}$/,
78
+ strong: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$/,
79
+ }
80
+
81
+ const pattern = rules[strength]
82
+ if (!pattern) return true
83
+
84
+ if (!pattern.test(value)) {
85
+ ;(validatePasswordStrength as typeof validatePasswordStrength & { errors?: unknown[] }).errors = [{
86
+ keyword: 'passwordStrength',
87
+ message: `Password does not meet ${strength} strength requirements`,
88
+ params: { strength },
89
+ }]
90
+ return false
91
+ }
92
+
93
+ return true
94
+ },
95
+ } as unknown as KeywordDefinition)
96
+ }
97
+
98
+ if (!ajv.getKeyword?.('idCard')) {
99
+ validator.addKeyword('idCard', {
100
+ type: 'string',
101
+ validate: (schema: unknown, data: unknown) => {
102
+ if (!schema) return true
103
+
104
+ const value = String(data ?? '')
105
+ const pattern = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/
106
+
107
+ if (!pattern.test(value)) {
108
+ return false
109
+ }
110
+
111
+ return customValidatorPlugin._validateIdCardChecksum(value)
112
+ },
113
+ } as unknown as KeywordDefinition)
114
+ }
115
+ },
116
+ _validateIdCardChecksum(idCard: string) {
117
+ const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
118
+ const checksums = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
119
+
120
+ let sum = 0
121
+ for (let i = 0; i < 17; i += 1) {
122
+ sum += Number.parseInt(idCard[i], 10) * weights[i]
123
+ }
124
+
125
+ const checksum = checksums[sum % 11]
126
+ return idCard[17].toUpperCase() === checksum
127
+ },
128
+ hooks: {
129
+ onBeforeValidate() {},
130
+ onAfterValidate() {},
131
+ onError(error) {
132
+ console.error('[custom-validator] Error:', (error as Error).message)
133
+ },
134
+ },
135
+ }
136
+
137
+ export default customValidatorPlugin
138
+
139
+
140
+
@@ -0,0 +1,28 @@
1
+ /**
2
+ * IConditionalBuilder interface (shape definition).
3
+ * Implementation class is in src/core/ConditionalBuilder.ts (Phase 8).
4
+ */
5
+ import type { JSONSchema } from './schema.js'
6
+ import type { ValidationResult } from './validate.js'
7
+
8
+ export interface IConditionalBuilder {
9
+ and(condition: (data: unknown) => boolean): this
10
+ or(condition: (data: unknown) => boolean): this
11
+ /** then/else accept string DSL, JSONSchema, or null (no-op). */
12
+ then(schema: string | JSONSchema | null): this
13
+ else(schema: string | JSONSchema | null): this
14
+ elseIf(condition: (data: unknown) => boolean): this
15
+ message(msg: string): this
16
+ /** Serialise to a JSON Schema object (internal conditional marker). */
17
+ toSchema(): JSONSchema
18
+ /** Alias for toSchema(). */
19
+ build(): JSONSchema
20
+ /** Synchronous assertion; throws ValidationError on failure. */
21
+ assert(data: unknown, options?: Record<string, unknown>): unknown
22
+ /** Synchronous validation; returns ValidationResult. */
23
+ validate(data: unknown, options?: Record<string, unknown>): ValidationResult<unknown>
24
+ /** Async validation; returns Promise<ValidationResult>. */
25
+ validateAsync(data: unknown, options?: Record<string, unknown>): Promise<ValidationResult<unknown>>
26
+ /** Quick boolean check (no error details). */
27
+ check(data: unknown): boolean
28
+ }