schema-dsl 1.2.5 → 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 (243) hide show
  1. package/CHANGELOG.md +130 -238
  2. package/LICENSE +21 -21
  3. package/README.md +628 -2486
  4. package/dist/DslBuilder-BIgQOAXp.d.ts +343 -0
  5. package/dist/DslBuilder-CjHTucNQ.d.cts +343 -0
  6. package/dist/Validator-CllRdrY0.d.ts +192 -0
  7. package/dist/Validator-D6okG9tr.d.cts +192 -0
  8. package/dist/index.cjs +6640 -0
  9. package/dist/index.d.cts +1151 -0
  10. package/dist/index.d.ts +1151 -0
  11. package/dist/index.js +6574 -0
  12. package/dist/plugin-CIKtTMtS.d.cts +246 -0
  13. package/dist/plugin-CIKtTMtS.d.ts +246 -0
  14. package/dist/plugins/custom-format.cjs +3818 -0
  15. package/dist/plugins/custom-format.d.cts +12 -0
  16. package/dist/plugins/custom-format.d.ts +12 -0
  17. package/dist/plugins/custom-format.js +3788 -0
  18. package/dist/plugins/custom-type-example.cjs +3811 -0
  19. package/dist/plugins/custom-type-example.d.cts +8 -0
  20. package/dist/plugins/custom-type-example.d.ts +8 -0
  21. package/dist/plugins/custom-type-example.js +3781 -0
  22. package/dist/plugins/custom-validator.cjs +144 -0
  23. package/dist/plugins/custom-validator.d.cts +10 -0
  24. package/dist/plugins/custom-validator.d.ts +10 -0
  25. package/dist/plugins/custom-validator.js +119 -0
  26. package/docs/FEATURE-INDEX.md +553 -519
  27. package/docs/add-custom-locale.md +496 -483
  28. package/docs/add-keyword.md +24 -0
  29. package/docs/api-reference.md +1047 -805
  30. package/docs/api.md +13 -0
  31. package/docs/best-practices-project-structure.md +417 -408
  32. package/docs/best-practices.md +712 -672
  33. package/docs/cache-manager.md +344 -336
  34. package/docs/compile.md +45 -0
  35. package/docs/conditional-api.md +1307 -1278
  36. package/docs/custom-extensions-guide.md +339 -411
  37. package/docs/design-philosophy.md +606 -601
  38. package/docs/doc-index.md +324 -0
  39. package/docs/dsl-syntax.md +714 -664
  40. package/docs/dynamic-locale.md +608 -598
  41. package/docs/enum.md +482 -475
  42. package/docs/error-handling.md +1975 -1966
  43. package/docs/export-guide.md +501 -462
  44. package/docs/export-limitations.md +567 -551
  45. package/docs/faq.md +596 -577
  46. package/docs/frontend-i18n-guide.md +307 -293
  47. package/docs/i18n-user-guide.md +487 -474
  48. package/docs/i18n.md +476 -457
  49. package/docs/index.md +48 -0
  50. package/docs/json-schema-basics.md +40 -0
  51. package/docs/label-vs-description.md +271 -262
  52. package/docs/markdown-exporter.md +406 -397
  53. package/docs/mongodb-exporter.md +302 -295
  54. package/docs/multi-language.md +26 -0
  55. package/docs/multi-type-support.md +322 -329
  56. package/docs/mysql-exporter.md +280 -273
  57. package/docs/number-operators.md +449 -442
  58. package/docs/optional-marker-guide.md +326 -321
  59. package/docs/performance-guide.md +49 -0
  60. package/docs/plugin-system.md +381 -542
  61. package/docs/plugin-type-registration.md +34 -0
  62. package/docs/postgresql-exporter.md +311 -304
  63. package/docs/public/favicon.svg +5 -0
  64. package/docs/quick-start.md +435 -761
  65. package/docs/runtime-locale-support.md +532 -521
  66. package/docs/schema-helper.md +345 -340
  67. package/docs/schema-utils-advanced-issues.md +23 -0
  68. package/docs/schema-utils-best-practices.md +20 -0
  69. package/docs/schema-utils-chaining.md +150 -143
  70. package/docs/schema-utils.md +524 -490
  71. package/docs/security-checklist.md +20 -0
  72. package/docs/string-extensions.md +488 -480
  73. package/docs/troubleshooting.md +486 -471
  74. package/docs/type-converter.md +310 -319
  75. package/docs/type-reference.md +242 -219
  76. package/docs/typescript-guide.md +584 -573
  77. package/docs/union-type-guide.md +157 -147
  78. package/docs/union-types.md +284 -277
  79. package/docs/validate-async.md +491 -480
  80. package/docs/validate-batch.md +49 -0
  81. package/docs/validate-dsl-object-support.md +578 -573
  82. package/docs/validate.md +506 -486
  83. package/docs/validation-guide.md +502 -484
  84. package/docs/validator.md +39 -0
  85. package/package.json +131 -73
  86. package/plugins/custom-format.cjs +8 -0
  87. package/plugins/custom-type-example.cjs +8 -0
  88. package/plugins/custom-validator.cjs +8 -0
  89. package/src/adapters/DslAdapter.ts +111 -0
  90. package/src/adapters/index.ts +1 -0
  91. package/src/config/constants.ts +83 -0
  92. package/src/config/index.ts +2 -0
  93. package/src/config/patterns.ts +77 -0
  94. package/src/core/CacheManager.ts +169 -0
  95. package/src/core/ConditionalBuilder.ts +382 -0
  96. package/src/core/ConditionalRuntime.ts +28 -0
  97. package/src/core/ConditionalValidator.ts +255 -0
  98. package/src/core/DslBuilder.ts +687 -0
  99. package/src/core/ErrorCodes.ts +38 -0
  100. package/src/core/ErrorFormatter.ts +271 -0
  101. package/src/core/JSONSchemaCore.ts +65 -0
  102. package/src/core/Locale.ts +187 -0
  103. package/src/core/MessageTemplate.ts +42 -0
  104. package/src/core/ObjectDslBuilder.ts +64 -0
  105. package/src/core/PluginManager.ts +326 -0
  106. package/src/core/StringExtensions.ts +140 -0
  107. package/src/core/TemplateEngine.ts +44 -0
  108. package/src/core/Validator.ts +448 -0
  109. package/src/errors/I18nError.ts +159 -0
  110. package/src/errors/ValidationError.ts +105 -0
  111. package/src/exporters/BaseExporter.ts +60 -0
  112. package/src/exporters/MarkdownExporter.ts +305 -0
  113. package/src/exporters/MongoDBExporter.ts +126 -0
  114. package/src/exporters/MySQLExporter.ts +156 -0
  115. package/src/exporters/PostgreSQLExporter.ts +222 -0
  116. package/src/exporters/index.ts +18 -0
  117. package/src/index.ts +651 -0
  118. package/{lib/locales/en-US.js → src/locales/en-US.ts} +160 -176
  119. package/{lib/locales/es-ES.js → src/locales/es-ES.ts} +160 -113
  120. package/{lib/locales/fr-FR.js → src/locales/fr-FR.ts} +160 -113
  121. package/src/locales/index.ts +103 -0
  122. package/{lib/locales/ja-JP.js → src/locales/ja-JP.ts} +160 -118
  123. package/src/locales/types.ts +156 -0
  124. package/{lib/locales/zh-CN.js → src/locales/zh-CN.ts} +160 -177
  125. package/src/parser/ConstraintParser.ts +101 -0
  126. package/src/parser/DslParser.ts +470 -0
  127. package/src/parser/SchemaCompiler.ts +66 -0
  128. package/src/parser/TypeRegistry.ts +250 -0
  129. package/src/parser/index.ts +6 -0
  130. package/src/plugins/custom-format.ts +124 -0
  131. package/src/plugins/custom-type-example.ts +106 -0
  132. package/src/plugins/custom-validator.ts +138 -0
  133. package/src/types/conditional.ts +28 -0
  134. package/src/types/config.ts +59 -0
  135. package/src/types/dsl.ts +131 -0
  136. package/src/types/error.ts +60 -0
  137. package/src/types/index.ts +17 -0
  138. package/src/types/infer.ts +128 -0
  139. package/src/types/plugin.ts +58 -0
  140. package/src/types/safe-regex.d.ts +9 -0
  141. package/src/types/schema.ts +66 -0
  142. package/src/types/validate.ts +71 -0
  143. package/src/utils/SchemaHelper.ts +196 -0
  144. package/src/utils/SchemaUtils.ts +365 -0
  145. package/src/utils/TypeConverter.ts +215 -0
  146. package/src/utils/index.ts +10 -0
  147. package/src/validators/CustomKeywords.ts +477 -0
  148. package/.eslintignore +0 -11
  149. package/.eslintrc.json +0 -27
  150. package/CONTRIBUTING.md +0 -368
  151. package/STATUS.md +0 -491
  152. package/changelogs/v1.0.0.md +0 -328
  153. package/changelogs/v1.0.9.md +0 -367
  154. package/changelogs/v1.1.0.md +0 -389
  155. package/changelogs/v1.1.1.md +0 -308
  156. package/changelogs/v1.1.2.md +0 -183
  157. package/changelogs/v1.1.3.md +0 -161
  158. package/changelogs/v1.1.4.md +0 -432
  159. package/changelogs/v1.1.5.md +0 -493
  160. package/changelogs/v1.1.6.md +0 -211
  161. package/changelogs/v1.1.8.md +0 -376
  162. package/changelogs/v1.2.3.md +0 -124
  163. package/docs/INDEX.md +0 -252
  164. package/docs/issues-resolved-summary.md +0 -196
  165. package/docs/performance-benchmark-report.md +0 -179
  166. package/docs/performance-quick-reference.md +0 -123
  167. package/docs/user-questions-answered.md +0 -353
  168. package/docs/validation-rules-v1.0.2.md +0 -1608
  169. package/examples/README.md +0 -81
  170. package/examples/array-dsl-example.js +0 -227
  171. package/examples/conditional-example.js +0 -288
  172. package/examples/conditional-non-object.js +0 -129
  173. package/examples/conditional-validate-example.js +0 -321
  174. package/examples/custom-extension.js +0 -85
  175. package/examples/dsl-match-example.js +0 -74
  176. package/examples/dsl-style.js +0 -118
  177. package/examples/dynamic-locale-configuration.js +0 -348
  178. package/examples/dynamic-locale-example.js +0 -287
  179. package/examples/enum.examples.js +0 -324
  180. package/examples/export-demo.js +0 -130
  181. package/examples/express-integration.js +0 -376
  182. package/examples/i18n-error-handling-complete.js +0 -381
  183. package/examples/i18n-error-handling-quickstart.md +0 -0
  184. package/examples/i18n-error.examples.js +0 -181
  185. package/examples/i18n-full-demo.js +0 -301
  186. package/examples/i18n-memory-safety.examples.js +0 -268
  187. package/examples/markdown-export.js +0 -71
  188. package/examples/middleware-usage.js +0 -93
  189. package/examples/new-features-comparison.js +0 -315
  190. package/examples/password-reset/README.md +0 -153
  191. package/examples/password-reset/schema.js +0 -26
  192. package/examples/password-reset/test.js +0 -101
  193. package/examples/plugin-system.examples.js +0 -205
  194. package/examples/schema-utils-chaining.examples.js +0 -250
  195. package/examples/simple-example.js +0 -122
  196. package/examples/slug.examples.js +0 -179
  197. package/examples/string-extensions.js +0 -297
  198. package/examples/union-type-example.js +0 -127
  199. package/examples/union-types-example.js +0 -77
  200. package/examples/user-registration/README.md +0 -156
  201. package/examples/user-registration/routes.js +0 -92
  202. package/examples/user-registration/schema.js +0 -150
  203. package/examples/user-registration/server.js +0 -74
  204. package/index.d.ts +0 -3658
  205. package/index.js +0 -475
  206. package/index.mjs +0 -60
  207. package/lib/adapters/DslAdapter.js +0 -995
  208. package/lib/adapters/index.js +0 -20
  209. package/lib/config/constants.js +0 -286
  210. package/lib/config/patterns/common.js +0 -47
  211. package/lib/config/patterns/creditCard.js +0 -9
  212. package/lib/config/patterns/idCard.js +0 -9
  213. package/lib/config/patterns/index.js +0 -9
  214. package/lib/config/patterns/licensePlate.js +0 -4
  215. package/lib/config/patterns/passport.js +0 -4
  216. package/lib/config/patterns/phone.js +0 -9
  217. package/lib/config/patterns/postalCode.js +0 -5
  218. package/lib/core/CacheManager.js +0 -376
  219. package/lib/core/ConditionalBuilder.js +0 -503
  220. package/lib/core/DslBuilder.js +0 -1589
  221. package/lib/core/ErrorCodes.js +0 -233
  222. package/lib/core/ErrorFormatter.js +0 -445
  223. package/lib/core/JSONSchemaCore.js +0 -347
  224. package/lib/core/Locale.js +0 -130
  225. package/lib/core/MessageTemplate.js +0 -98
  226. package/lib/core/PluginManager.js +0 -448
  227. package/lib/core/StringExtensions.js +0 -240
  228. package/lib/core/Validator.js +0 -654
  229. package/lib/errors/I18nError.js +0 -328
  230. package/lib/errors/ValidationError.js +0 -191
  231. package/lib/exporters/MarkdownExporter.js +0 -420
  232. package/lib/exporters/MongoDBExporter.js +0 -162
  233. package/lib/exporters/MySQLExporter.js +0 -212
  234. package/lib/exporters/PostgreSQLExporter.js +0 -289
  235. package/lib/exporters/index.js +0 -24
  236. package/lib/locales/index.js +0 -8
  237. package/lib/utils/LRUCache.js +0 -174
  238. package/lib/utils/SchemaHelper.js +0 -240
  239. package/lib/utils/SchemaUtils.js +0 -445
  240. package/lib/utils/TypeConverter.js +0 -245
  241. package/lib/utils/index.js +0 -13
  242. package/lib/validators/CustomKeywords.js +0 -616
  243. 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,124 @@
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
+ },
110
+ uninstall() {
111
+ },
112
+ addCustomFormats(ajv, dslBuilder) {
113
+ for (const [name, config] of Object.entries(FORMATS)) {
114
+ ajv.addFormat(name, {
115
+ validate: config.validate ?? config.pattern!,
116
+ })
117
+ dslBuilder.registerType(name, config.schema)
118
+ }
119
+ },
120
+ }
121
+
122
+ export default customFormatPlugin
123
+
124
+
@@ -0,0 +1,106 @@
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
+ },
20
+ uninstall() {
21
+ },
22
+ registerCustomTypes(dslBuilder) {
23
+ dslBuilder.registerType('order-id', {
24
+ type: 'string',
25
+ pattern: /^ORD[0-9]{12}$/.source,
26
+ minLength: 15,
27
+ maxLength: 15,
28
+ _customMessages: {
29
+ pattern: 'Invalid order ID format; must be 15 characters starting with ORD',
30
+ },
31
+ })
32
+
33
+ dslBuilder.registerType('sku', {
34
+ type: 'string',
35
+ pattern: /^SKU-[A-Z0-9]{6,10}$/.source,
36
+ minLength: 10,
37
+ maxLength: 14,
38
+ _customMessages: {
39
+ pattern: 'Invalid SKU format; must be SKU- followed by 6–10 alphanumeric characters',
40
+ },
41
+ })
42
+
43
+ dslBuilder.registerType('price', {
44
+ type: 'number',
45
+ minimum: 0,
46
+ multipleOf: 0.01,
47
+ _customMessages: {
48
+ minimum: 'Price cannot be negative',
49
+ multipleOf: 'Price can have at most 2 decimal places',
50
+ },
51
+ })
52
+
53
+ dslBuilder.registerType('rating', {
54
+ type: 'integer',
55
+ minimum: 1,
56
+ maximum: 5,
57
+ _customMessages: {
58
+ minimum: 'Rating cannot be lower than 1 star',
59
+ maximum: 'Rating cannot exceed 5 stars',
60
+ },
61
+ })
62
+
63
+ dslBuilder.registerType('color-code', {
64
+ oneOf: [
65
+ { type: 'string', pattern: /^#[0-9A-Fa-f]{6}$/.source },
66
+ { type: 'string', pattern: /^#[0-9A-Fa-f]{3}$/.source },
67
+ { type: 'string', pattern: /^rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)$/.source },
68
+ { type: 'string', pattern: /^rgba\(\d{1,3},\s*\d{1,3},\s*\d{1,3},\s*(0|1|0?\.\d+)\)$/.source },
69
+ ],
70
+ })
71
+
72
+ dslBuilder.registerType('semver', {
73
+ type: 'string',
74
+ 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,
75
+ _customMessages: {
76
+ pattern: 'Invalid version format; must follow semantic versioning (e.g. 1.0.0, 2.1.3-beta.1)',
77
+ },
78
+ })
79
+
80
+ dslBuilder.registerType('dynamic-age', () => {
81
+ const currentYear = new Date().getFullYear()
82
+ return {
83
+ type: 'integer',
84
+ minimum: 0,
85
+ maximum: currentYear - 1900,
86
+ _customMessages: {
87
+ minimum: 'Age cannot be negative',
88
+ maximum: `Age cannot exceed ${currentYear - 1900} years`,
89
+ },
90
+ }
91
+ })
92
+
93
+ dslBuilder.registerType('phone-intl', {
94
+ type: 'string',
95
+ pattern: /^\+?[1-9]\d{1,14}$/.source,
96
+ minLength: 8,
97
+ maxLength: 15,
98
+ _customMessages: {
99
+ pattern: 'Please enter a valid international phone number (E.164 format)',
100
+ },
101
+ })
102
+ },
103
+ }
104
+
105
+ export default customTypeExamplePlugin
106
+
@@ -0,0 +1,138 @@
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
+ },
36
+ uninstall() {
37
+ delete getPluginBucket()['custom-validator']
38
+ },
39
+ addCustomKeywords(validator) {
40
+ const ajv = validator.getAjv() as {
41
+ getKeyword?: (name: string) => unknown
42
+ }
43
+
44
+ if (!ajv.getKeyword?.('unique')) {
45
+ validator.addKeyword('unique', {
46
+ async: true,
47
+ type: 'string',
48
+ validate: async function validateUnique(schema: unknown) {
49
+ if (!schema) return true
50
+ const { table, field } = schema as { table?: string; field?: string }
51
+ const exists = false
52
+ if (exists) {
53
+ ;(validateUnique as typeof validateUnique & { errors?: unknown[] }).errors = [{
54
+ keyword: 'unique',
55
+ message: `${field} already exists in ${table}`,
56
+ params: { table, field },
57
+ }]
58
+ return false
59
+ }
60
+ return true
61
+ },
62
+ } as unknown as KeywordDefinition)
63
+ }
64
+
65
+ if (!ajv.getKeyword?.('passwordStrength')) {
66
+ validator.addKeyword('passwordStrength', {
67
+ type: 'string',
68
+ validate: function validatePasswordStrength(schema: unknown, data: unknown) {
69
+ if (!schema) return true
70
+
71
+ const strength = String(schema)
72
+ const value = String(data ?? '')
73
+ const rules: Record<string, RegExp> = {
74
+ weak: /^.{6,}$/,
75
+ medium: /^(?=.*[a-z])(?=.*[A-Z]).{8,}$/,
76
+ strong: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$/,
77
+ }
78
+
79
+ const pattern = rules[strength]
80
+ if (!pattern) return true
81
+
82
+ if (!pattern.test(value)) {
83
+ ;(validatePasswordStrength as typeof validatePasswordStrength & { errors?: unknown[] }).errors = [{
84
+ keyword: 'passwordStrength',
85
+ message: `Password does not meet ${strength} strength requirements`,
86
+ params: { strength },
87
+ }]
88
+ return false
89
+ }
90
+
91
+ return true
92
+ },
93
+ } as unknown as KeywordDefinition)
94
+ }
95
+
96
+ if (!ajv.getKeyword?.('idCard')) {
97
+ validator.addKeyword('idCard', {
98
+ type: 'string',
99
+ validate: (schema: unknown, data: unknown) => {
100
+ if (!schema) return true
101
+
102
+ const value = String(data ?? '')
103
+ 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]$/
104
+
105
+ if (!pattern.test(value)) {
106
+ return false
107
+ }
108
+
109
+ return customValidatorPlugin._validateIdCardChecksum(value)
110
+ },
111
+ } as unknown as KeywordDefinition)
112
+ }
113
+ },
114
+ _validateIdCardChecksum(idCard: string) {
115
+ const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
116
+ const checksums = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
117
+
118
+ let sum = 0
119
+ for (let i = 0; i < 17; i += 1) {
120
+ sum += Number.parseInt(idCard[i], 10) * weights[i]
121
+ }
122
+
123
+ const checksum = checksums[sum % 11]
124
+ return idCard[17].toUpperCase() === checksum
125
+ },
126
+ hooks: {
127
+ onBeforeValidate() {},
128
+ onAfterValidate() {},
129
+ onError(error) {
130
+ console.error('[custom-validator] Error:', (error as Error).message)
131
+ },
132
+ },
133
+ }
134
+
135
+ export default customValidatorPlugin
136
+
137
+
138
+
@@ -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
+ }