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.
- package/CHANGELOG.md +130 -113
- package/LICENSE +21 -21
- package/README.md +628 -628
- package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
- package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
- package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
- package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
- package/dist/index.cjs +75 -29
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +75 -29
- package/dist/plugins/custom-format.cjs +33 -17
- package/dist/plugins/custom-format.d.cts +1 -1
- package/dist/plugins/custom-format.d.ts +1 -1
- package/dist/plugins/custom-format.js +33 -17
- package/dist/plugins/custom-type-example.cjs +33 -17
- package/dist/plugins/custom-type-example.d.cts +1 -1
- package/dist/plugins/custom-type-example.d.ts +1 -1
- package/dist/plugins/custom-type-example.js +33 -17
- package/dist/plugins/custom-validator.cjs +0 -2
- package/dist/plugins/custom-validator.d.cts +1 -1
- package/dist/plugins/custom-validator.d.ts +1 -1
- package/dist/plugins/custom-validator.js +0 -2
- package/docs/FEATURE-INDEX.md +553 -553
- package/docs/add-custom-locale.md +496 -496
- package/docs/add-keyword.md +24 -24
- package/docs/api-reference.md +1047 -1047
- package/docs/api.md +13 -13
- package/docs/best-practices-project-structure.md +417 -417
- package/docs/best-practices.md +712 -712
- package/docs/cache-manager.md +344 -344
- package/docs/compile.md +45 -45
- package/docs/conditional-api.md +1307 -1307
- package/docs/custom-extensions-guide.md +339 -339
- package/docs/design-philosophy.md +606 -606
- package/docs/doc-index.md +324 -324
- package/docs/dsl-syntax.md +714 -714
- package/docs/dynamic-locale.md +608 -608
- package/docs/enum.md +482 -482
- package/docs/error-handling.md +1975 -1975
- package/docs/export-guide.md +501 -501
- package/docs/export-limitations.md +567 -567
- package/docs/faq.md +596 -596
- package/docs/frontend-i18n-guide.md +307 -307
- package/docs/i18n-user-guide.md +487 -487
- package/docs/i18n.md +476 -476
- package/docs/index.md +48 -48
- package/docs/json-schema-basics.md +40 -40
- package/docs/label-vs-description.md +271 -271
- package/docs/markdown-exporter.md +406 -406
- package/docs/mongodb-exporter.md +302 -302
- package/docs/multi-language.md +26 -26
- package/docs/multi-type-support.md +322 -322
- package/docs/mysql-exporter.md +280 -280
- package/docs/number-operators.md +449 -449
- package/docs/optional-marker-guide.md +326 -326
- package/docs/performance-guide.md +49 -49
- package/docs/plugin-system.md +381 -381
- package/docs/plugin-type-registration.md +34 -34
- package/docs/postgresql-exporter.md +311 -311
- package/docs/public/favicon.svg +4 -4
- package/docs/quick-start.md +435 -435
- package/docs/runtime-locale-support.md +532 -532
- package/docs/schema-helper.md +345 -345
- package/docs/schema-utils-advanced-issues.md +23 -23
- package/docs/schema-utils-best-practices.md +20 -20
- package/docs/schema-utils-chaining.md +150 -150
- package/docs/schema-utils.md +524 -524
- package/docs/security-checklist.md +20 -20
- package/docs/string-extensions.md +488 -488
- package/docs/troubleshooting.md +486 -486
- package/docs/type-converter.md +310 -310
- package/docs/type-reference.md +242 -242
- package/docs/typescript-guide.md +584 -584
- package/docs/union-type-guide.md +157 -157
- package/docs/union-types.md +284 -284
- package/docs/validate-async.md +491 -491
- package/docs/validate-batch.md +49 -49
- package/docs/validate-dsl-object-support.md +578 -578
- package/docs/validate.md +506 -506
- package/docs/validation-guide.md +502 -502
- package/docs/validator.md +39 -39
- package/package.json +131 -131
- package/plugins/custom-format.cjs +8 -8
- package/plugins/custom-type-example.cjs +8 -8
- package/plugins/custom-validator.cjs +8 -8
- package/src/adapters/DslAdapter.ts +111 -111
- package/src/adapters/index.ts +1 -1
- package/src/config/constants.ts +83 -83
- package/src/config/index.ts +2 -2
- package/src/config/patterns.ts +77 -77
- package/src/core/CacheManager.ts +169 -159
- package/src/core/ConditionalBuilder.ts +382 -382
- package/src/core/ConditionalRuntime.ts +27 -27
- package/src/core/ConditionalValidator.ts +254 -254
- package/src/core/DslBuilder.ts +687 -677
- package/src/core/ErrorCodes.ts +38 -38
- package/src/core/ErrorFormatter.ts +271 -271
- package/src/core/JSONSchemaCore.ts +65 -65
- package/src/core/Locale.ts +187 -187
- package/src/core/MessageTemplate.ts +42 -42
- package/src/core/ObjectDslBuilder.ts +64 -64
- package/src/core/PluginManager.ts +326 -326
- package/src/core/StringExtensions.ts +140 -140
- package/src/core/TemplateEngine.ts +44 -44
- package/src/core/Validator.ts +448 -448
- package/src/errors/I18nError.ts +159 -159
- package/src/errors/ValidationError.ts +105 -105
- package/src/exporters/BaseExporter.ts +60 -60
- package/src/exporters/MarkdownExporter.ts +305 -305
- package/src/exporters/MongoDBExporter.ts +126 -126
- package/src/exporters/MySQLExporter.ts +156 -155
- package/src/exporters/PostgreSQLExporter.ts +222 -222
- package/src/exporters/index.ts +18 -18
- package/src/index.ts +651 -633
- package/src/locales/en-US.ts +160 -160
- package/src/locales/es-ES.ts +160 -160
- package/src/locales/fr-FR.ts +160 -160
- package/src/locales/index.ts +103 -103
- package/src/locales/ja-JP.ts +160 -160
- package/src/locales/types.ts +156 -156
- package/src/locales/zh-CN.ts +160 -160
- package/src/parser/ConstraintParser.ts +101 -101
- package/src/parser/DslParser.ts +470 -470
- package/src/parser/SchemaCompiler.ts +66 -66
- package/src/parser/TypeRegistry.ts +250 -250
- package/src/parser/index.ts +6 -6
- package/src/plugins/custom-format.ts +124 -126
- package/src/plugins/custom-type-example.ts +106 -108
- package/src/plugins/custom-validator.ts +138 -140
- package/src/types/conditional.ts +28 -28
- package/src/types/config.ts +59 -59
- package/src/types/dsl.ts +131 -131
- package/src/types/error.ts +60 -60
- package/src/types/index.ts +17 -17
- package/src/types/infer.ts +127 -127
- package/src/types/plugin.ts +58 -58
- package/src/types/safe-regex.d.ts +9 -9
- package/src/types/schema.ts +66 -66
- package/src/types/validate.ts +71 -71
- package/src/utils/SchemaHelper.ts +196 -196
- package/src/utils/SchemaUtils.ts +365 -346
- package/src/utils/TypeConverter.ts +215 -215
- package/src/utils/index.ts +10 -10
- package/src/validators/CustomKeywords.ts +477 -477
|
@@ -1,222 +1,222 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PostgreSQLExporter — Export JSON Schema as a PostgreSQL CREATE TABLE DDL statement.
|
|
3
|
-
*
|
|
4
|
-
* v2 fixes:
|
|
5
|
-
* Identifiers are escaped with double quotes (`"identifier"` format) instead of v1's unescaped raw identifiers.
|
|
6
|
-
* Column comments use the fully-qualified `"schema"."table"."column"` format.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { JSONSchema } from '../types/schema.js'
|
|
10
|
-
import { BaseExporter, type ExporterOptions } from './BaseExporter.js'
|
|
11
|
-
import { TypeConverter } from '../utils/TypeConverter.js'
|
|
12
|
-
|
|
13
|
-
// ==================== Type definitions ====================
|
|
14
|
-
|
|
15
|
-
export interface PostgreSQLExporterOptions extends ExporterOptions {
|
|
16
|
-
/** PostgreSQL schema name (default: public). */
|
|
17
|
-
schema: string
|
|
18
|
-
/** Whether to wrap identifiers in double quotes (default false — compatible with v1 behaviour). */
|
|
19
|
-
quoteIdentifiers?: boolean
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface GeneratePgIndexOptions {
|
|
23
|
-
name?: string
|
|
24
|
-
unique?: boolean
|
|
25
|
-
method?: 'btree' | 'hash' | 'gin' | 'gist'
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ==================== PostgreSQLExporter ====================
|
|
29
|
-
|
|
30
|
-
export class PostgreSQLExporter extends BaseExporter<PostgreSQLExporterOptions> {
|
|
31
|
-
constructor(options: Partial<PostgreSQLExporterOptions> = {}) {
|
|
32
|
-
super({ schema: 'public', ...options })
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Export as a PostgreSQL CREATE TABLE statement.
|
|
37
|
-
*/
|
|
38
|
-
export(tableName: string, jsonSchema: JSONSchema): string {
|
|
39
|
-
if (!tableName || typeof tableName !== 'string') {
|
|
40
|
-
throw new Error('[schema-dsl] Table name is required')
|
|
41
|
-
}
|
|
42
|
-
this._assertObjectSchema(jsonSchema)
|
|
43
|
-
|
|
44
|
-
// Fix: wrap identifiers in double quotes to avoid reserved-word conflicts
|
|
45
|
-
const schemaIdent = this._quoteIdent(this.options.schema)
|
|
46
|
-
const tableIdent = this._quoteIdent(tableName)
|
|
47
|
-
const fullTableName = `${schemaIdent}.${tableIdent}`
|
|
48
|
-
|
|
49
|
-
const columns = this._convertProperties(jsonSchema)
|
|
50
|
-
const primaryKey = this._detectPrimaryKey(jsonSchema)
|
|
51
|
-
|
|
52
|
-
let ddl = `CREATE TABLE ${fullTableName} (\n`
|
|
53
|
-
ddl += columns.map(col => ` ${col}`).join(',\n')
|
|
54
|
-
|
|
55
|
-
if (primaryKey) {
|
|
56
|
-
ddl += `,\n PRIMARY KEY (${this._quoteIdent(primaryKey)})`
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
ddl += `\n);`
|
|
60
|
-
|
|
61
|
-
if (jsonSchema.description) {
|
|
62
|
-
ddl += `\n\nCOMMENT ON TABLE ${fullTableName} IS '${this._escapeString(jsonSchema.description)}';`
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const commentDdls = this._generateColumnComments(fullTableName, tableName, jsonSchema)
|
|
66
|
-
if (commentDdls.length > 0) {
|
|
67
|
-
ddl += '\n\n' + commentDdls.join('\n')
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return ddl
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Generate a CREATE INDEX statement.
|
|
75
|
-
*/
|
|
76
|
-
generateIndex(tableName: string, columnName: string, options: GeneratePgIndexOptions = {}): string {
|
|
77
|
-
const fullTableName = `${this._quoteIdent(this.options.schema)}.${this._quoteIdent(tableName)}`
|
|
78
|
-
const indexName = options.name ?? `idx_${tableName}_${columnName}`
|
|
79
|
-
const unique = options.unique ? 'UNIQUE ' : ''
|
|
80
|
-
const method = options.method ?? 'btree'
|
|
81
|
-
return `CREATE ${unique}INDEX ${this._quoteIdent(indexName)} ON ${fullTableName} USING ${method} (${this._quoteIdent(columnName)});`
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Static quick-export shorthand.
|
|
86
|
-
*/
|
|
87
|
-
static export(tableName: string, jsonSchema: JSONSchema): string {
|
|
88
|
-
return new PostgreSQLExporter().export(tableName, jsonSchema)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ==================== Private methods ====================
|
|
92
|
-
|
|
93
|
-
/** Conditionally wrap a PG identifier in double quotes (when quoteIdentifiers=true). */
|
|
94
|
-
private _quoteIdent(name: string): string {
|
|
95
|
-
if (this.options.quoteIdentifiers) {
|
|
96
|
-
return `"${name.replace(/"/g, '""')}"`
|
|
97
|
-
}
|
|
98
|
-
return name
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private _convertProperties(schema: JSONSchema): string[] {
|
|
102
|
-
if (!schema.properties) return []
|
|
103
|
-
|
|
104
|
-
const required = schema.required ?? []
|
|
105
|
-
const columns: string[] = []
|
|
106
|
-
|
|
107
|
-
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
108
|
-
columns.push(this._convertColumn(name, propSchema, required.includes(name)))
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return columns
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private _convertColumn(name: string, schema: JSONSchema, isRequired: boolean): string {
|
|
115
|
-
const { jsonType, sqlType } = this._resolveColumnType(name, schema)
|
|
116
|
-
|
|
117
|
-
let def = `${this._quoteIdent(name)} ${sqlType}`
|
|
118
|
-
|
|
119
|
-
if (isRequired) def += ' NOT NULL'
|
|
120
|
-
|
|
121
|
-
if (schema.default !== undefined) {
|
|
122
|
-
def += ` DEFAULT ${this._formatDefaultValue(schema.default, jsonType)}`
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const checkConstraints = this._generateCheckConstraints(name, schema)
|
|
126
|
-
if (checkConstraints.length > 0) {
|
|
127
|
-
def += ' ' + checkConstraints.join(' ')
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return def
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private _resolveColumnType(name: string, schema: JSONSchema): { jsonType: string; sqlType: string } {
|
|
134
|
-
if (schema.type) {
|
|
135
|
-
return {
|
|
136
|
-
jsonType: String(schema.type),
|
|
137
|
-
sqlType: TypeConverter.toPostgreSQLType(schema.type as string | string[], schema),
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const variants = (schema.anyOf ?? schema.oneOf) as JSONSchema[] | undefined
|
|
142
|
-
if (!variants?.length) {
|
|
143
|
-
return {
|
|
144
|
-
jsonType: 'string',
|
|
145
|
-
sqlType: TypeConverter.toPostgreSQLType('string', schema),
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const sqlTypes = new Set(
|
|
150
|
-
variants.map(variant => TypeConverter.toPostgreSQLType((variant.type as string | string[] | undefined) ?? 'string', variant))
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
if (sqlTypes.size !== 1) {
|
|
154
|
-
const unionKind = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
155
|
-
throw new Error(`[schema-dsl] PostgreSQL exporter cannot safely map ${unionKind} for column "${name}" to a single SQL type`)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
jsonType: String(variants[0]?.type ?? 'string'),
|
|
160
|
-
sqlType: [...sqlTypes][0],
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private _generateCheckConstraints(columnName: string, schema: JSONSchema): string[] {
|
|
165
|
-
const checks: string[] = []
|
|
166
|
-
const col = this._quoteIdent(columnName)
|
|
167
|
-
|
|
168
|
-
if (schema.minLength !== undefined || schema.maxLength !== undefined) {
|
|
169
|
-
if (schema.minLength !== undefined && schema.maxLength !== undefined) {
|
|
170
|
-
checks.push(`CHECK (LENGTH(${col}) BETWEEN ${schema.minLength} AND ${schema.maxLength})`)
|
|
171
|
-
} else if (schema.minLength !== undefined) {
|
|
172
|
-
checks.push(`CHECK (LENGTH(${col}) >= ${schema.minLength})`)
|
|
173
|
-
} else if (schema.maxLength !== undefined) {
|
|
174
|
-
checks.push(`CHECK (LENGTH(${col}) <= ${schema.maxLength})`)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (schema.minimum !== undefined || schema.maximum !== undefined) {
|
|
179
|
-
if (schema.minimum !== undefined && schema.maximum !== undefined) {
|
|
180
|
-
checks.push(`CHECK (${col} BETWEEN ${schema.minimum} AND ${schema.maximum})`)
|
|
181
|
-
} else if (schema.minimum !== undefined) {
|
|
182
|
-
checks.push(`CHECK (${col} >= ${schema.minimum})`)
|
|
183
|
-
} else if (schema.maximum !== undefined) {
|
|
184
|
-
checks.push(`CHECK (${col} <= ${schema.maximum})`)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (schema.enum) {
|
|
189
|
-
const values = (schema.enum as unknown[]).map(v => `'${this._escapeString(String(v))}'`).join(', ')
|
|
190
|
-
checks.push(`CHECK (${col} IN (${values}))`)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return checks
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private _formatDefaultValue(value: unknown, type: string): string {
|
|
197
|
-
if (value === null) return 'NULL'
|
|
198
|
-
if (type === 'string') return `'${this._escapeString(String(value))}'`
|
|
199
|
-
if (type === 'boolean') return value ? 'TRUE' : 'FALSE'
|
|
200
|
-
if (type === 'object' || type === 'array') return `'${JSON.stringify(value)}'::JSONB`
|
|
201
|
-
return String(value)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
private _generateColumnComments(_fullTableName: string, tableName: string, schema: JSONSchema): string[] {
|
|
205
|
-
if (!schema.properties) return []
|
|
206
|
-
|
|
207
|
-
const comments: string[] = []
|
|
208
|
-
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
209
|
-
if (propSchema.description) {
|
|
210
|
-
// fullTableName is already quoted; quote the column identifier separately
|
|
211
|
-
const schemaIdent = this._quoteIdent(this.options.schema)
|
|
212
|
-
const tableIdent = this._quoteIdent(tableName)
|
|
213
|
-
const colIdent = this._quoteIdent(name)
|
|
214
|
-
comments.push(
|
|
215
|
-
`COMMENT ON COLUMN ${schemaIdent}.${tableIdent}.${colIdent} IS '${this._escapeString(propSchema.description)}';`,
|
|
216
|
-
)
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return comments
|
|
221
|
-
}
|
|
222
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQLExporter — Export JSON Schema as a PostgreSQL CREATE TABLE DDL statement.
|
|
3
|
+
*
|
|
4
|
+
* v2 fixes:
|
|
5
|
+
* Identifiers are escaped with double quotes (`"identifier"` format) instead of v1's unescaped raw identifiers.
|
|
6
|
+
* Column comments use the fully-qualified `"schema"."table"."column"` format.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { JSONSchema } from '../types/schema.js'
|
|
10
|
+
import { BaseExporter, type ExporterOptions } from './BaseExporter.js'
|
|
11
|
+
import { TypeConverter } from '../utils/TypeConverter.js'
|
|
12
|
+
|
|
13
|
+
// ==================== Type definitions ====================
|
|
14
|
+
|
|
15
|
+
export interface PostgreSQLExporterOptions extends ExporterOptions {
|
|
16
|
+
/** PostgreSQL schema name (default: public). */
|
|
17
|
+
schema: string
|
|
18
|
+
/** Whether to wrap identifiers in double quotes (default false — compatible with v1 behaviour). */
|
|
19
|
+
quoteIdentifiers?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GeneratePgIndexOptions {
|
|
23
|
+
name?: string
|
|
24
|
+
unique?: boolean
|
|
25
|
+
method?: 'btree' | 'hash' | 'gin' | 'gist'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ==================== PostgreSQLExporter ====================
|
|
29
|
+
|
|
30
|
+
export class PostgreSQLExporter extends BaseExporter<PostgreSQLExporterOptions> {
|
|
31
|
+
constructor(options: Partial<PostgreSQLExporterOptions> = {}) {
|
|
32
|
+
super({ schema: 'public', ...options })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Export as a PostgreSQL CREATE TABLE statement.
|
|
37
|
+
*/
|
|
38
|
+
export(tableName: string, jsonSchema: JSONSchema): string {
|
|
39
|
+
if (!tableName || typeof tableName !== 'string') {
|
|
40
|
+
throw new Error('[schema-dsl] Table name is required')
|
|
41
|
+
}
|
|
42
|
+
this._assertObjectSchema(jsonSchema)
|
|
43
|
+
|
|
44
|
+
// Fix: wrap identifiers in double quotes to avoid reserved-word conflicts
|
|
45
|
+
const schemaIdent = this._quoteIdent(this.options.schema)
|
|
46
|
+
const tableIdent = this._quoteIdent(tableName)
|
|
47
|
+
const fullTableName = `${schemaIdent}.${tableIdent}`
|
|
48
|
+
|
|
49
|
+
const columns = this._convertProperties(jsonSchema)
|
|
50
|
+
const primaryKey = this._detectPrimaryKey(jsonSchema)
|
|
51
|
+
|
|
52
|
+
let ddl = `CREATE TABLE ${fullTableName} (\n`
|
|
53
|
+
ddl += columns.map(col => ` ${col}`).join(',\n')
|
|
54
|
+
|
|
55
|
+
if (primaryKey) {
|
|
56
|
+
ddl += `,\n PRIMARY KEY (${this._quoteIdent(primaryKey)})`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
ddl += `\n);`
|
|
60
|
+
|
|
61
|
+
if (jsonSchema.description) {
|
|
62
|
+
ddl += `\n\nCOMMENT ON TABLE ${fullTableName} IS '${this._escapeString(jsonSchema.description)}';`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const commentDdls = this._generateColumnComments(fullTableName, tableName, jsonSchema)
|
|
66
|
+
if (commentDdls.length > 0) {
|
|
67
|
+
ddl += '\n\n' + commentDdls.join('\n')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return ddl
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generate a CREATE INDEX statement.
|
|
75
|
+
*/
|
|
76
|
+
generateIndex(tableName: string, columnName: string, options: GeneratePgIndexOptions = {}): string {
|
|
77
|
+
const fullTableName = `${this._quoteIdent(this.options.schema)}.${this._quoteIdent(tableName)}`
|
|
78
|
+
const indexName = options.name ?? `idx_${tableName}_${columnName}`
|
|
79
|
+
const unique = options.unique ? 'UNIQUE ' : ''
|
|
80
|
+
const method = options.method ?? 'btree'
|
|
81
|
+
return `CREATE ${unique}INDEX ${this._quoteIdent(indexName)} ON ${fullTableName} USING ${method} (${this._quoteIdent(columnName)});`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Static quick-export shorthand.
|
|
86
|
+
*/
|
|
87
|
+
static export(tableName: string, jsonSchema: JSONSchema): string {
|
|
88
|
+
return new PostgreSQLExporter().export(tableName, jsonSchema)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ==================== Private methods ====================
|
|
92
|
+
|
|
93
|
+
/** Conditionally wrap a PG identifier in double quotes (when quoteIdentifiers=true). */
|
|
94
|
+
private _quoteIdent(name: string): string {
|
|
95
|
+
if (this.options.quoteIdentifiers) {
|
|
96
|
+
return `"${name.replace(/"/g, '""')}"`
|
|
97
|
+
}
|
|
98
|
+
return name
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private _convertProperties(schema: JSONSchema): string[] {
|
|
102
|
+
if (!schema.properties) return []
|
|
103
|
+
|
|
104
|
+
const required = schema.required ?? []
|
|
105
|
+
const columns: string[] = []
|
|
106
|
+
|
|
107
|
+
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
108
|
+
columns.push(this._convertColumn(name, propSchema, required.includes(name)))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return columns
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private _convertColumn(name: string, schema: JSONSchema, isRequired: boolean): string {
|
|
115
|
+
const { jsonType, sqlType } = this._resolveColumnType(name, schema)
|
|
116
|
+
|
|
117
|
+
let def = `${this._quoteIdent(name)} ${sqlType}`
|
|
118
|
+
|
|
119
|
+
if (isRequired) def += ' NOT NULL'
|
|
120
|
+
|
|
121
|
+
if (schema.default !== undefined) {
|
|
122
|
+
def += ` DEFAULT ${this._formatDefaultValue(schema.default, jsonType)}`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const checkConstraints = this._generateCheckConstraints(name, schema)
|
|
126
|
+
if (checkConstraints.length > 0) {
|
|
127
|
+
def += ' ' + checkConstraints.join(' ')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return def
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private _resolveColumnType(name: string, schema: JSONSchema): { jsonType: string; sqlType: string } {
|
|
134
|
+
if (schema.type) {
|
|
135
|
+
return {
|
|
136
|
+
jsonType: String(schema.type),
|
|
137
|
+
sqlType: TypeConverter.toPostgreSQLType(schema.type as string | string[], schema),
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const variants = (schema.anyOf ?? schema.oneOf) as JSONSchema[] | undefined
|
|
142
|
+
if (!variants?.length) {
|
|
143
|
+
return {
|
|
144
|
+
jsonType: 'string',
|
|
145
|
+
sqlType: TypeConverter.toPostgreSQLType('string', schema),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const sqlTypes = new Set(
|
|
150
|
+
variants.map(variant => TypeConverter.toPostgreSQLType((variant.type as string | string[] | undefined) ?? 'string', variant))
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if (sqlTypes.size !== 1) {
|
|
154
|
+
const unionKind = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
155
|
+
throw new Error(`[schema-dsl] PostgreSQL exporter cannot safely map ${unionKind} for column "${name}" to a single SQL type`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
jsonType: String(variants[0]?.type ?? 'string'),
|
|
160
|
+
sqlType: [...sqlTypes][0],
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private _generateCheckConstraints(columnName: string, schema: JSONSchema): string[] {
|
|
165
|
+
const checks: string[] = []
|
|
166
|
+
const col = this._quoteIdent(columnName)
|
|
167
|
+
|
|
168
|
+
if (schema.minLength !== undefined || schema.maxLength !== undefined) {
|
|
169
|
+
if (schema.minLength !== undefined && schema.maxLength !== undefined) {
|
|
170
|
+
checks.push(`CHECK (LENGTH(${col}) BETWEEN ${schema.minLength} AND ${schema.maxLength})`)
|
|
171
|
+
} else if (schema.minLength !== undefined) {
|
|
172
|
+
checks.push(`CHECK (LENGTH(${col}) >= ${schema.minLength})`)
|
|
173
|
+
} else if (schema.maxLength !== undefined) {
|
|
174
|
+
checks.push(`CHECK (LENGTH(${col}) <= ${schema.maxLength})`)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (schema.minimum !== undefined || schema.maximum !== undefined) {
|
|
179
|
+
if (schema.minimum !== undefined && schema.maximum !== undefined) {
|
|
180
|
+
checks.push(`CHECK (${col} BETWEEN ${schema.minimum} AND ${schema.maximum})`)
|
|
181
|
+
} else if (schema.minimum !== undefined) {
|
|
182
|
+
checks.push(`CHECK (${col} >= ${schema.minimum})`)
|
|
183
|
+
} else if (schema.maximum !== undefined) {
|
|
184
|
+
checks.push(`CHECK (${col} <= ${schema.maximum})`)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (schema.enum) {
|
|
189
|
+
const values = (schema.enum as unknown[]).map(v => `'${this._escapeString(String(v))}'`).join(', ')
|
|
190
|
+
checks.push(`CHECK (${col} IN (${values}))`)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return checks
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private _formatDefaultValue(value: unknown, type: string): string {
|
|
197
|
+
if (value === null) return 'NULL'
|
|
198
|
+
if (type === 'string') return `'${this._escapeString(String(value))}'`
|
|
199
|
+
if (type === 'boolean') return value ? 'TRUE' : 'FALSE'
|
|
200
|
+
if (type === 'object' || type === 'array') return `'${this._escapeString(JSON.stringify(value))}'::JSONB`
|
|
201
|
+
return String(value)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private _generateColumnComments(_fullTableName: string, tableName: string, schema: JSONSchema): string[] {
|
|
205
|
+
if (!schema.properties) return []
|
|
206
|
+
|
|
207
|
+
const comments: string[] = []
|
|
208
|
+
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
209
|
+
if (propSchema.description) {
|
|
210
|
+
// fullTableName is already quoted; quote the column identifier separately
|
|
211
|
+
const schemaIdent = this._quoteIdent(this.options.schema)
|
|
212
|
+
const tableIdent = this._quoteIdent(tableName)
|
|
213
|
+
const colIdent = this._quoteIdent(name)
|
|
214
|
+
comments.push(
|
|
215
|
+
`COMMENT ON COLUMN ${schemaIdent}.${tableIdent}.${colIdent} IS '${this._escapeString(propSchema.description)}';`,
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return comments
|
|
221
|
+
}
|
|
222
|
+
}
|
package/src/exporters/index.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* src/exporters/index.ts — Unified re-export for all exporters
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { BaseExporter } from './BaseExporter.js'
|
|
6
|
-
export type { ExporterOptions } from './BaseExporter.js'
|
|
7
|
-
|
|
8
|
-
export { MongoDBExporter } from './MongoDBExporter.js'
|
|
9
|
-
export type { MongoDBExporterOptions, MongoDBValidationSchema, MongoDBCreateCommand } from './MongoDBExporter.js'
|
|
10
|
-
|
|
11
|
-
export { MySQLExporter } from './MySQLExporter.js'
|
|
12
|
-
export type { MySQLExporterOptions, GenerateIndexOptions } from './MySQLExporter.js'
|
|
13
|
-
|
|
14
|
-
export { PostgreSQLExporter } from './PostgreSQLExporter.js'
|
|
15
|
-
export type { PostgreSQLExporterOptions, GeneratePgIndexOptions } from './PostgreSQLExporter.js'
|
|
16
|
-
|
|
17
|
-
export { MarkdownExporter } from './MarkdownExporter.js'
|
|
18
|
-
export type { MarkdownExporterOptions } from './MarkdownExporter.js'
|
|
1
|
+
/**
|
|
2
|
+
* src/exporters/index.ts — Unified re-export for all exporters
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { BaseExporter } from './BaseExporter.js'
|
|
6
|
+
export type { ExporterOptions } from './BaseExporter.js'
|
|
7
|
+
|
|
8
|
+
export { MongoDBExporter } from './MongoDBExporter.js'
|
|
9
|
+
export type { MongoDBExporterOptions, MongoDBValidationSchema, MongoDBCreateCommand } from './MongoDBExporter.js'
|
|
10
|
+
|
|
11
|
+
export { MySQLExporter } from './MySQLExporter.js'
|
|
12
|
+
export type { MySQLExporterOptions, GenerateIndexOptions } from './MySQLExporter.js'
|
|
13
|
+
|
|
14
|
+
export { PostgreSQLExporter } from './PostgreSQLExporter.js'
|
|
15
|
+
export type { PostgreSQLExporterOptions, GeneratePgIndexOptions } from './PostgreSQLExporter.js'
|
|
16
|
+
|
|
17
|
+
export { MarkdownExporter } from './MarkdownExporter.js'
|
|
18
|
+
export type { MarkdownExporterOptions } from './MarkdownExporter.js'
|