UNPKG

@datazod/zod-sql

Version:

Convert Zod schemas to SQL table definitions with support for SQLite, PostgreSQL, and MySQL

102 lines (101 loc) 4.8 kB
import { z } from 'zod'; import { mapZodToSql } from '../../maps'; import { buildColumnDefinition, getAutoIdColumn, getTimestampColumns, isInteger, isNullable, processNestedObject, quoteIdentifier } from '../../utils'; /** * Creates a SQL DDL statement from a Zod schema */ export function createTableDDL(tableName, schema, options = {}) { const { dialect = 'sqlite', primaryKey, indexes = {}, flattenDepth = 2, extraColumns = [], timestamps = false, autoId = false } = options; const shape = schema.shape; const cols = []; const constraints = []; // Prepare columns arrays for different positions const startCols = []; const mainCols = []; const endCols = []; // Add auto ID column if requested (always at the start) if (autoId) { const autoIdColumnDef = getAutoIdColumn(dialect, autoId); if (autoIdColumnDef) { startCols.push(autoIdColumnDef); } } // Add timestamp columns if requested (after ID but before other columns) if (timestamps) { startCols.push(...getTimestampColumns(dialect)); } // Process extra columns for 'start' position for (const column of extraColumns) { if (column.position === 'start') { startCols.push(buildColumnDefinition(column, dialect)); } } // Process each field in the schema for (const [key, type] of Object.entries(shape)) { // Unwrap nullable/optional to get the correct SQL type let unwrappedType = type; if (unwrappedType instanceof z.ZodNullable || unwrappedType instanceof z.ZodOptional) { unwrappedType = unwrappedType.unwrap(); } if (unwrappedType instanceof z.ZodObject && flattenDepth > 0) { // Flatten nested object processNestedObject(key, unwrappedType, mainCols, flattenDepth, dialect); } else if (unwrappedType instanceof z.ZodNumber) { // Explicitly handle numbers to ensure proper type mapping const isInt = isInteger(unwrappedType); let sqlType; switch (dialect) { case 'postgres': sqlType = isInt ? 'INTEGER' : 'DOUBLE PRECISION'; break; case 'mysql': sqlType = isInt ? 'INT' : 'DOUBLE'; break; case 'sqlite': default: sqlType = isInt ? 'INTEGER' : 'REAL'; } const nullable = isNullable(type) ? '' : ' NOT NULL'; // Add PRIMARY KEY directly to the column if it's a single column primary key const isPrimaryKey = primaryKey === key && !Array.isArray(primaryKey); const pkStr = isPrimaryKey ? ' PRIMARY KEY' : ''; mainCols.push(`${quoteIdentifier(key, dialect)} ${sqlType}${nullable}${pkStr}`); } else { // Handle other types including arrays const sqlType = mapZodToSql(unwrappedType, dialect); const nullable = isNullable(type) ? '' : ' NOT NULL'; // Add PRIMARY KEY directly to the column if it's a single column primary key const isPrimaryKey = primaryKey === key && !Array.isArray(primaryKey); const pkStr = isPrimaryKey ? ' PRIMARY KEY' : ''; mainCols.push(`${quoteIdentifier(key, dialect)} ${sqlType}${nullable}${pkStr}`); } } // Process extra columns for 'end' position or unspecified position (default to end) for (const column of extraColumns) { if (!column.position || column.position === 'end') { endCols.push(buildColumnDefinition(column, dialect)); } } // Combine all columns in the correct order: start, main, end cols.push(...startCols, ...mainCols, ...endCols); // Add compound primary key constraint if specified (and not already set in a column) const hasPrimaryKeyColumn = extraColumns.some((col) => col.primaryKey) || (primaryKey && !Array.isArray(primaryKey)) || autoId; if (primaryKey && Array.isArray(primaryKey) && !hasPrimaryKeyColumn) { constraints.push(`PRIMARY KEY (${primaryKey.map((k) => quoteIdentifier(k, dialect)).join(', ')})`); } // Generate the CREATE TABLE statement const allDefinitions = [...cols, ...constraints]; let sql = `CREATE TABLE IF NOT EXISTS ${quoteIdentifier(tableName, dialect)} (\n ${allDefinitions.join(',\n ')}\n);`; // Add indexes for (const [indexName, columns] of Object.entries(indexes)) { sql += `\nCREATE INDEX IF NOT EXISTS ${quoteIdentifier(indexName, dialect)} ON ${quoteIdentifier(tableName, dialect)} (${columns .map((c) => quoteIdentifier(c, dialect)) .join(', ')});`; } return sql; }