pg2zod 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.
@@ -0,0 +1,835 @@
1
+ import pg from "pg";
2
+
3
+ //#region src/introspect.ts
4
+ const { Pool } = pg;
5
+ /**
6
+ * Introspect a PostgreSQL database and return complete metadata
7
+ */
8
+ async function introspectDatabase(config, options = {}) {
9
+ const pool = new Pool(config);
10
+ try {
11
+ const schemas = options.schemas ?? ["public"];
12
+ const [tables, enums, compositeTypes, rangeTypes, domains] = await Promise.all([
13
+ introspectTables(pool, schemas, options),
14
+ introspectEnums(pool, schemas),
15
+ introspectCompositeTypes(pool, schemas),
16
+ introspectRangeTypes(pool, schemas),
17
+ introspectDomains(pool, schemas)
18
+ ]);
19
+ return {
20
+ tables,
21
+ enums,
22
+ compositeTypes,
23
+ rangeTypes,
24
+ domains
25
+ };
26
+ } finally {
27
+ await pool.end();
28
+ }
29
+ }
30
+ /**
31
+ * Introspect tables and their columns
32
+ */
33
+ async function introspectTables(pool, schemas, options) {
34
+ const schemaFilter = schemas.map((_, i) => `$${i + 1}`).join(", ");
35
+ const columnsQuery = `
36
+ SELECT
37
+ c.table_schema,
38
+ c.table_name,
39
+ c.column_name,
40
+ c.data_type,
41
+ c.is_nullable,
42
+ c.column_default,
43
+ c.character_maximum_length,
44
+ c.numeric_precision,
45
+ c.numeric_scale,
46
+ c.datetime_precision,
47
+ c.udt_name,
48
+ c.domain_name,
49
+ COALESCE(
50
+ (SELECT array_ndims(ARRAY[]::text[]) FROM information_schema.element_types e
51
+ WHERE e.object_schema = c.table_schema
52
+ AND e.object_name = c.table_name
53
+ AND e.object_type = 'TABLE'
54
+ AND e.collection_type_identifier = c.dtd_identifier
55
+ ), 0
56
+ ) as array_dimensions
57
+ FROM information_schema.columns c
58
+ WHERE c.table_schema IN (${schemaFilter})
59
+ ORDER BY c.table_schema, c.table_name, c.ordinal_position
60
+ `;
61
+ const columnsResult = await pool.query(columnsQuery, schemas);
62
+ const constraintsQuery = `
63
+ SELECT
64
+ tc.table_schema,
65
+ tc.table_name,
66
+ tc.constraint_name,
67
+ cc.check_clause,
68
+ ccu.column_name
69
+ FROM information_schema.table_constraints tc
70
+ JOIN information_schema.check_constraints cc
71
+ ON tc.constraint_name = cc.constraint_name
72
+ AND tc.constraint_schema = cc.constraint_schema
73
+ LEFT JOIN information_schema.constraint_column_usage ccu
74
+ ON tc.constraint_name = ccu.constraint_name
75
+ AND tc.constraint_schema = ccu.constraint_schema
76
+ WHERE tc.constraint_type = 'CHECK'
77
+ AND tc.table_schema IN (${schemaFilter})
78
+ `;
79
+ const constraintsResult = await pool.query(constraintsQuery, schemas);
80
+ const primaryKeysQuery = `
81
+ SELECT
82
+ tc.table_schema,
83
+ tc.table_name,
84
+ kcu.column_name
85
+ FROM information_schema.table_constraints tc
86
+ JOIN information_schema.key_column_usage kcu
87
+ ON tc.constraint_name = kcu.constraint_name
88
+ AND tc.table_schema = kcu.table_schema
89
+ WHERE tc.constraint_type = 'PRIMARY KEY'
90
+ AND tc.table_schema IN (${schemaFilter})
91
+ ORDER BY kcu.ordinal_position
92
+ `;
93
+ const primaryKeysResult = await pool.query(primaryKeysQuery, schemas);
94
+ const uniqueConstraintsQuery = `
95
+ SELECT
96
+ tc.table_schema,
97
+ tc.table_name,
98
+ tc.constraint_name,
99
+ array_agg(kcu.column_name ORDER BY kcu.ordinal_position) as columns
100
+ FROM information_schema.table_constraints tc
101
+ JOIN information_schema.key_column_usage kcu
102
+ ON tc.constraint_name = kcu.constraint_name
103
+ AND tc.table_schema = kcu.table_schema
104
+ WHERE tc.constraint_type = 'UNIQUE'
105
+ AND tc.table_schema IN (${schemaFilter})
106
+ GROUP BY tc.table_schema, tc.table_name, tc.constraint_name
107
+ `;
108
+ const uniqueConstraintsResult = await pool.query(uniqueConstraintsQuery, schemas);
109
+ const tableMap = /* @__PURE__ */ new Map();
110
+ for (const row of columnsResult.rows) {
111
+ const tableKey = `${row.table_schema}.${row.table_name}`;
112
+ if (!tableMap.has(tableKey)) tableMap.set(tableKey, {
113
+ tableName: row.table_name,
114
+ schemaName: row.table_schema,
115
+ columns: [],
116
+ checkConstraints: [],
117
+ primaryKeys: [],
118
+ uniqueConstraints: []
119
+ });
120
+ const table = tableMap.get(tableKey);
121
+ const isArray = row.data_type === "ARRAY";
122
+ table.columns.push({
123
+ columnName: row.column_name,
124
+ dataType: row.data_type,
125
+ isNullable: row.is_nullable === "YES",
126
+ columnDefault: row.column_default,
127
+ characterMaximumLength: row.character_maximum_length,
128
+ numericPrecision: row.numeric_precision,
129
+ numericScale: row.numeric_scale,
130
+ datetimePrecision: row.datetime_precision,
131
+ udtName: row.udt_name,
132
+ domainName: row.domain_name,
133
+ arrayDimensions: row.array_dimensions || 0,
134
+ isArray
135
+ });
136
+ }
137
+ for (const row of constraintsResult.rows) {
138
+ const tableKey = `${row.table_schema}.${row.table_name}`;
139
+ const table = tableMap.get(tableKey);
140
+ if (table) table.checkConstraints.push({
141
+ constraintName: row.constraint_name,
142
+ checkClause: row.check_clause,
143
+ columnName: row.column_name
144
+ });
145
+ }
146
+ for (const row of primaryKeysResult.rows) {
147
+ const tableKey = `${row.table_schema}.${row.table_name}`;
148
+ const table = tableMap.get(tableKey);
149
+ if (table) table.primaryKeys.push(row.column_name);
150
+ }
151
+ for (const row of uniqueConstraintsResult.rows) {
152
+ const tableKey = `${row.table_schema}.${row.table_name}`;
153
+ const table = tableMap.get(tableKey);
154
+ if (table) table.uniqueConstraints.push({
155
+ constraintName: row.constraint_name,
156
+ columns: row.columns
157
+ });
158
+ }
159
+ let tables = Array.from(tableMap.values());
160
+ if (options.tables) tables = tables.filter((t) => options.tables.includes(t.tableName));
161
+ if (options.excludeTables) tables = tables.filter((t) => !options.excludeTables.includes(t.tableName));
162
+ return tables;
163
+ }
164
+ /**
165
+ * Introspect enum types
166
+ */
167
+ async function introspectEnums(pool, schemas) {
168
+ const query = `
169
+ SELECT
170
+ t.typname as enum_name,
171
+ n.nspname as schema_name,
172
+ array_agg(e.enumlabel ORDER BY e.enumsortorder) as enum_values
173
+ FROM pg_type t
174
+ JOIN pg_enum e ON t.oid = e.enumtypid
175
+ JOIN pg_namespace n ON t.typnamespace = n.oid
176
+ WHERE n.nspname IN (${schemas.map((_, i) => `$${i + 1}`).join(", ")})
177
+ GROUP BY t.typname, n.nspname
178
+ ORDER BY t.typname
179
+ `;
180
+ return (await pool.query(query, schemas)).rows.map((row) => {
181
+ let enumValues;
182
+ if (Array.isArray(row.enum_values)) enumValues = row.enum_values;
183
+ else if (typeof row.enum_values === "string") if (row.enum_values.startsWith("{") && row.enum_values.endsWith("}")) enumValues = row.enum_values.slice(1, -1).split(",").map((v) => v.trim());
184
+ else enumValues = [row.enum_values];
185
+ else enumValues = [row.enum_values];
186
+ return {
187
+ enumName: row.enum_name,
188
+ enumValues,
189
+ schemaName: row.schema_name
190
+ };
191
+ });
192
+ }
193
+ /**
194
+ * Introspect composite types
195
+ */
196
+ async function introspectCompositeTypes(pool, schemas) {
197
+ const query = `
198
+ SELECT
199
+ t.typname as type_name,
200
+ n.nspname as schema_name,
201
+ a.attname as attribute_name,
202
+ a.attnum as attribute_number,
203
+ format_type(a.atttypid, a.atttypmod) as data_type
204
+ FROM pg_type t
205
+ JOIN pg_namespace n ON t.typnamespace = n.oid
206
+ JOIN pg_class c ON t.typrelid = c.oid
207
+ JOIN pg_attribute a ON c.oid = a.attrelid
208
+ WHERE t.typtype = 'c'
209
+ AND n.nspname IN (${schemas.map((_, i) => `$${i + 1}`).join(", ")})
210
+ AND a.attnum > 0
211
+ AND NOT a.attisdropped
212
+ ORDER BY t.typname, a.attnum
213
+ `;
214
+ const result = await pool.query(query, schemas);
215
+ const typeMap = /* @__PURE__ */ new Map();
216
+ for (const row of result.rows) {
217
+ const typeKey = `${row.schema_name}.${row.type_name}`;
218
+ if (!typeMap.has(typeKey)) typeMap.set(typeKey, {
219
+ typeName: row.type_name,
220
+ schemaName: row.schema_name,
221
+ attributes: []
222
+ });
223
+ typeMap.get(typeKey).attributes.push({
224
+ attributeName: row.attribute_name,
225
+ dataType: row.data_type,
226
+ attributeNumber: row.attribute_number
227
+ });
228
+ }
229
+ return Array.from(typeMap.values());
230
+ }
231
+ /**
232
+ * Introspect range types
233
+ */
234
+ async function introspectRangeTypes(pool, schemas) {
235
+ const query = `
236
+ SELECT
237
+ t.typname as range_name,
238
+ n.nspname as schema_name,
239
+ format_type(r.rngsubtype, NULL) as subtype
240
+ FROM pg_type t
241
+ JOIN pg_namespace n ON t.typnamespace = n.oid
242
+ JOIN pg_range r ON t.oid = r.rngtypid
243
+ WHERE n.nspname IN (${schemas.map((_, i) => `$${i + 1}`).join(", ")})
244
+ ORDER BY t.typname
245
+ `;
246
+ return (await pool.query(query, schemas)).rows.map((row) => ({
247
+ rangeName: row.range_name,
248
+ subtype: row.subtype,
249
+ schemaName: row.schema_name
250
+ }));
251
+ }
252
+ /**
253
+ * Introspect domain types
254
+ */
255
+ async function introspectDomains(pool, schemas) {
256
+ const schemaFilter = schemas.map((_, i) => `$${i + 1}`).join(", ");
257
+ const domainsQuery = `
258
+ SELECT
259
+ t.typname as domain_name,
260
+ n.nspname as schema_name,
261
+ format_type(t.typbasetype, t.typtypmod) as data_type,
262
+ t.typnotnull as is_not_null,
263
+ t.typdefault as domain_default,
264
+ information_schema._pg_char_max_length(t.typbasetype, t.typtypmod) as character_maximum_length,
265
+ information_schema._pg_numeric_precision(t.typbasetype, t.typtypmod) as numeric_precision,
266
+ information_schema._pg_numeric_scale(t.typbasetype, t.typtypmod) as numeric_scale
267
+ FROM pg_type t
268
+ JOIN pg_namespace n ON t.typnamespace = n.oid
269
+ WHERE t.typtype = 'd'
270
+ AND n.nspname IN (${schemaFilter})
271
+ ORDER BY t.typname
272
+ `;
273
+ const domainsResult = await pool.query(domainsQuery, schemas);
274
+ const constraintsQuery = `
275
+ SELECT
276
+ t.typname as domain_name,
277
+ n.nspname as schema_name,
278
+ c.conname as constraint_name,
279
+ pg_get_constraintdef(c.oid) as check_clause
280
+ FROM pg_constraint c
281
+ JOIN pg_type t ON c.contypid = t.oid
282
+ JOIN pg_namespace n ON t.typnamespace = n.oid
283
+ WHERE c.contype = 'c'
284
+ AND n.nspname IN (${schemaFilter})
285
+ `;
286
+ const constraintsResult = await pool.query(constraintsQuery, schemas);
287
+ const domains = domainsResult.rows.map((row) => ({
288
+ domainName: row.domain_name,
289
+ dataType: row.data_type,
290
+ schemaName: row.schema_name,
291
+ characterMaximumLength: row.character_maximum_length,
292
+ numericPrecision: row.numeric_precision,
293
+ numericScale: row.numeric_scale,
294
+ isNullable: !row.is_not_null,
295
+ domainDefault: row.domain_default,
296
+ checkConstraints: []
297
+ }));
298
+ for (const row of constraintsResult.rows) {
299
+ const domain = domains.find((d) => d.domainName === row.domain_name && d.schemaName === row.schema_name);
300
+ if (domain) domain.checkConstraints.push({
301
+ constraintName: row.constraint_name,
302
+ checkClause: row.check_clause,
303
+ columnName: null
304
+ });
305
+ }
306
+ return domains;
307
+ }
308
+
309
+ //#endregion
310
+ //#region src/type-mapper.ts
311
+ /**
312
+ * Map PostgreSQL column to Zod schema string with strict validation
313
+ */
314
+ function mapColumnToZod(column, metadata, options, warnings) {
315
+ if (column.domainName) {
316
+ const domain = metadata.domains.find((d) => d.domainName === column.domainName);
317
+ if (domain) {
318
+ let schema = `${toPascalCase(domain.schemaName)}${toPascalCase(domain.domainName)}Schema`;
319
+ return column.isNullable ? `${schema}.nullable()` : schema;
320
+ }
321
+ }
322
+ if (column.isArray) {
323
+ let arraySchema = `z.array(${mapBaseTypeToZod(column, metadata, options, warnings)})`;
324
+ if (column.arrayDimensions > 1) for (let i = 1; i < column.arrayDimensions; i++) arraySchema = `z.array(${arraySchema})`;
325
+ return column.isNullable ? `${arraySchema}.nullable()` : arraySchema;
326
+ }
327
+ const baseSchema = mapBaseTypeToZod(column, metadata, options, warnings);
328
+ return column.isNullable ? `${baseSchema}.nullable()` : baseSchema;
329
+ }
330
+ /**
331
+ * Map base PostgreSQL type to Zod
332
+ */
333
+ function mapBaseTypeToZod(column, metadata, options, warnings) {
334
+ let udtName = column.udtName;
335
+ const dataType = column.dataType.toLowerCase();
336
+ if (column.isArray && udtName.startsWith("_")) udtName = udtName.substring(1);
337
+ if (options.customTypeMappings?.[udtName]) return options.customTypeMappings[udtName];
338
+ const enumType = metadata.enums.find((e) => e.enumName === udtName);
339
+ if (enumType) return `${toPascalCase(enumType.schemaName)}${toPascalCase(enumType.enumName)}Schema`;
340
+ const compositeType = metadata.compositeTypes.find((t) => t.typeName === udtName);
341
+ if (compositeType) return `${toPascalCase(compositeType.schemaName)}${toPascalCase(compositeType.typeName)}CompositeSchema`;
342
+ const rangeType = metadata.rangeTypes.find((r) => r.rangeName === udtName);
343
+ if (rangeType) return `${toPascalCase(rangeType.schemaName)}${toPascalCase(rangeType.rangeName)}Schema`;
344
+ switch (dataType === "array" ? udtName : dataType) {
345
+ case "smallint":
346
+ case "integer":
347
+ case "int":
348
+ case "int2":
349
+ case "int4": return "z.number().int()";
350
+ case "bigint":
351
+ case "int8": return "z.bigint()";
352
+ case "decimal":
353
+ case "numeric":
354
+ if (column.numericPrecision !== null && column.numericScale !== null) return `z.number() /* precision: ${column.numericPrecision}, scale: ${column.numericScale} */`;
355
+ return "z.number()";
356
+ case "real":
357
+ case "float4": return "z.number()";
358
+ case "double precision":
359
+ case "float8": return "z.number()";
360
+ case "money": return "z.string().regex(/^\\$?[0-9,]+(\\.\\d{2})?$/)";
361
+ case "character varying":
362
+ case "varchar":
363
+ if (column.characterMaximumLength) return `z.string().max(${column.characterMaximumLength})`;
364
+ return "z.string()";
365
+ case "character":
366
+ case "char":
367
+ if (column.characterMaximumLength) return `z.string().length(${column.characterMaximumLength})`;
368
+ return "z.string()";
369
+ case "text": return "z.string()";
370
+ case "citext": return "z.string()";
371
+ case "boolean":
372
+ case "bool": return "z.boolean()";
373
+ case "timestamp":
374
+ case "timestamp without time zone": return "z.date()";
375
+ case "timestamp with time zone":
376
+ case "timestamptz": return "z.date()";
377
+ case "date": return "z.date()";
378
+ case "time":
379
+ case "time without time zone": return "z.iso.time()";
380
+ case "time with time zone":
381
+ case "timetz": return "z.string().regex(/^\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?[+-]\\d{2}:\\d{2}$/)";
382
+ case "interval": return "z.iso.duration()";
383
+ case "uuid": return "z.uuid()";
384
+ case "json": return "z.record(z.string(), z.unknown())";
385
+ case "jsonb": return "z.record(z.string(), z.unknown())";
386
+ case "inet": return "z.union([z.ipv4(), z.ipv6()])";
387
+ case "cidr": return "z.union([z.cidrv4(), z.cidrv6()])";
388
+ case "macaddr": return "z.mac()";
389
+ case "macaddr8": return "z.string().regex(/^([0-9A-Fa-f]{2}[:-]){7}([0-9A-Fa-f]{2})$/)";
390
+ case "bit":
391
+ if (column.characterMaximumLength) return `z.string().regex(/^[01]{${column.characterMaximumLength}}$/)`;
392
+ return "z.string().regex(/^[01]+$/)";
393
+ case "bit varying":
394
+ case "varbit":
395
+ if (column.characterMaximumLength) return `z.string().regex(/^[01]{0,${column.characterMaximumLength}}$/)`;
396
+ return "z.string().regex(/^[01]*$/)";
397
+ case "point": return "z.tuple([z.number(), z.number()])";
398
+ case "line": return "z.object({ a: z.number(), b: z.number(), c: z.number() })";
399
+ case "lseg": return "z.tuple([z.tuple([z.number(), z.number()]), z.tuple([z.number(), z.number()])])";
400
+ case "box": return "z.tuple([z.tuple([z.number(), z.number()]), z.tuple([z.number(), z.number()])])";
401
+ case "path": return "z.array(z.tuple([z.number(), z.number()]))";
402
+ case "polygon": return "z.array(z.tuple([z.number(), z.number()]))";
403
+ case "circle": return "z.object({ center: z.tuple([z.number(), z.number()]), radius: z.number() })";
404
+ case "tsvector": return "z.string() /* tsvector */";
405
+ case "tsquery": return "z.string() /* tsquery */";
406
+ case "xml": return "z.string() /* XML */";
407
+ case "bytea": return "z.instanceof(Buffer)";
408
+ case "oid": return "z.number().int().positive()";
409
+ case "regproc":
410
+ case "regprocedure":
411
+ case "regoper":
412
+ case "regoperator":
413
+ case "regclass":
414
+ case "regtype":
415
+ case "regrole":
416
+ case "regnamespace":
417
+ case "regconfig":
418
+ case "regdictionary": return "z.string() /* PostgreSQL OID reference */";
419
+ case "pg_lsn": return "z.string().regex(/^[0-9A-F]+\\/[0-9A-F]+$/)";
420
+ default:
421
+ const warning = `Unknown type: ${dataType} (udt: ${udtName}) in column ${column.columnName}`;
422
+ warnings.push(warning);
423
+ if (options.strictMode) throw new Error(warning);
424
+ return "z.unknown() /* unmapped type */";
425
+ }
426
+ }
427
+ /**
428
+ * Apply check constraints as Zod refinements
429
+ */
430
+ function applyCheckConstraints(columnName, baseSchema, constraints) {
431
+ let schema = baseSchema;
432
+ for (const constraint of constraints) {
433
+ const checkClause = constraint.checkClause.toLowerCase();
434
+ const geMatch = checkClause.match(/* @__PURE__ */ new RegExp(`${columnName}\\s*>=\\s*([\\d.]+)`));
435
+ if (geMatch) {
436
+ const value = geMatch[1];
437
+ if (schema.includes("z.number()")) schema = schema.replace("z.number()", `z.number().min(${value})`);
438
+ else if (schema.includes("z.bigint()")) schema = schema.replace("z.bigint()", `z.bigint().min(${value}n)`);
439
+ continue;
440
+ }
441
+ const gtMatch = checkClause.match(/* @__PURE__ */ new RegExp(`${columnName}\\s*>\\s*([\\d.]+)`));
442
+ if (gtMatch) {
443
+ const value = parseFloat(gtMatch[1]);
444
+ if (schema.includes("z.number()")) schema = schema.replace("z.number()", `z.number().min(${value + Number.EPSILON})`);
445
+ continue;
446
+ }
447
+ const leMatch = checkClause.match(/* @__PURE__ */ new RegExp(`${columnName}\\s*<=\\s*([\\d.]+)`));
448
+ if (leMatch) {
449
+ const value = leMatch[1];
450
+ if (schema.includes("z.number()")) schema = schema.replace("z.number()", `z.number().max(${value})`);
451
+ else if (schema.includes("z.bigint()")) schema = schema.replace("z.bigint()", `z.bigint().max(${value}n)`);
452
+ continue;
453
+ }
454
+ const ltMatch = checkClause.match(/* @__PURE__ */ new RegExp(`${columnName}\\s*<\\s*([\\d.]+)`));
455
+ if (ltMatch) {
456
+ const value = parseFloat(ltMatch[1]);
457
+ if (schema.includes("z.number()")) schema = schema.replace("z.number()", `z.number().max(${value - Number.EPSILON})`);
458
+ continue;
459
+ }
460
+ const betweenMatch = checkClause.match(/* @__PURE__ */ new RegExp(`${columnName}\\s*between\\s*([\\d.]+)\\s*and\\s*([\\d.]+)`));
461
+ if (betweenMatch) {
462
+ const [, min, max] = betweenMatch;
463
+ if (schema.includes("z.number()")) schema = schema.replace("z.number()", `z.number().min(${min}).max(${max})`);
464
+ continue;
465
+ }
466
+ const inMatch = checkClause.match(/* @__PURE__ */ new RegExp(`${columnName}\\s*in\\s*\\(([^)]+)\\)`));
467
+ if (inMatch) {
468
+ const values = inMatch[1].split(",").map((v) => v.trim().replace(/'/g, ""));
469
+ if (schema.includes("z.string()")) schema = `z.enum([${values.map((v) => `'${v}'`).join(", ")}])`;
470
+ continue;
471
+ }
472
+ const anyArrayMatch = checkClause.match(/* @__PURE__ */ new RegExp(`\\(?${columnName}\\s*=\\s*any\\s*\\(array\\[([^\\]]+)\\]`));
473
+ if (anyArrayMatch) {
474
+ const values = anyArrayMatch[1].split(",").map((v) => {
475
+ const match = v.trim().match(/'([^']+)'/);
476
+ return match ? match[1] : null;
477
+ }).filter((v) => v !== null);
478
+ if (values.length > 0 && schema.includes("z.string()")) {
479
+ schema = `z.enum([${values.map((v) => `'${v}'`).join(", ")}])`;
480
+ continue;
481
+ }
482
+ }
483
+ const regexMatch = checkClause.match(/* @__PURE__ */ new RegExp(`${columnName}\\s*~\\s*'([^']+)'`));
484
+ if (regexMatch) {
485
+ const pattern = regexMatch[1];
486
+ if (schema.includes("z.string()")) schema = schema.replace("z.string()", `z.string().regex(/${pattern}/)`);
487
+ continue;
488
+ }
489
+ const lengthMatch = checkClause.match(/* @__PURE__ */ new RegExp(`length\\(${columnName}\\)\\s*([><=]+)\\s*([\\d]+)`));
490
+ if (lengthMatch) {
491
+ const [, operator, value] = lengthMatch;
492
+ if (schema.includes("z.string()")) {
493
+ if (operator === ">=" || operator === ">") schema = schema.replace("z.string()", `z.string().min(${value})`);
494
+ else if (operator === "<=" || operator === "<") schema = schema.replace("z.string()", `z.string().max(${value})`);
495
+ }
496
+ continue;
497
+ }
498
+ schema += ` /* CHECK: ${constraint.checkClause} */`;
499
+ }
500
+ return schema;
501
+ }
502
+ /**
503
+ * Convert snake_case to PascalCase
504
+ */
505
+ function toPascalCase(str) {
506
+ return str.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
507
+ }
508
+ /**
509
+ * Convert snake_case to camelCase
510
+ */
511
+ function toCamelCase(str) {
512
+ const pascal = toPascalCase(str);
513
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
514
+ }
515
+
516
+ //#endregion
517
+ //#region src/generator.ts
518
+ /**
519
+ * Generate Zod schemas from database metadata
520
+ */
521
+ function generateSchemas(metadata, options = {}) {
522
+ const warnings = [];
523
+ const schemas = [];
524
+ const enums = metadata.enums.map((enumType) => ({
525
+ name: toPascalCase(enumType.schemaName) + toPascalCase(enumType.enumName),
526
+ code: generateEnumSchema(enumType.enumName, enumType.enumValues, options, enumType.schemaName)
527
+ }));
528
+ const ranges = metadata.rangeTypes.map((rangeType) => ({
529
+ name: toPascalCase(rangeType.schemaName) + toPascalCase(rangeType.rangeName),
530
+ code: generateRangeSchema(rangeType.rangeName, rangeType.subtype, metadata, options, warnings, rangeType.schemaName)
531
+ }));
532
+ const compositeTypes = options.includeCompositeTypes ? metadata.compositeTypes.map((compositeType) => ({
533
+ name: toPascalCase(compositeType.schemaName) + toPascalCase(compositeType.typeName) + "Composite",
534
+ code: generateCompositeTypeSchema(compositeType, metadata, options, warnings)
535
+ })) : [];
536
+ const domains = metadata.domains.map((domain) => ({
537
+ name: toPascalCase(domain.schemaName) + toPascalCase(domain.domainName),
538
+ code: generateDomainSchema(domain, metadata, options, warnings)
539
+ }));
540
+ for (const table of metadata.tables) {
541
+ const schema = generateTableSchema(table, metadata, options, warnings);
542
+ schemas.push(schema);
543
+ }
544
+ return {
545
+ schemas,
546
+ enums,
547
+ compositeTypes,
548
+ domains,
549
+ ranges,
550
+ warnings
551
+ };
552
+ }
553
+ /**
554
+ * Generate enum schema
555
+ */
556
+ function generateEnumSchema(enumName, values, options, schemaPrefix) {
557
+ const baseName = toPascalCase(enumName);
558
+ const fullName = schemaPrefix ? `${toPascalCase(schemaPrefix)}${baseName}` : baseName;
559
+ const schemaName = `${fullName}Schema`;
560
+ const typeName = fullName;
561
+ const valuesStr = values.map((v) => `'${v}'`).join(", ");
562
+ let code = "";
563
+ if (options.includeComments) code += `/** PostgreSQL enum: ${enumName} */\n`;
564
+ code += `export const ${schemaName} = z.enum([${valuesStr}]);\n`;
565
+ code += `export type ${typeName} = z.infer<typeof ${schemaName}>;\n`;
566
+ return code;
567
+ }
568
+ /**
569
+ * Generate range type schema
570
+ */
571
+ function generateRangeSchema(rangeName, subtype, _metadata, options, _warnings, schemaPrefix) {
572
+ const baseName = toPascalCase(rangeName);
573
+ const fullName = schemaPrefix ? `${toPascalCase(schemaPrefix)}${baseName}` : baseName;
574
+ const schemaName = `${fullName}Schema`;
575
+ const typeName = fullName;
576
+ const subtypeSchema = mapSubtypeToZod(subtype, _metadata);
577
+ let code = "";
578
+ if (options.includeComments) code += `/** PostgreSQL range type: ${rangeName}<${subtype}> */\n`;
579
+ code += `export const ${schemaName} = z.tuple([${subtypeSchema}.nullable(), ${subtypeSchema}.nullable()]);\n`;
580
+ code += `export type ${typeName} = z.infer<typeof ${schemaName}>;\n`;
581
+ return code;
582
+ }
583
+ /**
584
+ * Map PostgreSQL subtype to Zod for range types
585
+ */
586
+ function mapSubtypeToZod(subtype, _metadata) {
587
+ switch (subtype.toLowerCase()) {
588
+ case "integer":
589
+ case "int":
590
+ case "int4": return "z.number().int()";
591
+ case "bigint":
592
+ case "int8": return "z.bigint()";
593
+ case "numeric":
594
+ case "decimal": return "z.number()";
595
+ case "date": return "z.date()";
596
+ case "timestamp":
597
+ case "timestamp without time zone":
598
+ case "timestamp with time zone":
599
+ case "timestamptz": return "z.date()";
600
+ default: return "z.unknown()";
601
+ }
602
+ }
603
+ /**
604
+ * Generate composite type schema
605
+ */
606
+ function generateCompositeTypeSchema(compositeType, metadata, options, warnings) {
607
+ const baseName = toPascalCase(compositeType.typeName);
608
+ const fullName = `${toPascalCase(compositeType.schemaName)}${baseName}Composite`;
609
+ const schemaName = `${fullName}Schema`;
610
+ const typeName = fullName;
611
+ let code = "";
612
+ if (options.includeComments) code += `/** PostgreSQL composite type: ${compositeType.typeName} */\n`;
613
+ code += `export const ${schemaName} = z.object({\n`;
614
+ for (const attr of compositeType.attributes) {
615
+ const fieldName = options.useCamelCase ? toCamelCase(attr.attributeName) : attr.attributeName;
616
+ const zodType = mapColumnToZod({
617
+ columnName: attr.attributeName,
618
+ dataType: attr.dataType,
619
+ isNullable: true,
620
+ columnDefault: null,
621
+ characterMaximumLength: null,
622
+ numericPrecision: null,
623
+ numericScale: null,
624
+ datetimePrecision: null,
625
+ udtName: attr.dataType,
626
+ domainName: null,
627
+ arrayDimensions: 0,
628
+ isArray: false
629
+ }, metadata, options, warnings);
630
+ if (options.includeComments) code += ` /** ${attr.dataType} */\n`;
631
+ code += ` ${fieldName}: ${zodType},\n`;
632
+ }
633
+ code += `});\n`;
634
+ code += `export type ${typeName} = z.infer<typeof ${schemaName}>;\n`;
635
+ return code;
636
+ }
637
+ /**
638
+ * Generate domain schema
639
+ */
640
+ function generateDomainSchema(domain, metadata, options, warnings) {
641
+ const baseName = toPascalCase(domain.domainName);
642
+ const fullName = `${toPascalCase(domain.schemaName)}${baseName}`;
643
+ const schemaName = `${fullName}Schema`;
644
+ const typeName = fullName;
645
+ let zodType = mapColumnToZod({
646
+ columnName: domain.domainName,
647
+ dataType: domain.dataType,
648
+ isNullable: domain.isNullable,
649
+ columnDefault: domain.domainDefault,
650
+ characterMaximumLength: domain.characterMaximumLength,
651
+ numericPrecision: domain.numericPrecision,
652
+ numericScale: domain.numericScale,
653
+ datetimePrecision: null,
654
+ udtName: domain.dataType,
655
+ domainName: null,
656
+ arrayDimensions: 0,
657
+ isArray: false
658
+ }, metadata, options, warnings);
659
+ if (domain.checkConstraints.length > 0) zodType = applyCheckConstraints(domain.domainName, zodType, domain.checkConstraints);
660
+ let code = "";
661
+ if (options.includeComments) code += `/** PostgreSQL domain: ${domain.domainName} (base: ${domain.dataType}) */\n`;
662
+ code += `export const ${schemaName} = ${zodType};\n`;
663
+ code += `export type ${typeName} = z.infer<typeof ${schemaName}>;\n`;
664
+ return code;
665
+ }
666
+ /**
667
+ * Generate table schema
668
+ */
669
+ function generateTableSchema(table, metadata, options, warnings) {
670
+ const schemaName = `${toPascalCase(table.schemaName)}${toPascalCase(table.tableName)}`;
671
+ const readSchemaName = `${schemaName}Schema`;
672
+ const insertSchemaName = `${schemaName}InsertSchema`;
673
+ const updateSchemaName = `${schemaName}UpdateSchema`;
674
+ const typeName = schemaName;
675
+ const insertTypeName = `${schemaName}Insert`;
676
+ const updateTypeName = `${schemaName}Update`;
677
+ let readCode = "";
678
+ if (options.includeComments) readCode += `/** Table: ${table.schemaName}.${table.tableName} */\n`;
679
+ readCode += `export const ${readSchemaName} = z.object({\n`;
680
+ for (const column of table.columns) {
681
+ const fieldName = options.useCamelCase ? toCamelCase(column.columnName) : column.columnName;
682
+ let zodType = mapColumnToZod(column, metadata, options, warnings);
683
+ const columnConstraints = table.checkConstraints.filter((c) => c.columnName === column.columnName);
684
+ if (columnConstraints.length > 0) zodType = applyCheckConstraints(column.columnName, zodType, columnConstraints);
685
+ if (options.includeComments) {
686
+ const commentParts = [column.dataType];
687
+ if (column.columnDefault) commentParts.push(`default: ${column.columnDefault}`);
688
+ readCode += ` /** ${commentParts.join(", ")} */\n`;
689
+ }
690
+ readCode += ` ${fieldName}: ${zodType},\n`;
691
+ }
692
+ readCode += `});\n`;
693
+ readCode += `export type ${typeName} = z.infer<typeof ${readSchemaName}>;\n`;
694
+ let insertCode;
695
+ let updateCode;
696
+ if (options.generateInputSchemas !== false) {
697
+ const optionalFields = [];
698
+ for (const column of table.columns) {
699
+ const fieldName = options.useCamelCase ? toCamelCase(column.columnName) : column.columnName;
700
+ const hasDefault = column.columnDefault !== null;
701
+ const isSerial = column.columnDefault?.includes("nextval") ?? false;
702
+ if (isSerial || column.columnDefault?.includes("gen_random_uuid()") || hasDefault) {
703
+ let zodType = mapColumnToZod(column, metadata, options, warnings);
704
+ const columnConstraints = table.checkConstraints.filter((c) => c.columnName === column.columnName);
705
+ if (columnConstraints.length > 0) zodType = applyCheckConstraints(column.columnName, zodType, columnConstraints);
706
+ zodType = `${zodType}.optional()`;
707
+ let comment;
708
+ if (options.includeComments) {
709
+ const commentParts = [column.dataType];
710
+ if (hasDefault) commentParts.push(`default: ${column.columnDefault}`);
711
+ if (isSerial) commentParts.push("auto-generated");
712
+ comment = commentParts.join(", ");
713
+ }
714
+ optionalFields.push({
715
+ fieldName,
716
+ zodType,
717
+ comment
718
+ });
719
+ }
720
+ }
721
+ insertCode = "";
722
+ if (options.includeComments) insertCode += `/** Insert schema for ${table.tableName} */\n`;
723
+ if (optionalFields.length === 0) insertCode += `export const ${insertSchemaName} = ${readSchemaName};\n`;
724
+ else {
725
+ insertCode += `export const ${insertSchemaName} = ${readSchemaName}.extend({\n`;
726
+ for (const field of optionalFields) {
727
+ if (field.comment) insertCode += ` /** ${field.comment} */\n`;
728
+ insertCode += ` ${field.fieldName}: ${field.zodType},\n`;
729
+ }
730
+ insertCode += `});\n`;
731
+ }
732
+ insertCode += `export type ${insertTypeName} = z.infer<typeof ${insertSchemaName}>;\n`;
733
+ updateCode = "";
734
+ if (options.includeComments) updateCode += `/** Update schema for ${table.tableName} (all fields optional) */\n`;
735
+ updateCode += `export const ${updateSchemaName} = ${readSchemaName}.partial();\n`;
736
+ updateCode += `export type ${updateTypeName} = z.infer<typeof ${updateSchemaName}>;\n`;
737
+ }
738
+ return {
739
+ tableName: table.tableName,
740
+ schemaName: table.schemaName,
741
+ readSchema: readCode,
742
+ inputSchema: insertCode,
743
+ typeDefinitions: `${readCode}${insertCode ? "\n" + insertCode : ""}${updateCode ? "\n" + updateCode : ""}`
744
+ };
745
+ }
746
+ /**
747
+ * Format the complete output file
748
+ */
749
+ function formatOutput(result) {
750
+ let output = `/**\n`;
751
+ output += ` * ==========================================\n`;
752
+ output += ` * | GENERATED BY PG-TO-ZOD (TBP) |\n`;
753
+ output += ` * ==========================================\n`;
754
+ output += ` *\n`;
755
+ output += ` * ⚠️ DO NOT EDIT THIS FILE MANUALLY!\n`;
756
+ output += ` *\n`;
757
+ output += ` * This file was automatically generated from\n`;
758
+ output += ` * your PostgreSQL database schema.\n`;
759
+ output += ` *\n`;
760
+ output += ` * To regenerate, run:\n`;
761
+ output += ` * pg-to-zod --url <connection-url> -o <file>\n`;
762
+ output += ` *\n`;
763
+ output += ` * Any manual changes will be overwritten when\n`;
764
+ output += ` * the code is regenerated.\n`;
765
+ output += ` * ==========================================\n`;
766
+ output += ` */\n\n`;
767
+ output += `import { z } from 'zod';\n\n`;
768
+ if (result.enums.length > 0) {
769
+ output += `// ============================================\n`;
770
+ output += `// Enums\n`;
771
+ output += `// ============================================\n\n`;
772
+ for (const enumSchema of result.enums) output += enumSchema.code + "\n";
773
+ }
774
+ if (result.domains.length > 0) {
775
+ output += `// ============================================\n`;
776
+ output += `// Domains\n`;
777
+ output += `// ============================================\n\n`;
778
+ for (const domain of result.domains) output += domain.code + "\n";
779
+ }
780
+ if (result.ranges.length > 0) {
781
+ output += `// ============================================\n`;
782
+ output += `// Range Types\n`;
783
+ output += `// ============================================\n\n`;
784
+ for (const range of result.ranges) output += range.code + "\n";
785
+ }
786
+ if (result.compositeTypes.length > 0) {
787
+ output += `// ============================================\n`;
788
+ output += `// Composite Types\n`;
789
+ output += `// ============================================\n\n`;
790
+ for (const compositeType of result.compositeTypes) output += compositeType.code + "\n";
791
+ }
792
+ if (result.schemas.length > 0) {
793
+ output += `// ============================================\n`;
794
+ output += `// Tables\n`;
795
+ output += `// ============================================\n\n`;
796
+ for (const schema of result.schemas) output += schema.typeDefinitions + "\n";
797
+ }
798
+ if (result.warnings.length > 0) {
799
+ output += `// ============================================\n`;
800
+ output += `// Warnings\n`;
801
+ output += `// ============================================\n`;
802
+ output += `// The following warnings were generated:\n`;
803
+ for (const warning of result.warnings) output += `// - ${warning}\n`;
804
+ }
805
+ return output;
806
+ }
807
+
808
+ //#endregion
809
+ //#region src/index.ts
810
+ /**
811
+ * Main function: introspect database and generate Zod schemas
812
+ */
813
+ async function generateZodSchemas(config, options = {}) {
814
+ return generateSchemas(await introspectDatabase(config, options), options);
815
+ }
816
+ /**
817
+ * Convenience function: generate schemas and return formatted output string
818
+ */
819
+ async function generateZodSchemasString(config, options = {}) {
820
+ return formatOutput(await generateZodSchemas(config, options));
821
+ }
822
+ /**
823
+ * Default export
824
+ */
825
+ var src_default = {
826
+ generateZodSchemas,
827
+ generateZodSchemasString,
828
+ introspectDatabase,
829
+ generateSchemas,
830
+ formatOutput
831
+ };
832
+
833
+ //#endregion
834
+ export { generateSchemas as a, toPascalCase as c, formatOutput as i, introspectDatabase as l, generateZodSchemasString as n, mapColumnToZod as o, src_default as r, toCamelCase as s, generateZodSchemas as t };
835
+ //# sourceMappingURL=src-18pV45Fu.js.map