turbine-orm 0.11.0 → 0.13.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.
@@ -33,27 +33,21 @@ const node_fs_1 = require("node:fs");
33
33
  const node_path_1 = require("node:path");
34
34
  const pg_1 = __importDefault(require("pg"));
35
35
  const index_js_1 = require("../adapters/index.js");
36
+ const dialect_js_1 = require("../dialect.js");
36
37
  const errors_js_1 = require("../errors.js");
37
- const index_js_2 = require("../query/index.js");
38
38
  // ---------------------------------------------------------------------------
39
39
  // Tracking table management
40
40
  // ---------------------------------------------------------------------------
41
41
  const TRACKING_TABLE = '_turbine_migrations';
42
- const QUOTED_TRACKING_TABLE = (0, index_js_2.quoteIdent)(TRACKING_TABLE);
43
- const CREATE_TRACKING_TABLE = `
44
- CREATE TABLE IF NOT EXISTS ${QUOTED_TRACKING_TABLE} (
45
- id SERIAL PRIMARY KEY,
46
- name TEXT NOT NULL UNIQUE,
47
- checksum TEXT NOT NULL,
48
- applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
49
- );
50
- `;
51
- async function ensureTrackingTable(client) {
52
- await client.query(CREATE_TRACKING_TABLE);
42
+ function quotedTrackingTable(dialect) {
43
+ return dialect.quoteIdentifier(TRACKING_TABLE);
44
+ }
45
+ async function ensureTrackingTable(client, dialect = dialect_js_1.postgresDialect) {
46
+ await client.query(dialect.buildMigrationTrackingTable(quotedTrackingTable(dialect)));
53
47
  }
54
- async function getAppliedMigrations(client) {
55
- await ensureTrackingTable(client);
56
- const result = await client.query(`SELECT id, name, applied_at, checksum FROM ${QUOTED_TRACKING_TABLE} ORDER BY id ASC`);
48
+ async function getAppliedMigrations(client, dialect = dialect_js_1.postgresDialect) {
49
+ await ensureTrackingTable(client, dialect);
50
+ const result = await client.query(dialect.buildMigrationSelectApplied(quotedTrackingTable(dialect)));
57
51
  return result.rows;
58
52
  }
59
53
  // ---------------------------------------------------------------------------
@@ -265,8 +259,8 @@ async function releaseLock(client, lockId, adapter) {
265
259
  * Validate that applied migration files have not been modified or deleted since they were run.
266
260
  * Returns an array of mismatched migrations (empty if all are clean).
267
261
  */
268
- async function validateChecksums(client, migrationsDir) {
269
- const applied = await getAppliedMigrations(client);
262
+ async function validateChecksums(client, migrationsDir, dialect = dialect_js_1.postgresDialect) {
263
+ const applied = await getAppliedMigrations(client, dialect);
270
264
  const allFiles = listMigrationFiles(migrationsDir);
271
265
  const fileMap = new Map(allFiles.map((f) => [f.name, f]));
272
266
  const mismatches = [];
@@ -286,7 +280,7 @@ async function validateChecksums(client, migrationsDir) {
286
280
  if (currentHash !== migration.checksum) {
287
281
  // Auto-upgrade legacy djb2 checksums to SHA-256 without flagging as modified
288
282
  if (isLegacyChecksum(migration.checksum)) {
289
- await client.query(`UPDATE ${QUOTED_TRACKING_TABLE} SET checksum = $1 WHERE name = $2`, [
283
+ await client.query(dialect.buildMigrationUpdateChecksum(quotedTrackingTable(dialect)), [
290
284
  currentHash,
291
285
  migration.name,
292
286
  ]);
@@ -321,6 +315,7 @@ async function migrateUp(connectionString, migrationsDir, options) {
321
315
  await client.connect();
322
316
  // Treat `force` as an alias for `allowDrift` for backwards compatibility.
323
317
  const allowDrift = options?.allowDrift === true || options?.force === true;
318
+ const dialect = options?.dialect ?? dialect_js_1.postgresDialect;
324
319
  try {
325
320
  // Derive an advisory lock ID per-database so concurrent migrations in
326
321
  // sibling databases on the same Postgres cluster do not contend.
@@ -334,7 +329,7 @@ async function migrateUp(connectionString, migrationsDir, options) {
334
329
  throw new errors_js_1.MigrationError('[turbine] Could not acquire migration lock — another migration is already running');
335
330
  }
336
331
  try {
337
- await ensureTrackingTable(client);
332
+ await ensureTrackingTable(client, dialect);
338
333
  // Validate checksums of already-applied migrations.
339
334
  // Drift = an APPLIED migration's on-disk file has changed (or been deleted)
340
335
  // since it was run. Either situation means the database state and the
@@ -342,7 +337,7 @@ async function migrateUp(connectionString, migrationsDir, options) {
342
337
  // Users can pass `allowDrift: true` (CLI: `--allow-drift`) to force past
343
338
  // the block when they are intentionally rewriting history.
344
339
  if (!allowDrift) {
345
- const mismatches = await validateChecksums(client, migrationsDir);
340
+ const mismatches = await validateChecksums(client, migrationsDir, dialect);
346
341
  if (mismatches.length > 0) {
347
342
  const modified = mismatches.filter((m) => m.type === 'modified');
348
343
  const missing = mismatches.filter((m) => m.type === 'missing');
@@ -366,7 +361,7 @@ async function migrateUp(connectionString, migrationsDir, options) {
366
361
  throw new errors_js_1.MigrationError(lines.join('\n'));
367
362
  }
368
363
  }
369
- const applied = await getAppliedMigrations(client);
364
+ const applied = await getAppliedMigrations(client, dialect);
370
365
  const appliedNames = new Set(applied.map((m) => m.name));
371
366
  const allFiles = listMigrationFiles(migrationsDir);
372
367
  let pending = allFiles.filter((f) => !appliedNames.has(f.name));
@@ -386,7 +381,7 @@ async function migrateUp(connectionString, migrationsDir, options) {
386
381
  try {
387
382
  await client.query('BEGIN');
388
383
  await client.query(up);
389
- await client.query(`INSERT INTO ${QUOTED_TRACKING_TABLE} (name, checksum) VALUES ($1, $2) ON CONFLICT (name) DO NOTHING`, [file.name, hash]);
384
+ await client.query(dialect.buildMigrationInsertApplied(quotedTrackingTable(dialect)), [file.name, hash]);
390
385
  await client.query('COMMIT');
391
386
  results.push(file);
392
387
  }
@@ -419,6 +414,7 @@ async function migrateUp(connectionString, migrationsDir, options) {
419
414
  async function migrateDown(connectionString, migrationsDir, options) {
420
415
  const client = new pg_1.default.Client({ connectionString });
421
416
  await client.connect();
417
+ const dialect = options?.dialect ?? dialect_js_1.postgresDialect;
422
418
  try {
423
419
  // Derive a per-database advisory lock ID so concurrent migrations in
424
420
  // sibling databases on the same cluster do not contend.
@@ -430,8 +426,8 @@ async function migrateDown(connectionString, migrationsDir, options) {
430
426
  throw new errors_js_1.MigrationError('[turbine] Could not acquire migration lock — another migration is already running');
431
427
  }
432
428
  try {
433
- await ensureTrackingTable(client);
434
- const applied = await getAppliedMigrations(client);
429
+ await ensureTrackingTable(client, dialect);
430
+ const applied = await getAppliedMigrations(client, dialect);
435
431
  if (applied.length === 0) {
436
432
  return { rolledBack: [], errors: [] };
437
433
  }
@@ -458,7 +454,7 @@ async function migrateDown(connectionString, migrationsDir, options) {
458
454
  try {
459
455
  await client.query('BEGIN');
460
456
  await client.query(down);
461
- await client.query(`DELETE FROM ${QUOTED_TRACKING_TABLE} WHERE name = $1`, [migration.name]);
457
+ await client.query(dialect.buildMigrationDeleteApplied(quotedTrackingTable(dialect)), [migration.name]);
462
458
  await client.query('COMMIT');
463
459
  results.push(file);
464
460
  }
@@ -483,12 +479,13 @@ async function migrateDown(connectionString, migrationsDir, options) {
483
479
  * Get the status of all migrations (applied vs pending).
484
480
  * Includes checksum validation for applied migrations.
485
481
  */
486
- async function migrateStatus(connectionString, migrationsDir) {
482
+ async function migrateStatus(connectionString, migrationsDir, options) {
487
483
  const client = new pg_1.default.Client({ connectionString });
488
484
  await client.connect();
485
+ const dialect = options?.dialect ?? dialect_js_1.postgresDialect;
489
486
  try {
490
- await ensureTrackingTable(client);
491
- const applied = await getAppliedMigrations(client);
487
+ await ensureTrackingTable(client, dialect);
488
+ const applied = await getAppliedMigrations(client, dialect);
492
489
  const appliedMap = new Map(applied.map((m) => [m.name, m]));
493
490
  const allFiles = listMigrationFiles(migrationsDir);
494
491
  return allFiles.map((file) => {
@@ -33,6 +33,28 @@ exports.postgresDialect = {
33
33
  const suffix = orderBy ? ` ${orderBy}` : '';
34
34
  return `COALESCE(json_agg(${jsonObjectExpr}${suffix}), ${this.emptyJsonArrayLiteral})`;
35
35
  },
36
+ buildReturningClause(selection = '*') {
37
+ return ` RETURNING ${selection}`;
38
+ },
39
+ buildInsertStatement(input) {
40
+ return `INSERT INTO ${input.table} (${input.columns.join(', ')}) VALUES (${input.valuePlaceholders.join(', ')})${this.buildReturningClause(input.returning)}`;
41
+ },
42
+ buildBulkInsertStatement(input) {
43
+ if (!input.columnArrayTypes || input.columnArrayTypes.length !== input.columns.length) {
44
+ throw new Error('PostgreSQL bulk insert requires one array type per column');
45
+ }
46
+ const columnArrays = input.columns.map((_, columnIndex) => input.rowValues.map((row) => row[columnIndex]));
47
+ const unnestArgs = input.columns.map((_, i) => `${this.paramPlaceholder(i + 1)}::${input.columnArrayTypes[i]}`);
48
+ let sql = `INSERT INTO ${input.table} (${input.columns.join(', ')}) SELECT * FROM UNNEST(${unnestArgs.join(', ')})`;
49
+ if (input.skipDuplicates)
50
+ sql += ' ON CONFLICT DO NOTHING';
51
+ return { sql: `${sql}${this.buildReturningClause(input.returning)}`, params: columnArrays };
52
+ },
53
+ buildUpsertStatement(input) {
54
+ return (`INSERT INTO ${input.table} (${input.insertColumns.join(', ')}) VALUES (${input.valuePlaceholders.join(', ')})` +
55
+ ` ON CONFLICT (${input.conflictColumns.join(', ')}) DO UPDATE SET ${input.updateSetClauses.join(', ')}` +
56
+ this.buildReturningClause(input.returning));
57
+ },
36
58
  buildInsensitiveLike(column, paramRef) {
37
59
  return `${column} ILIKE ${paramRef}`;
38
60
  },
@@ -54,4 +76,56 @@ exports.postgresDialect = {
54
76
  // This hook is the package boundary MySQL/SQLite implementations will fill.
55
77
  return 'unknown';
56
78
  },
79
+ buildColumnType(input) {
80
+ if (input.type === 'VARCHAR' && input.maxLength != null) {
81
+ return `VARCHAR(${input.maxLength})`;
82
+ }
83
+ return input.type;
84
+ },
85
+ buildColumnDefinition(input) {
86
+ const parts = [input.name, this.buildColumnType(input)];
87
+ if (input.primaryKey)
88
+ parts.push('PRIMARY KEY');
89
+ if (input.unique && !input.primaryKey)
90
+ parts.push('UNIQUE');
91
+ if (input.notNull)
92
+ parts.push('NOT NULL');
93
+ if (input.defaultValue != null)
94
+ parts.push(`DEFAULT ${input.defaultValue}`);
95
+ if (input.references)
96
+ parts.push(`REFERENCES ${input.references.table}(${input.references.column})`);
97
+ return parts.join(' ');
98
+ },
99
+ buildPrimaryKeyConstraint(columns) {
100
+ return `PRIMARY KEY (${columns.join(', ')})`;
101
+ },
102
+ buildCreateTableStatement(input) {
103
+ const body = input.definitions.map((d) => ` ${d}`).join(',\n');
104
+ return `CREATE TABLE ${input.table} (\n${body}\n);`;
105
+ },
106
+ buildCreateIndexStatement(input) {
107
+ return `CREATE INDEX ${input.name} ON ${input.table}(${input.columns.join(', ')});`;
108
+ },
109
+ buildMigrationTrackingTable(table) {
110
+ return `
111
+ CREATE TABLE IF NOT EXISTS ${table} (
112
+ id SERIAL PRIMARY KEY,
113
+ name TEXT NOT NULL UNIQUE,
114
+ checksum TEXT NOT NULL,
115
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
116
+ );
117
+ `;
118
+ },
119
+ buildMigrationSelectApplied(table) {
120
+ return `SELECT id, name, applied_at, checksum FROM ${table} ORDER BY id ASC`;
121
+ },
122
+ buildMigrationUpdateChecksum(table) {
123
+ return `UPDATE ${table} SET checksum = ${this.paramPlaceholder(1)} WHERE name = ${this.paramPlaceholder(2)}`;
124
+ },
125
+ buildMigrationInsertApplied(table) {
126
+ return `INSERT INTO ${table} (name, checksum) VALUES (${this.paramPlaceholder(1)}, ${this.paramPlaceholder(2)}) ON CONFLICT (name) DO NOTHING`;
127
+ },
128
+ buildMigrationDeleteApplied(table) {
129
+ return `DELETE FROM ${table} WHERE name = ${this.paramPlaceholder(1)}`;
130
+ },
57
131
  };
@@ -736,7 +736,12 @@ class QueryInterface {
736
736
  const columns = entries.map(([k]) => this.toSqlColumn(k));
737
737
  const params = entries.map(([, v]) => v);
738
738
  const placeholders = entries.map((_, i) => `${this.p(i + 1)}`);
739
- const sql = `INSERT INTO ${this.q(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING *`;
739
+ const sql = this.dialect.buildInsertStatement({
740
+ table: this.q(this.table),
741
+ columns,
742
+ valuePlaceholders: placeholders,
743
+ returning: '*',
744
+ });
740
745
  return {
741
746
  sql,
742
747
  params,
@@ -776,27 +781,24 @@ class QueryInterface {
776
781
  }
777
782
  const keys = Object.keys(args.data[0]).filter((k) => args.data[0][k] !== undefined);
778
783
  const columns = keys.map((k) => this.toColumn(k));
779
- // Build column arrays for UNNEST
780
- const columnArrays = keys.map(() => []);
781
- for (const row of args.data) {
784
+ const rowValues = args.data.map((row) => {
782
785
  const record = row;
783
- keys.forEach((key, i) => {
784
- columnArrays[i].push(record[key]);
785
- });
786
- }
787
- // Use actual Postgres types for array casts
786
+ return keys.map((key) => record[key]);
787
+ });
788
+ // Use actual Postgres types for array casts in the default PostgreSQL dialect.
788
789
  const typeCasts = columns.map((col) => this.getColumnArrayType(col));
789
- const unnestArgs = columnArrays.map((_, i) => `${this.p(i + 1)}::${typeCasts[i]}`);
790
790
  const quotedColumns = columns.map((c) => this.q(c));
791
- let sql = `INSERT INTO ${qt} (${quotedColumns.join(', ')}) SELECT * FROM UNNEST(${unnestArgs.join(', ')})`;
792
- // skipDuplicates: add ON CONFLICT DO NOTHING
793
- if (args.skipDuplicates) {
794
- sql += ` ON CONFLICT DO NOTHING`;
795
- }
796
- sql += ` RETURNING *`;
791
+ const built = this.dialect.buildBulkInsertStatement({
792
+ table: qt,
793
+ columns: quotedColumns,
794
+ rowValues,
795
+ columnArrayTypes: typeCasts,
796
+ skipDuplicates: args.skipDuplicates,
797
+ returning: '*',
798
+ });
797
799
  return {
798
- sql,
799
- params: columnArrays,
800
+ sql: built.sql,
801
+ params: built.params,
800
802
  transform: (result) => result.rows.map((row) => this.parseRow(row, this.table)),
801
803
  tag: `${this.table}.createMany`,
802
804
  };
@@ -825,7 +827,7 @@ class QueryInterface {
825
827
  const whereClause = this.buildWhereClause(whereObj, freshParams);
826
828
  const whereSql = whereClause ? ` WHERE ${whereClause}` : '';
827
829
  this.assertMutationHasPredicate('update', whereSql, args.allowFullTableScan);
828
- return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql} RETURNING *`;
830
+ return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql}${this.dialect.buildReturningClause('*')}`;
829
831
  });
830
832
  // On cache hit, validate predicate
831
833
  if (whereFp === '') {
@@ -874,7 +876,7 @@ class QueryInterface {
874
876
  const clause = this.buildWhereClause(whereObj, freshParams);
875
877
  const whereSql = clause ? ` WHERE ${clause}` : '';
876
878
  this.assertMutationHasPredicate('delete', whereSql, args.allowFullTableScan);
877
- return `DELETE FROM ${this.q(this.table)}${whereSql} RETURNING *`;
879
+ return `DELETE FROM ${this.q(this.table)}${whereSql}${this.dialect.buildReturningClause('*')}`;
878
880
  });
879
881
  // On cache hit, still validate the predicate
880
882
  if (whereFp === '') {
@@ -928,9 +930,14 @@ class QueryInterface {
928
930
  });
929
931
  const updateParams = updateEntries.map(([, v]) => v);
930
932
  const params = [...createParams, ...updateParams];
931
- const sql = `INSERT INTO ${this.q(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})` +
932
- ` ON CONFLICT (${conflictColumns.join(', ')}) DO UPDATE SET ${setClauses.join(', ')}` +
933
- ` RETURNING *`;
933
+ const sql = this.dialect.buildUpsertStatement({
934
+ table: this.q(this.table),
935
+ insertColumns: columns,
936
+ valuePlaceholders: placeholders,
937
+ conflictColumns,
938
+ updateSetClauses: setClauses,
939
+ returning: '*',
940
+ });
934
941
  return {
935
942
  sql,
936
943
  params,
@@ -14,7 +14,7 @@ exports.schemaDiff = schemaDiff;
14
14
  exports.schemaPush = schemaPush;
15
15
  exports.schemaToSQLString = schemaToSQLString;
16
16
  const pg_1 = __importDefault(require("pg"));
17
- const index_js_1 = require("./query/index.js");
17
+ const dialect_js_1 = require("./dialect.js");
18
18
  const schema_js_1 = require("./schema.js");
19
19
  // ---------------------------------------------------------------------------
20
20
  // SQL Generation — SchemaDef → CREATE TABLE statements
@@ -25,7 +25,8 @@ const schema_js_1 = require("./schema.js");
25
25
  * Returns CREATE TABLE statements (in dependency order based on references)
26
26
  * followed by CREATE INDEX statements for foreign key columns.
27
27
  */
28
- function schemaToSQL(schema) {
28
+ function schemaToSQL(schema, options) {
29
+ const dialect = options?.dialect ?? dialect_js_1.postgresDialect;
29
30
  const statements = [];
30
31
  // Topologically sort tables by their foreign key references
31
32
  const sorted = topologicalSort(schema);
@@ -33,12 +34,12 @@ function schemaToSQL(schema) {
33
34
  // Generate CREATE TABLE statements
34
35
  for (const tableName of sorted) {
35
36
  const table = schema.tables[tableName];
36
- statements.push(generateCreateTable(table, resolveRef));
37
+ statements.push(generateCreateTable(table, resolveRef, dialect));
37
38
  }
38
39
  // Generate CREATE INDEX for foreign key columns
39
40
  for (const tableName of sorted) {
40
41
  const table = schema.tables[tableName];
41
- const indexes = generateForeignKeyIndexes(table);
42
+ const indexes = generateForeignKeyIndexes(table, dialect);
42
43
  statements.push(...indexes);
43
44
  }
44
45
  return statements;
@@ -134,42 +135,28 @@ function topologicalSort(schema) {
134
135
  * to their snake_case DDL form, so users can write either camelCase JS
135
136
  * accessor names or snake_case DDL names.
136
137
  */
137
- function generateCreateTable(table, resolveRef) {
138
+ function generateCreateTable(table, resolveRef, dialect = dialect_js_1.postgresDialect) {
138
139
  const tableName = table.name;
139
140
  const columnDefs = [];
140
141
  const compositePk = table.primaryKey && table.primaryKey.length > 0 ? table.primaryKey : null;
141
142
  for (const [fieldName, config] of Object.entries(table.columns)) {
142
- columnDefs.push(generateColumnDef(fieldName, config, resolveRef));
143
+ columnDefs.push(generateColumnDef(fieldName, config, resolveRef, dialect));
143
144
  }
144
145
  // Append a table-level PRIMARY KEY constraint when a composite PK is set.
145
146
  if (compositePk) {
146
- const cols = compositePk.map((c) => (0, index_js_1.quoteIdent)((0, schema_js_1.camelToSnake)(c))).join(', ');
147
- columnDefs.push(`PRIMARY KEY (${cols})`);
147
+ const cols = compositePk.map((c) => dialect.quoteIdentifier((0, schema_js_1.camelToSnake)(c)));
148
+ columnDefs.push(dialect.buildPrimaryKeyConstraint(cols));
148
149
  }
149
- const body = columnDefs.map((d) => ` ${d}`).join(',\n');
150
- return `CREATE TABLE ${(0, index_js_1.quoteIdent)(tableName)} (\n${body}\n);`;
150
+ return dialect.buildCreateTableStatement({
151
+ table: dialect.quoteIdentifier(tableName),
152
+ definitions: columnDefs,
153
+ });
151
154
  }
152
155
  /**
153
156
  * Generate a single column definition line (e.g. "id BIGSERIAL PRIMARY KEY").
154
157
  */
155
- function generateColumnDef(fieldName, config, resolveRef) {
158
+ function generateColumnDef(fieldName, config, resolveRef, dialect = dialect_js_1.postgresDialect) {
156
159
  const snakeName = (0, schema_js_1.camelToSnake)(fieldName);
157
- const parts = [(0, index_js_1.quoteIdent)(snakeName)];
158
- // Type
159
- if (config.type === 'VARCHAR' && config.maxLength != null) {
160
- parts.push(`VARCHAR(${config.maxLength})`);
161
- }
162
- else {
163
- parts.push(config.type);
164
- }
165
- // PRIMARY KEY
166
- if (config.isPrimaryKey) {
167
- parts.push('PRIMARY KEY');
168
- }
169
- // UNIQUE (only if not primary key — PK is implicitly unique)
170
- if (config.isUnique && !config.isPrimaryKey) {
171
- parts.push('UNIQUE');
172
- }
173
160
  // NOT NULL — serial types are implicitly NOT NULL, but explicit is fine.
174
161
  // A column is NOT NULL if:
175
162
  // 1. Explicitly marked .notNull(), OR
@@ -178,25 +165,36 @@ function generateColumnDef(fieldName, config, resolveRef) {
178
165
  // A column is left nullable if .nullable() was called.
179
166
  const isSerial = config.type === 'BIGSERIAL';
180
167
  const implicitNotNull = isSerial || config.isPrimaryKey;
181
- if (config.isNotNull && !implicitNotNull) {
182
- parts.push('NOT NULL');
183
- }
168
+ const notNull = config.isNotNull && !implicitNotNull;
184
169
  // DEFAULT
170
+ let defaultValue;
185
171
  if (config.defaultValue != null) {
186
- const sqlDefault = normalizeDefault(config.defaultValue);
187
- parts.push(`DEFAULT ${sqlDefault}`);
172
+ defaultValue = normalizeDefault(config.defaultValue);
188
173
  }
189
174
  // REFERENCES — resolve the raw table name through the optional resolver so
190
175
  // both camelCase accessor names and snake_case DDL names work.
176
+ let references;
191
177
  if (config.referencesTarget) {
192
178
  const refParts = config.referencesTarget.split('.');
193
179
  if (refParts.length === 2) {
194
180
  const rawTable = refParts[0];
195
181
  const refTable = resolveRef ? resolveRef(rawTable) : rawTable;
196
- parts.push(`REFERENCES ${(0, index_js_1.quoteIdent)(refTable)}(${(0, index_js_1.quoteIdent)(refParts[1])})`);
182
+ references = {
183
+ table: dialect.quoteIdentifier(refTable),
184
+ column: dialect.quoteIdentifier(refParts[1]),
185
+ };
197
186
  }
198
187
  }
199
- return parts.join(' ');
188
+ return dialect.buildColumnDefinition({
189
+ name: dialect.quoteIdentifier(snakeName),
190
+ type: config.type,
191
+ maxLength: config.maxLength,
192
+ primaryKey: config.isPrimaryKey,
193
+ unique: config.isUnique,
194
+ notNull,
195
+ defaultValue,
196
+ references,
197
+ });
200
198
  }
201
199
  /**
202
200
  * Normalize a default value from the user's schema definition to valid SQL.
@@ -236,13 +234,17 @@ function normalizeDefault(val) {
236
234
  * Generate CREATE INDEX statements for foreign key columns.
237
235
  * Only generates indexes for columns that have a REFERENCES clause.
238
236
  */
239
- function generateForeignKeyIndexes(table) {
237
+ function generateForeignKeyIndexes(table, dialect = dialect_js_1.postgresDialect) {
240
238
  const indexes = [];
241
239
  for (const [fieldName, config] of Object.entries(table.columns)) {
242
240
  if (config.referencesTarget) {
243
241
  const snakeName = (0, schema_js_1.camelToSnake)(fieldName);
244
242
  const indexName = `idx_${table.name}_${snakeName}`;
245
- indexes.push(`CREATE INDEX ${(0, index_js_1.quoteIdent)(indexName)} ON ${(0, index_js_1.quoteIdent)(table.name)}(${(0, index_js_1.quoteIdent)(snakeName)});`);
243
+ indexes.push(dialect.buildCreateIndexStatement({
244
+ name: dialect.quoteIdentifier(indexName),
245
+ table: dialect.quoteIdentifier(table.name),
246
+ columns: [dialect.quoteIdentifier(snakeName)],
247
+ }));
246
248
  }
247
249
  }
248
250
  return indexes;
@@ -254,6 +256,7 @@ function generateForeignKeyIndexes(table) {
254
256
  * DDL is needed to make the database match the schema definition.
255
257
  */
256
258
  async function schemaDiff(schema, connectionString) {
259
+ const dialect = dialect_js_1.postgresDialect;
257
260
  const client = new pg_1.default.Client({ connectionString });
258
261
  await client.connect();
259
262
  try {
@@ -313,11 +316,11 @@ async function schemaDiff(schema, connectionString) {
313
316
  const ddlName = tableDef.name;
314
317
  if (!existingTables.has(ddlName)) {
315
318
  result.create.push(tableDef);
316
- result.statements.push(generateCreateTable(tableDef, resolveRef));
317
- const fkIndexes = generateForeignKeyIndexes(tableDef);
319
+ result.statements.push(generateCreateTable(tableDef, resolveRef, dialect));
320
+ const fkIndexes = generateForeignKeyIndexes(tableDef, dialect);
318
321
  result.statements.push(...fkIndexes);
319
322
  // Reverse: DROP TABLE (with indexes — they drop automatically)
320
- result.reverseStatements.unshift(`DROP TABLE IF EXISTS ${(0, index_js_1.quoteIdent)(ddlName)} CASCADE;`);
323
+ result.reverseStatements.unshift(`DROP TABLE IF EXISTS ${dialect.quoteIdentifier(ddlName)} CASCADE;`);
321
324
  }
322
325
  }
323
326
  // Tables to drop (in DB but not in schema)
@@ -341,9 +344,9 @@ async function schemaDiff(schema, connectionString) {
341
344
  const dbCol = dbCols[snakeName];
342
345
  if (!dbCol) {
343
346
  // Column exists in schema but not in DB — ADD COLUMN
344
- const colDef = generateColumnDef(fieldName, config, resolveRef);
345
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ADD COLUMN ${colDef};`;
346
- const reverseSql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} DROP COLUMN ${(0, index_js_1.quoteIdent)(snakeName)};`;
347
+ const colDef = generateColumnDef(fieldName, config, resolveRef, dialect);
348
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ADD COLUMN ${colDef};`;
349
+ const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} DROP COLUMN ${dialect.quoteIdentifier(snakeName)};`;
347
350
  alterDef.columns.push({ column: snakeName, action: 'add', sql, reverseSql });
348
351
  result.statements.push(sql);
349
352
  result.reverseStatements.unshift(reverseSql);
@@ -354,8 +357,8 @@ async function schemaDiff(schema, connectionString) {
354
357
  if (expectedUdt && dbCol.udtName !== expectedUdt) {
355
358
  const sqlType = config.type === 'VARCHAR' && config.maxLength ? `VARCHAR(${config.maxLength})` : config.type;
356
359
  const oldSqlType = udtToSqlType(dbCol.udtName, dbCol.maxLength);
357
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} TYPE ${sqlType} USING ${(0, index_js_1.quoteIdent)(snakeName)}::${sqlType};`;
358
- const reverseSql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} TYPE ${oldSqlType} USING ${(0, index_js_1.quoteIdent)(snakeName)}::${oldSqlType};`;
360
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} TYPE ${sqlType} USING ${dialect.quoteIdentifier(snakeName)}::${sqlType};`;
361
+ const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} TYPE ${oldSqlType} USING ${dialect.quoteIdentifier(snakeName)}::${oldSqlType};`;
359
362
  alterDef.columns.push({ column: snakeName, action: 'alter_type', sql, reverseSql });
360
363
  result.statements.push(sql);
361
364
  result.reverseStatements.unshift(reverseSql);
@@ -364,15 +367,15 @@ async function schemaDiff(schema, connectionString) {
364
367
  const shouldBeNotNull = config.isNotNull || config.isPrimaryKey || config.type === 'BIGSERIAL';
365
368
  const isCurrentlyNullable = dbCol.isNullable;
366
369
  if (shouldBeNotNull && isCurrentlyNullable && !config.isNullable) {
367
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} SET NOT NULL;`;
368
- const reverseSql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} DROP NOT NULL;`;
370
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} SET NOT NULL;`;
371
+ const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} DROP NOT NULL;`;
369
372
  alterDef.columns.push({ column: snakeName, action: 'set_not_null', sql, reverseSql });
370
373
  result.statements.push(sql);
371
374
  result.reverseStatements.unshift(reverseSql);
372
375
  }
373
376
  else if (!shouldBeNotNull && !isCurrentlyNullable && config.isNullable) {
374
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} DROP NOT NULL;`;
375
- const reverseSql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} SET NOT NULL;`;
377
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} DROP NOT NULL;`;
378
+ const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} SET NOT NULL;`;
376
379
  alterDef.columns.push({ column: snakeName, action: 'drop_not_null', sql, reverseSql });
377
380
  result.statements.push(sql);
378
381
  result.reverseStatements.unshift(reverseSql);
@@ -384,16 +387,16 @@ async function schemaDiff(schema, connectionString) {
384
387
  const dbDefault = dbCol.columnDefault;
385
388
  if (schemaDefault && !dbDefault) {
386
389
  // Schema has default, DB doesn't
387
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} SET DEFAULT ${schemaDefault};`;
388
- const reverseSql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} DROP DEFAULT;`;
390
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} SET DEFAULT ${schemaDefault};`;
391
+ const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} DROP DEFAULT;`;
389
392
  alterDef.columns.push({ column: snakeName, action: 'set_default', sql, reverseSql });
390
393
  result.statements.push(sql);
391
394
  result.reverseStatements.unshift(reverseSql);
392
395
  }
393
396
  else if (!schemaDefault && dbDefault && !isSequenceDefault(dbDefault)) {
394
397
  // DB has a non-sequence default, schema doesn't
395
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} DROP DEFAULT;`;
396
- const reverseSql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} SET DEFAULT ${dbDefault};`;
398
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} DROP DEFAULT;`;
399
+ const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} SET DEFAULT ${dbDefault};`;
397
400
  alterDef.columns.push({ column: snakeName, action: 'drop_default', sql, reverseSql });
398
401
  result.statements.push(sql);
399
402
  result.reverseStatements.unshift(reverseSql);
@@ -403,8 +406,8 @@ async function schemaDiff(schema, connectionString) {
403
406
  !isSequenceDefault(dbDefault) &&
404
407
  !defaultsMatch(schemaDefault, dbDefault)) {
405
408
  // Both have defaults but they differ
406
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} SET DEFAULT ${schemaDefault};`;
407
- const reverseSql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ALTER COLUMN ${(0, index_js_1.quoteIdent)(snakeName)} SET DEFAULT ${dbDefault};`;
409
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} SET DEFAULT ${schemaDefault};`;
410
+ const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ALTER COLUMN ${dialect.quoteIdentifier(snakeName)} SET DEFAULT ${dbDefault};`;
408
411
  alterDef.columns.push({ column: snakeName, action: 'set_default', sql, reverseSql });
409
412
  result.statements.push(sql);
410
413
  result.reverseStatements.unshift(reverseSql);
@@ -416,16 +419,16 @@ async function schemaDiff(schema, connectionString) {
416
419
  const wantsUnique = config.isUnique === true;
417
420
  if (wantsUnique && !hasDbUnique) {
418
421
  const constraintName = `${tableName}_${snakeName}_key`;
419
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ADD CONSTRAINT ${(0, index_js_1.quoteIdent)(constraintName)} UNIQUE (${(0, index_js_1.quoteIdent)(snakeName)});`;
420
- const reverseSql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} DROP CONSTRAINT ${(0, index_js_1.quoteIdent)(constraintName)};`;
422
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ADD CONSTRAINT ${dialect.quoteIdentifier(constraintName)} UNIQUE (${dialect.quoteIdentifier(snakeName)});`;
423
+ const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} DROP CONSTRAINT ${dialect.quoteIdentifier(constraintName)};`;
421
424
  alterDef.columns.push({ column: snakeName, action: 'add_unique', sql, reverseSql });
422
425
  result.statements.push(sql);
423
426
  result.reverseStatements.unshift(reverseSql);
424
427
  }
425
428
  else if (!wantsUnique && hasDbUnique) {
426
429
  const constraintName = tableUniques[snakeName];
427
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} DROP CONSTRAINT ${(0, index_js_1.quoteIdent)(constraintName)};`;
428
- const reverseSql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} ADD CONSTRAINT ${(0, index_js_1.quoteIdent)(constraintName)} UNIQUE (${(0, index_js_1.quoteIdent)(snakeName)});`;
430
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} DROP CONSTRAINT ${dialect.quoteIdentifier(constraintName)};`;
431
+ const reverseSql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ADD CONSTRAINT ${dialect.quoteIdentifier(constraintName)} UNIQUE (${dialect.quoteIdentifier(snakeName)});`;
429
432
  alterDef.columns.push({ column: snakeName, action: 'drop_unique', sql, reverseSql });
430
433
  result.statements.push(sql);
431
434
  result.reverseStatements.unshift(reverseSql);
@@ -436,7 +439,7 @@ async function schemaDiff(schema, connectionString) {
436
439
  for (const dbColName of Object.keys(dbCols)) {
437
440
  const hasField = Object.entries(tableDef.columns).some(([fieldName]) => (0, schema_js_1.camelToSnake)(fieldName) === dbColName);
438
441
  if (!hasField) {
439
- const sql = `ALTER TABLE ${(0, index_js_1.quoteIdent)(tableName)} DROP COLUMN ${(0, index_js_1.quoteIdent)(dbColName)};`;
442
+ const sql = `ALTER TABLE ${dialect.quoteIdentifier(tableName)} DROP COLUMN ${dialect.quoteIdentifier(dbColName)};`;
440
443
  const reverseSql = `-- Cannot auto-reverse DROP COLUMN for "${dbColName}" — add it back manually`;
441
444
  alterDef.columns.push({ column: dbColName, action: 'drop', sql, reverseSql });
442
445
  // Don't auto-add drops to statements for safety — user must opt in
@@ -569,7 +572,7 @@ async function schemaPush(schema, connectionString, options = {}) {
569
572
  * Generate the full DDL as a single formatted string.
570
573
  * Useful for printing or saving to a .sql file.
571
574
  */
572
- function schemaToSQLString(schema) {
573
- const statements = schemaToSQL(schema);
575
+ function schemaToSQLString(schema, options) {
576
+ const statements = schemaToSQL(schema, options);
574
577
  return `${statements.join('\n\n')}\n`;
575
578
  }
@@ -12,6 +12,7 @@
12
12
  * DROP TABLE users;
13
13
  */
14
14
  import type { DatabaseAdapter } from '../adapters/index.js';
15
+ import { type Dialect } from '../dialect.js';
15
16
  export interface MigrationFile {
16
17
  /** Full filename (e.g. "20260325120000_create_users.sql") */
17
18
  filename: string;
@@ -115,6 +116,7 @@ export declare function migrateUp(connectionString: string, migrationsDir: strin
115
116
  allowDrift?: boolean /** @deprecated use allowDrift */;
116
117
  force?: boolean;
117
118
  adapter?: DatabaseAdapter;
119
+ dialect?: Dialect;
118
120
  }): Promise<{
119
121
  applied: MigrationFile[];
120
122
  errors: Array<{
@@ -133,6 +135,7 @@ export declare function migrateUp(connectionString: string, migrationsDir: strin
133
135
  export declare function migrateDown(connectionString: string, migrationsDir: string, options?: {
134
136
  step?: number;
135
137
  adapter?: DatabaseAdapter;
138
+ dialect?: Dialect;
136
139
  }): Promise<{
137
140
  rolledBack: MigrationFile[];
138
141
  errors: Array<{
@@ -144,5 +147,7 @@ export declare function migrateDown(connectionString: string, migrationsDir: str
144
147
  * Get the status of all migrations (applied vs pending).
145
148
  * Includes checksum validation for applied migrations.
146
149
  */
147
- export declare function migrateStatus(connectionString: string, migrationsDir: string): Promise<MigrationStatus[]>;
150
+ export declare function migrateStatus(connectionString: string, migrationsDir: string, options?: {
151
+ dialect?: Dialect;
152
+ }): Promise<MigrationStatus[]>;
148
153
  //# sourceMappingURL=migrate.d.ts.map